Skip to content

feat(website): TRI tab, the app at app.t27.ai inside the Queen - #1011

Merged
gHashTag merged 3 commits into
mainfrom
feat/queen-tri-tab
Sep 14, 2026
Merged

gHashTag merged 3 commits into
mainfrom
feat/queen-tri-tab

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

Description

A thirteenth Queen tab, TRI (key r): the app at app.t27.ai inside the
game. A row of its screens (Feed, Agent, AI, Profile, CRM) sits over one frame
of the real app page, https://app.t27.ai/<route>?embed=1&lang=<lang>.

Every screen has its own address, #/queen?tab=tri&screen=chat, plus path=
for one profile. A click writes it and a reload reads it. An address changed
from outside (Back, a link, a script) moves the screen and the frame. Stages the
app switches inside the frame (the AI pipeline) come back as a route message and
are written to the address without reloading the frame.

Closes #1010

Related Issue

#1010. The player side is gHashTag/999-multibots-telegraf
feat/player-embed-for-game: it lets t27.ai frame the app, adds embed mode and
the route bridge, and keeps the tab's sign-in out of frames by other sites. It
ships and is verified live before this PR merges.

Changes Made

  • src/lib/triScreens.ts (new): the screens, their app routes, the deep-path
    allowlist, the message filter and the address builder. Frame URLs are always
    APP_ORIGIN plus a listed route. Only plain-object messages from app.t27.ai
    and from TRI's own frame are heard, and nothing is posted into the frame.
  • src/components/QueenTri.tsx + .css (new): the screen row, the frame, and
    a strip when the app has not answered 8 s after load (a frame refused by
    frame-ancestors still fires load). One frame element for the life of the
    view, moved with contentWindow.location.replace, so a screen click adds no
    history entry and Back walks the app's own pages.
  • Queen.tsx, queenHud.ts, queenModules.ts, QueenCommand.tsx: the tab,
    key r, command entry and copy. Both address writers (the shell's tab and
    TRI's screen) build from the live hash, because React Router 7.13 hands the
    updater stale params inside a pending transition.
  • App.tsx: no TRI preview on the landing. An embedded Queen that reaches TRI
    frames nothing and says so.
  • CRM client ids are never put in the address (they are customers' Telegram
    ids); the CRM is addressed at its list.
  • QA: qa/tri-screens-contract.mjs (pure, in website-checks) and
    qa/queen-tri-contract.mjs (built site in Chrome, 14 checks). The viewport
    gate derives its command count from HUD_VIEWS, and agents-spec-contract
    knows the new module.

🔥 TOXIC VERDICT

What Works

  • queen-tri-contract 14 of 14 on the build. Checks 5, 12 and 14 fail on the
    previous build with the real defects (a CRM id in the hash, Back stuck twice,
    an embedded Queen framing the app).
  • tri-screens-contract kills 12 of 12 independent mutants of triScreens.ts,
    including the three a reviewer found surviving.

What Doesn't Work / Limits

  • On t27.ai, TRI is a guest. Telegram's login widget only allows app.t27.ai as
    an ancestor, and the player deliberately keeps the tab's session out of
    frames by other sites. Agent, AI, Profile and CRM say "sign in inside the
    app" with a link out. Identity in the game comes with the single sign-on step.
  • Until the app replaces instead of pushing in embed mode, history entries
    pushed in the frame before leaving TRI for another tab stay dead Back steps.
  • The Chrome contract runs with site isolation off so CDP can stub the app
    frame; out-of-process iframe behaviour is not measured.
  • check:tools and check:docs fail identically on the base commit.

Overall Assessment: APPROVE after the player is live. Self-Score: 7/10.

🌳 TECH TREE Options

  1. Single sign-on via a scoped game token (identity chip, live sign-in/out). ★★★★★
  2. Replace instead of push inside the embed so one Back always leaves TRI. ★★★☆☆
  3. Measure the tab in Safari and Telegram WebViews. ★★★☆☆
{
  "version": 1,
  "head_sha": "fb11082699ab82a67749b922e8365d50fdc82710",
  "summary": "The Queen gains a TRI tab that frames the real app.t27.ai screens (feed, agent, AI pipeline, profile, CRM) with one address per screen, route sync from the app, a no-answer strip, and guards that keep customer ids out of the address and never post into the frame.",
  "changes": [
    "apps/website/src/lib/triScreens.ts: screen list, app routes, deep-path allowlist, message filter and address builder for the TRI tab.",
    "apps/website/src/components/QueenTri.tsx and QueenTri.css: the screen row, a single app frame moved with location.replace, and the strip shown when the app does not answer.",
    "apps/website/src/pages/Queen.tsx, components/queenHud.ts, lib/queenModules.ts, components/QueenCommand.tsx: the tri tab, key r, command entry and copy, with both address writers built from the live hash.",
    "apps/website/src/App.tsx: no TRI preview on the landing; an embedded Queen shows a notice instead of framing the app.",
    "apps/website/qa/tri-screens-contract.mjs, qa/queen-tri-contract.mjs, website-checks.yml, package.json: new contracts; queen-viewport-contract and agents-spec-contract updated for the thirteenth tab."
  ],
  "tests": [
    {
      "command": "npm run check:tri (qa/tri-screens-contract.mjs)",
      "status": "passed",
      "result": "10 screens, 29 app paths, key r at position 13; 12 of 12 independent mutants of triScreens.ts fail it.",
      "evidence": "Local run in the feat/queen-tri-tab worktree on 2026-09-14; mutants from the fix pass mutate.sh"
    },
    {
      "command": "node qa/queen-tri-contract.mjs --no-build on the branch build",
      "status": "passed",
      "result": "14 of 14 checks pass, including Back after in-frame pushes, the replace control and an embedded Queen asking app.t27.ai for nothing.",
      "evidence": "Session scratchpad tri-tab/game/fix2/tri-full.log"
    },
    {
      "command": "node qa/queen-tri-contract.mjs --only 1,5,12,13,14 on the previous build",
      "status": "failed",
      "result": "Control, expected to fail: check 5 shows a CRM id in the hash and check 12 shows Back staying on the same frame twice.",
      "evidence": "Session scratchpad tri-tab/game/fix2 logs of the previous build"
    },
    {
      "command": "npm run typecheck:ratchet; npx eslint on the changed files; npx vite build",
      "status": "passed",
      "result": "179 type errors against a baseline of 179 with no file gaining errors, eslint clean, production build succeeds.",
      "evidence": "Local runs in the feat/queen-tri-tab worktree on 2026-09-14"
    },
    {
      "command": "npm run check:tools; npm run check:docs",
      "status": "failed",
      "result": "Both fail with the same assertions and hashes on a git archive copy of the base commit, so the failures predate this change.",
      "evidence": "Local runs on the branch and on the base archive, 2026-09-14"
    }
  ],
  "limitations": [
    "On t27.ai the TRI frame is a guest: screens that need a person link out to app.t27.ai until single sign-on ships.",
    "History entries the app pushes inside the frame before the user leaves TRI for another tab remain dead Back steps until the app replaces in embed mode.",
    "The Chrome contract runs with site isolation off to stub the app frame, and Safari, Firefox and Telegram WebViews were not measured."
  ],
  "tags": ["Website", "Queen", "Integration"],
  "blog": {
    "title": "The app inside the game",
    "summary": "The Queen board on t27.ai now frames the TRI app's real screens, one address per screen, while the app keeps its sign-in out of frames by other sites.",
    "outline": [
      "The ask: every screen of the TRI app, from the feed to the CRM, reachable from inside the Queen game with its own address.",
      "The shape: one frame of the real app page in embed mode, a screen row, and route messages that write the app's stage back into the game's address.",
      "The traps: router updaters that erase each other inside a transition, a fresh iframe per screen that stranded Back, and customer ids leaking into links.",
      "The security turn: t27.ai and app.t27.ai are one site, so the frame shared the tab's sign-in until the app stopped trusting frames by other sites.",
      "What is next: a scoped game token for single sign-on, replace-only navigation inside the embed, and measurements beyond Chrome."
    ]
  }
}

🤖 Generated with Claude Code

gHashTag and others added 3 commits September 15, 2026 00:45
…D_VIEWS

The gate pinned commands !== 11 while the rail already had 12 views (#980 added
PROJECT and left the pin). On the base commit 429499e it reports
"COUNT commands=12" at every size, next to other long-standing failures
(undeclared scrollers, the alerts slot, views=0 on the comb) that this commit
does not touch. The count is now read from src/components/queenHud.ts HUD_VIEWS,
so a new view cannot leave it stale again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n per address

A thirteenth Queen tab, TRI (key r; the digits are spent, t is TOOLS, p is
PROJECT). A row of the app's screens - Feed, Agent, AI, Profile, CRM - over one
frame of the real app page, https://app.t27.ai/<route>?embed=1&lang=<lang>.

Every screen has its own address: #/queen?tab=tri&screen=chat, and path= for
one CRM client (/crm/<id>, /crm/<id>/chat) or one profile (/<username>). A click
writes it, a reload reads it, an address changed from outside (Back, a link, a
script) moves the screen and the frame. Stages the app switches inside the frame
come back as {type:'t27-app', kind:'route', path} and are written to the address
without reloading the frame. The frame URL is always APP_ORIGIN plus a route
from src/lib/triScreens.ts or a deep path checked against it.

- Both address writers (the shell's tab, TRI's screen) build from the live hash:
  React Router 7.13 hands the setSearchParams updater the params of the hook's
  last render, and its navigations wait in a transition, so two quick writes
  built that way erased each other. Leaving TRI drops screen= and path=.
- An address that lags the live hash is TRI's own pending write, not an outside
  change, so it never moves the screen back or reloads the frame.
- The frame is keyed per screen: a fresh browsing context adds no history entry,
  so Back leaves TRI instead of walking the frame. name="t27-embed" marks it.
- Load is not success (a frame refused by frame-ancestors still loads Chrome's
  error page): without the app's own message 8 s after load, a strip says the
  app did not answer and links out to the same screen.
- Only plain-object messages from app.t27.ai and from TRI's own frame are heard;
  nothing is ever posted into the frame.
- Inside the app (its Hive frames this game) TRI frames nothing and says so.
- The phone rail scrolls its active button into view (13 items, about 6 fit).
- The landing renders no TRI preview: it would load the whole app for every
  visitor inside a sandbox where its link out cannot open.

Checks: qa/tri-screens-contract.mjs (pure, added to website-checks.yml as
check:tri) and qa/queen-tri-contract.mjs (browser, app frames answered by a CDP
Fetch stub; fails at check 1 on 429499e; a combined mutant - route message
reloading the frame, tabAddress keeping screen=, no rail scroll - fails checks
4, 8 and 11). agents-spec-contract pins keys 1234567890tpr and TRI on r.

Production app.t27.ai still refuses the frame (frame-ancestors has no t27.ai)
until the player's CSP and embed bridge ship; TRI then shows its strip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review findings on the TRI tab.

- Back after the app navigates inside the frame. The app's router pushes a
  history entry for every in-app page (AiPipelineNav, CRM links). A fresh
  iframe element per screen stranded those entries, so after two in-frame
  pages and a screen click Back did nothing twice (measured on the previous
  build). TRI now keeps one frame element for the life of the view and moves
  it with contentWindow.location.replace: a screen click still adds no entry,
  and Back walks the app's own pages with screen= following the app's route
  message. The src attribute stays the first URL (changing it would push);
  data-src names where TRI sent the frame. The full fix is on the app's side:
  in embed mode it should replace instead of push, and then one Back leaves
  TRI (check 13 pins that as a control). Entries pushed in the frame before
  leaving TRI for another tab still stay dead until the app replaces.
- path= no longer carries a CRM client. /crm/<id> ids are customers'
  Telegram ids and would sit in the t27.ai address bar, history and copied
  links. The CRM is addressed at its list; path= is kept for one profile.
- A trailing slash no longer drops the profile path (/alice/ -> /alice).
- The iframe's name="t27-embed" is gone: the app never read it (it decides
  embed from ?embed=1 and its parent and keeps its own marker in window.name).
- An embedded Queen (a landing preview, embed=1) reached TRI by the r key and
  framed the whole app inside the preview's sandbox. TRI there frames nothing
  and says so (new COPY key triPreview, en and ru).

Checks: qa/tri-screens-contract.mjs pins no CRM path, the slash, JSON
strings never parsed, and lang encoded as one query value; it fails on the
previous triScreens.ts, and 12 of 12 independent mutants fail it.
qa/queen-tri-contract.mjs: check 5 now covers the CRM id and a profile path,
new checks 12 (in-frame pushes, a screen click, Back walks them), 13 (control:
an app that replaces leaves TRI on one Back) and 14 (embedded Queen plus r
asks app.t27.ai for nothing); 5, 12 and 14 fail on the previous build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gHashTag
gHashTag merged commit 4df991a into main Sep 14, 2026
29 of 35 checks passed
github-actions Bot added a commit that referenced this pull request Sep 14, 2026
feat(website): TRI tab, the app at app.t27.ai inside the Queen (#1011)

* test(website): derive the Queen viewport gate's command count from HUD_VIEWS

The gate pinned commands !== 11 while the rail already had 12 views (#980 added
PROJECT and left the pin). On the base commit 429499e it reports
"COUNT commands=12" at every size, next to other long-standing failures
(undeclared scrollers, the alerts slot, views=0 on the comb) that this commit
does not touch. The count is now read from src/components/queenHud.ts HUD_VIEWS,
so a new view cannot leave it stale again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): TRI, the app at app.t27.ai inside the Queen, one screen per address

A thirteenth Queen tab, TRI (key r; the digits are spent, t is TOOLS, p is
PROJECT). A row of the app's screens - Feed, Agent, AI, Profile, CRM - over one
frame of the real app page, https://app.t27.ai/<route>?embed=1&lang=<lang>.

Every screen has its own address: #/queen?tab=tri&screen=chat, and path= for
one CRM client (/crm/<id>, /crm/<id>/chat) or one profile (/<username>). A click
writes it, a reload reads it, an address changed from outside (Back, a link, a
script) moves the screen and the frame. Stages the app switches inside the frame
come back as {type:'t27-app', kind:'route', path} and are written to the address
without reloading the frame. The frame URL is always APP_ORIGIN plus a route
from src/lib/triScreens.ts or a deep path checked against it.

- Both address writers (the shell's tab, TRI's screen) build from the live hash:
  React Router 7.13 hands the setSearchParams updater the params of the hook's
  last render, and its navigations wait in a transition, so two quick writes
  built that way erased each other. Leaving TRI drops screen= and path=.
- An address that lags the live hash is TRI's own pending write, not an outside
  change, so it never moves the screen back or reloads the frame.
- The frame is keyed per screen: a fresh browsing context adds no history entry,
  so Back leaves TRI instead of walking the frame. name="t27-embed" marks it.
- Load is not success (a frame refused by frame-ancestors still loads Chrome's
  error page): without the app's own message 8 s after load, a strip says the
  app did not answer and links out to the same screen.
- Only plain-object messages from app.t27.ai and from TRI's own frame are heard;
  nothing is ever posted into the frame.
- Inside the app (its Hive frames this game) TRI frames nothing and says so.
- The phone rail scrolls its active button into view (13 items, about 6 fit).
- The landing renders no TRI preview: it would load the whole app for every
  visitor inside a sandbox where its link out cannot open.

Checks: qa/tri-screens-contract.mjs (pure, added to website-checks.yml as
check:tri) and qa/queen-tri-contract.mjs (browser, app frames answered by a CDP
Fetch stub; fails at check 1 on 429499e; a combined mutant - route message
reloading the frame, tabAddress keeping screen=, no rail scroll - fails checks
4, 8 and 11). agents-spec-contract pins keys 1234567890tpr and TRI on r.

Production app.t27.ai still refuses the frame (frame-ancestors has no t27.ai)
until the player's CSP and embed bridge ship; TRI then shows its strip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): TRI keeps Back usable and customer ids out of the address

Review findings on the TRI tab.

- Back after the app navigates inside the frame. The app's router pushes a
  history entry for every in-app page (AiPipelineNav, CRM links). A fresh
  iframe element per screen stranded those entries, so after two in-frame
  pages and a screen click Back did nothing twice (measured on the previous
  build). TRI now keeps one frame element for the life of the view and moves
  it with contentWindow.location.replace: a screen click still adds no entry,
  and Back walks the app's own pages with screen= following the app's route
  message. The src attribute stays the first URL (changing it would push);
  data-src names where TRI sent the frame. The full fix is on the app's side:
  in embed mode it should replace instead of push, and then one Back leaves
  TRI (check 13 pins that as a control). Entries pushed in the frame before
  leaving TRI for another tab still stay dead until the app replaces.
- path= no longer carries a CRM client. /crm/<id> ids are customers'
  Telegram ids and would sit in the t27.ai address bar, history and copied
  links. The CRM is addressed at its list; path= is kept for one profile.
- A trailing slash no longer drops the profile path (/alice/ -> /alice).
- The iframe's name="t27-embed" is gone: the app never read it (it decides
  embed from ?embed=1 and its parent and keeps its own marker in window.name).
- An embedded Queen (a landing preview, embed=1) reached TRI by the r key and
  framed the whole app inside the preview's sandbox. TRI there frames nothing
  and says so (new COPY key triPreview, en and ru).

Checks: qa/tri-screens-contract.mjs pins no CRM path, the slash, JSON
strings never parsed, and lang encoded as one query value; it fails on the
previous triScreens.ts, and 12 of 12 independent mutants fail it.
qa/queen-tri-contract.mjs: check 5 now covers the CRM id and a profile path,
new checks 12 (in-frame pushes, a screen click, Back walks them), 13 (control:
an app that replaces leaves TRI on one Back) and 14 (embedded Queen plus r
asks app.t27.ai for nothing); 5, 12 and 14 fail on the previous build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@gHashTag

Copy link
Copy Markdown
Owner Author

Verified live on https://t27.ai after the publisher run (ghashtag.github.io "Publish assets/index-DRlHf4KG.js from trinity 4df991a"; tree of the merge equals the tree of the reviewed branch, 613407a0). The player side (gHashTag/999-multibots-telegraf#2407) was deployed and verified first.

Headless Chrome, one tab, the way a visitor gets there: an app.t27.ai document first stores a fake session value in that tab, then the same tab opens the Queen.

check result
control: top-level app.t27.ai/chat renders its header pass
#/queen?tab=tri&screen=chat shows TRI on chat pass
frame is https://app.t27.ai/chat?embed=1&lang=ru and loads (not refused by frame-ancestors) pass
frame in embed mode (marker set, no header), ancestor https://t27.ai pass
armed: the frame can read the tab's fake session value pass
TRI frame present and no "did not answer" strip after 9 s pass
click Feed: address drops screen=, frame document moves to /feed pass
outside hash change moves TRI to profile pass
no request carries the tab's session as Authorization (146 requests, 8 API calls) pass, 0 leaked

11 of 11, no page exceptions. The general Queen tab probe on the same bundle: 50 of 50 (the thirteenth tab included).

Negative control: the same probe on the previous live build (index-CDBPZ6ez.js, run finished one second before the switch) failed the TRI checks. It also exposed a vacuous check ("no strip" passes when there is no frame at all); that check now requires the frame first.

Not verified: Safari, Firefox, Telegram WebViews; identity inside TRI (the frame is a guest by design until single sign-on ships).

gHashTag added a commit that referenced this pull request Sep 14, 2026
…step ladder, lang toggle, phone core (#1014)

* chore(website): regenerate system-docs.json from the committed catalogs

public/docs/system-docs.json had drifted from the catalogs it is generated
from: check:docs failed at 429499e with "public/docs/system-docs.json is
stale or hand-edited" (the committed ladder said tools 91 while
public/tools/spec-tools.json holds 92). This is scripts/docs-from-specs.mjs as
committed, run on the committed catalogs, with the file's own generatedAt
(2026-09-12T12:30:15.363Z) passed through generate({ generatedAt }), so the
stamp keeps its milliseconds; the CLI's SOURCE_DATE_EPOCH takes whole seconds.

No new content, only what the catalogs already said: the mcp/inngest-dev row in
the tools table, a third tool for agent T in the ring and the bindings graph,
tools 91 -> 92 in the ladder and its figures, the external and per-repository
MCP lists, the tool counts in the witnesses table, and the sha256 of the source
files read at generation time. It goes first so the ladder change that follows
shows only its own rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): the Queen's language toggle writes ?lang= to the address

On /?lang=ru#/queen?tab=functions the HUD toggle switched the page to English
but left ?lang=ru in the address, so a reload came back in Russian. The
header's LanguageSwitcher already writes ?lang= with replaceState; the Queen's
toggle (the HUD tool button and the menu entry) now does the same, keeping
history.state, where HashRouter keeps its entry index.

Measured on local builds in headless Chrome (CDP, 1440x900):
before: after the toggle the search is still ?lang=ru with the document in en,
  and a reload is ru again (address agrees: false; reload keeps it: false).
after: the search is ?lang=en after the toggle, and a reload stays en.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): the Skill Explorer's link labels name the spec binding they count

#/skills counted "No spec 23", and a card said "No spec declared yet", for
skills whose own card shows witness spec+code and a spec at
specs/skills/<id>.t27 (t27/wave-audit, for one). The filter and the chip
measure whether a skill declares the spec it stands on -- a binding the build
checks -- not whether a card spec exists. The labels now say that; the data and
the counts are unchanged.

EN: No spec -> Unbound; No spec declared yet -> No spec binding declared yet;
Skills without a spec -> Skills that declare no spec binding;
"without a spec: {n}" -> "unbound: {n}".
RU: Со спекой -> С привязкой; Без спеки -> Без привязки;
Спека ещё не объявлена -> Привязка к спеке ещё не объявлена;
Скилы без спеки -> Скилы, не объявившие привязку к спеке;
"без спеки: {n}" -> "без привязки: {n}".

Measured on local builds in headless Chrome, #/skills?skill=t27%2Fwave-audit
in en and ru: before, the chip read "No spec" / "Без спеки" and the filters
"No spec 23" / "Без спеки 23"; after, "Unbound" / "Без привязки" with the same
counts (Bound 3, Unbound 23, Broken 0). check:explorer-languages passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): Shared core can be tapped on a phone

At 390x844 (touch emulation) on #/queen the worlds nav in the HUD head showed
only the world select and the connect button: QueenUniverse.css hid the last
nav button (Shared core) and the one before it (Game home) below 640px, and
inside the head the nav is display: contents, so the hidden buttons measured
0x0 and view=core had no way in on a phone. Shared core now stays. Inside the
shell's head the world select is capped at 150px, which frees the width the
button needs, so the head keeps its height. Game home and the T27 shortcut
stay hidden on a phone, as before.

The variant was chosen by injecting candidate CSS into the unmodified build
before any source change: unhiding the button alone pushed it onto a second
row (head 110px -> 142px, field 32px shorter); unhiding it and capping the
select kept the head at 110px.

Measured on local builds in headless Chrome at 390x844 with touch, #/queen and
#/queen?tab=crons: before, Shared core measured 0x0 and a tap could not reach
view=core; after, it is 93x26 and tappable, a tap opens view=core, the head is
110px tall and the document is still 390x844 (one screen), with every nav
control inside the viewport.

qa/queen-viewport-contract.mjs --no-build reports the same on both builds: the
same 30 size/view combinations fail with the same messages (COUNT commands=12,
STATUS SLOT MISSING: alerts, undeclared scrollers such as .queen27-hud-intel),
390x844 included, so those failures predate this change and it adds none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): one ladder of six steps wherever the site draws or names it

The layer ladder was stated several ways. On #/docs the figure said "The
five-layer ladder: Specs → Skills → Crons → Agents → Tools" and the
ladder-counts table had five rows; the PROJECT copy (queenModules.ts, the
Queen's PROJECT directive) and the docs meta description said "five-layer
ladder"; the Agent, Tool and Function Explorers drew six steps ending
Functions 28, with the Function Explorer's caption naming a ladder without
Tools; and the Skill and Cron Explorers drew no ladder at all.

Now one statement: Specs → Skills → Crons → Agents → Tools → Functions.
- scripts/agents-from-specs.mjs writes the ladder into spec-skills.json and
  spec-crons.json as well; the Skill and Cron Explorers draw it (LayerLadder,
  en/ru keys), and the Function Explorer's caption reads "ladder" like the rest.
- scripts/docs-from-specs.mjs counts functions in docs.ladder and adds a
  Functions row to the ladder-counts table; LadderFigure draws a sixth step and
  its caption says six steps, in en and ru.
- "six-step ladder" in the PROJECT copy (queenModules.ts, Queen.tsx) and in
  the docs meta description, in en and ru.
- The contracts follow: agents-spec-contract (the skills and crons catalogs
  carry the agents' ladder), docs-spec-contract and docs-from-specs.test
  (functions in the ladder and in the table).

The data is regenerated with the repository's generators, each file keeping
its generatedAt to the millisecond: spec-skills.json and spec-crons.json gain
only `ladder` (and a new contentSha256); spec-agents, spec-tools and
spec-functions come out byte-identical; system-docs.json gains the functions
count, the table row and the figure step.

Not changed here: the chapter's own title and prose, "The five-layer system"
and "Specs -> Skills -> Crons -> Agents -> Tools", are vendored from
gHashTag/t27 (public/t27/files/docs/system/layers.md and
specs/docs/chapters/layers.t27, pinned by sha256 in the docs contract), and
upstream still says five (its only commit to layers.md is 61241e1bc9). The
Russian title and body in i18n/docs.ru.json translate that vendored chapter
and stay with it. Until t27 changes layers.md, that chapter's prose names five
layers next to a six-row table.

Measured on local builds in headless Chrome: before, #/skills and #/crons drew
no ladder, #/functions captioned it "Specs → Skills → Crons → Agents →
Functions", the figure on #/docs/project had five steps under a five-layer
caption (ru: "Пятислойная лестница") and the table on #/docs/layers five rows.
After, all five Explorers draw the same six steps under the caption "ladder",
the figure has six steps ending "6. functions" under a six-step caption in en
and ru, and the table has a Functions row. check:docs, check:agents, both
generators' --check, their node --test suites and check:explorer-languages
pass.

qa/explorer-viewport-contract.mjs --no-build passes on both builds (6 sizes x 7
explorers) with the same 26 warnings: the Skill and Cron Explorers gain six
controls (the ladder links; 56 -> 62 at 1280x800) and no new warning, and at
390x844 both stay one phone pane with one scroller.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): the Queen's address names the card each embedded Explorer shows

Every Queen tab has its own address, and the tabs that frame an Explorer now
carry that Explorer's card in it too. Seven Queen tabs frame an Explorer, and
each kept its card only in the frame's own hash. Measured on t27.ai and on a
local build of 429499e: a pick in #/queen?tab=skills changed the frame, not
the address, so a reload opened the default skill;
?tab=specs&spec=specs/tri/tri_cli.t27 stayed on hello_world; a PROJECT chapter
jump was lost on reload; the language toggle reloaded the frame on its default;
and the ladder link Skills inside #/queen?tab=agents navigated the frame to the
Skill Explorer under a rail and an address that still said AGENTS.

The Queen address now names the card: skill=, cron=, agent=, function=, tool=,
spec= and chapter= (lib/queenEmbed SELECTION_KEY).
- Frame side, lib/queenFrame, which imports nothing so the entry chunk grows by
  1.1 kB: a page whose window the Queen named t27-queen-explorer posts each card
  it writes to its address (useHashParams.set, the Spec Explorer's pick) and
  hands a plain click on a link to an Explorer route to the Queen instead of
  following it. Frames the Queen did not name (the Spec Explorer in a skill
  card, the landing previews) post nothing and keep their links.
- Host side, components/useQueenExplorerFrame, used by QueenAgents and
  QueenSpecs: a message counts only from this origin and this frame. The frame
  boots on the address's card. A pick writes the address with replace, as tab=
  is written. An outside hash change, Back, or a frame load moves the frame with
  location.replace, which adds no history entry. A link to another Explorer
  switches the Queen tab, so the rail, the address, the frame title and the
  frame agree. The language toggle boots a new frame on the same card. Leaving
  a tab drops its card.
- Ids go through the Explorers' own *ExplorerHash helpers. A refused id is no
  card: the frame opens its default and the address takes that default. An id
  the helper accepts but the catalog lacks gets the Explorer's own not-found
  card, as on its own page, and the address keeps it -- no silent swap to a
  different card. The docs page has no such card and opens the first chapter;
  in a frame it writes the chapter it opened, so the address names it.
- An address that names no card of its tab (a link or Back to the bare tab)
  takes the frame's card, since the Explorers keep the open card on such a
  hash, so a reload opens what was on screen. A booting frame's report waits
  for its load event, and a report the frame has since been moved past is
  dropped as stale.
- The Spec Explorer, in a frame only, follows a later ?spec=; as with a list
  click, that discards an unsaved draft. On its own page it still reads its
  address once, as before (measured: a later ?spec= leaves the page on its spec
  on both builds).

Behaviour to know: a plain Queen tab visit writes the Explorer's default card
into the address once the frame has loaded (#/queen?tab=skills becomes
same inside its own frame. A hash without a card written inside the frame
changes nothing, since the frame still shows its card.

qa/queen-embed-address-contract.mjs (npm run check:queen-embed; --origin= for
a build served elsewhere) checks this in headless Chrome against the live DOM
of the Queen and the frame.
Results in headless Chrome at 1440x900, on a heavily loaded machine:
- build of 429499e, served locally: FAIL, 23 of 24. Every card check
  fails, each for its own reason (the default card under a deep link, no
  card in the address after a pick, skill= left behind after a tab switch,
  chapter=zz kept while chapter 1 shows); only "no uncaught exceptions"
  passes. The ?lang= check in D fails there too; that fix is an earlier
  commit.
- build of this commit: PASS, 24 of 24.
Also on this build: the live tabs probe (PROBE_SKIP_FRAMES=1) passes 47 of 47
(every tab still writes and follows its address by click, key, outside hash
and Back); standalone #/skills picks and ladder links behave as before; the
entry chunk is 309347 bytes against 308218 at 429499e. The typecheck
ratchet adds no errors; eslint reports only the react-refresh error that
main.tsx already had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): keep the shell's tab write in the shape the TRI contract pins

Rebasing onto #1011 merged two writers of the shell's tab: TRI builds the
address from the live hash (tabAddress(window.location.hash, next)) and this
branch drops or writes the embedded Explorer's card. The merge first read the
hash into a local, which qa/tri-screens-contract.mjs does not recognise as the
live-hash write it pins. Same behaviour, the literal form restored.

Refs #1013

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
github-actions Bot added a commit that referenced this pull request Sep 14, 2026
fix(website): Queen explorer cards in the address, skill labels, six-step ladder, lang toggle, phone core (#1014)

* chore(website): regenerate system-docs.json from the committed catalogs

public/docs/system-docs.json had drifted from the catalogs it is generated
from: check:docs failed at 429499e with "public/docs/system-docs.json is
stale or hand-edited" (the committed ladder said tools 91 while
public/tools/spec-tools.json holds 92). This is scripts/docs-from-specs.mjs as
committed, run on the committed catalogs, with the file's own generatedAt
(2026-09-12T12:30:15.363Z) passed through generate({ generatedAt }), so the
stamp keeps its milliseconds; the CLI's SOURCE_DATE_EPOCH takes whole seconds.

No new content, only what the catalogs already said: the mcp/inngest-dev row in
the tools table, a third tool for agent T in the ring and the bindings graph,
tools 91 -> 92 in the ladder and its figures, the external and per-repository
MCP lists, the tool counts in the witnesses table, and the sha256 of the source
files read at generation time. It goes first so the ladder change that follows
shows only its own rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): the Queen's language toggle writes ?lang= to the address

On /?lang=ru#/queen?tab=functions the HUD toggle switched the page to English
but left ?lang=ru in the address, so a reload came back in Russian. The
header's LanguageSwitcher already writes ?lang= with replaceState; the Queen's
toggle (the HUD tool button and the menu entry) now does the same, keeping
history.state, where HashRouter keeps its entry index.

Measured on local builds in headless Chrome (CDP, 1440x900):
before: after the toggle the search is still ?lang=ru with the document in en,
  and a reload is ru again (address agrees: false; reload keeps it: false).
after: the search is ?lang=en after the toggle, and a reload stays en.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): the Skill Explorer's link labels name the spec binding they count

#/skills counted "No spec 23", and a card said "No spec declared yet", for
skills whose own card shows witness spec+code and a spec at
specs/skills/<id>.t27 (t27/wave-audit, for one). The filter and the chip
measure whether a skill declares the spec it stands on -- a binding the build
checks -- not whether a card spec exists. The labels now say that; the data and
the counts are unchanged.

EN: No spec -> Unbound; No spec declared yet -> No spec binding declared yet;
Skills without a spec -> Skills that declare no spec binding;
"without a spec: {n}" -> "unbound: {n}".
RU: Со спекой -> С привязкой; Без спеки -> Без привязки;
Спека ещё не объявлена -> Привязка к спеке ещё не объявлена;
Скилы без спеки -> Скилы, не объявившие привязку к спеке;
"без спеки: {n}" -> "без привязки: {n}".

Measured on local builds in headless Chrome, #/skills?skill=t27%2Fwave-audit
in en and ru: before, the chip read "No spec" / "Без спеки" and the filters
"No spec 23" / "Без спеки 23"; after, "Unbound" / "Без привязки" with the same
counts (Bound 3, Unbound 23, Broken 0). check:explorer-languages passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): Shared core can be tapped on a phone

At 390x844 (touch emulation) on #/queen the worlds nav in the HUD head showed
only the world select and the connect button: QueenUniverse.css hid the last
nav button (Shared core) and the one before it (Game home) below 640px, and
inside the head the nav is display: contents, so the hidden buttons measured
0x0 and view=core had no way in on a phone. Shared core now stays. Inside the
shell's head the world select is capped at 150px, which frees the width the
button needs, so the head keeps its height. Game home and the T27 shortcut
stay hidden on a phone, as before.

The variant was chosen by injecting candidate CSS into the unmodified build
before any source change: unhiding the button alone pushed it onto a second
row (head 110px -> 142px, field 32px shorter); unhiding it and capping the
select kept the head at 110px.

Measured on local builds in headless Chrome at 390x844 with touch, #/queen and
#/queen?tab=crons: before, Shared core measured 0x0 and a tap could not reach
view=core; after, it is 93x26 and tappable, a tap opens view=core, the head is
110px tall and the document is still 390x844 (one screen), with every nav
control inside the viewport.

qa/queen-viewport-contract.mjs --no-build reports the same on both builds: the
same 30 size/view combinations fail with the same messages (COUNT commands=12,
STATUS SLOT MISSING: alerts, undeclared scrollers such as .queen27-hud-intel),
390x844 included, so those failures predate this change and it adds none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): one ladder of six steps wherever the site draws or names it

The layer ladder was stated several ways. On #/docs the figure said "The
five-layer ladder: Specs → Skills → Crons → Agents → Tools" and the
ladder-counts table had five rows; the PROJECT copy (queenModules.ts, the
Queen's PROJECT directive) and the docs meta description said "five-layer
ladder"; the Agent, Tool and Function Explorers drew six steps ending
Functions 28, with the Function Explorer's caption naming a ladder without
Tools; and the Skill and Cron Explorers drew no ladder at all.

Now one statement: Specs → Skills → Crons → Agents → Tools → Functions.
- scripts/agents-from-specs.mjs writes the ladder into spec-skills.json and
  spec-crons.json as well; the Skill and Cron Explorers draw it (LayerLadder,
  en/ru keys), and the Function Explorer's caption reads "ladder" like the rest.
- scripts/docs-from-specs.mjs counts functions in docs.ladder and adds a
  Functions row to the ladder-counts table; LadderFigure draws a sixth step and
  its caption says six steps, in en and ru.
- "six-step ladder" in the PROJECT copy (queenModules.ts, Queen.tsx) and in
  the docs meta description, in en and ru.
- The contracts follow: agents-spec-contract (the skills and crons catalogs
  carry the agents' ladder), docs-spec-contract and docs-from-specs.test
  (functions in the ladder and in the table).

The data is regenerated with the repository's generators, each file keeping
its generatedAt to the millisecond: spec-skills.json and spec-crons.json gain
only `ladder` (and a new contentSha256); spec-agents, spec-tools and
spec-functions come out byte-identical; system-docs.json gains the functions
count, the table row and the figure step.

Not changed here: the chapter's own title and prose, "The five-layer system"
and "Specs -> Skills -> Crons -> Agents -> Tools", are vendored from
gHashTag/t27 (public/t27/files/docs/system/layers.md and
specs/docs/chapters/layers.t27, pinned by sha256 in the docs contract), and
upstream still says five (its only commit to layers.md is 61241e1bc9). The
Russian title and body in i18n/docs.ru.json translate that vendored chapter
and stay with it. Until t27 changes layers.md, that chapter's prose names five
layers next to a six-row table.

Measured on local builds in headless Chrome: before, #/skills and #/crons drew
no ladder, #/functions captioned it "Specs → Skills → Crons → Agents →
Functions", the figure on #/docs/project had five steps under a five-layer
caption (ru: "Пятислойная лестница") and the table on #/docs/layers five rows.
After, all five Explorers draw the same six steps under the caption "ladder",
the figure has six steps ending "6. functions" under a six-step caption in en
and ru, and the table has a Functions row. check:docs, check:agents, both
generators' --check, their node --test suites and check:explorer-languages
pass.

qa/explorer-viewport-contract.mjs --no-build passes on both builds (6 sizes x 7
explorers) with the same 26 warnings: the Skill and Cron Explorers gain six
controls (the ladder links; 56 -> 62 at 1280x800) and no new warning, and at
390x844 both stay one phone pane with one scroller.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): the Queen's address names the card each embedded Explorer shows

Every Queen tab has its own address, and the tabs that frame an Explorer now
carry that Explorer's card in it too. Seven Queen tabs frame an Explorer, and
each kept its card only in the frame's own hash. Measured on t27.ai and on a
local build of 429499e: a pick in #/queen?tab=skills changed the frame, not
the address, so a reload opened the default skill;
?tab=specs&spec=specs/tri/tri_cli.t27 stayed on hello_world; a PROJECT chapter
jump was lost on reload; the language toggle reloaded the frame on its default;
and the ladder link Skills inside #/queen?tab=agents navigated the frame to the
Skill Explorer under a rail and an address that still said AGENTS.

The Queen address now names the card: skill=, cron=, agent=, function=, tool=,
spec= and chapter= (lib/queenEmbed SELECTION_KEY).
- Frame side, lib/queenFrame, which imports nothing so the entry chunk grows by
  1.1 kB: a page whose window the Queen named t27-queen-explorer posts each card
  it writes to its address (useHashParams.set, the Spec Explorer's pick) and
  hands a plain click on a link to an Explorer route to the Queen instead of
  following it. Frames the Queen did not name (the Spec Explorer in a skill
  card, the landing previews) post nothing and keep their links.
- Host side, components/useQueenExplorerFrame, used by QueenAgents and
  QueenSpecs: a message counts only from this origin and this frame. The frame
  boots on the address's card. A pick writes the address with replace, as tab=
  is written. An outside hash change, Back, or a frame load moves the frame with
  location.replace, which adds no history entry. A link to another Explorer
  switches the Queen tab, so the rail, the address, the frame title and the
  frame agree. The language toggle boots a new frame on the same card. Leaving
  a tab drops its card.
- Ids go through the Explorers' own *ExplorerHash helpers. A refused id is no
  card: the frame opens its default and the address takes that default. An id
  the helper accepts but the catalog lacks gets the Explorer's own not-found
  card, as on its own page, and the address keeps it -- no silent swap to a
  different card. The docs page has no such card and opens the first chapter;
  in a frame it writes the chapter it opened, so the address names it.
- An address that names no card of its tab (a link or Back to the bare tab)
  takes the frame's card, since the Explorers keep the open card on such a
  hash, so a reload opens what was on screen. A booting frame's report waits
  for its load event, and a report the frame has since been moved past is
  dropped as stale.
- The Spec Explorer, in a frame only, follows a later ?spec=; as with a list
  click, that discards an unsaved draft. On its own page it still reads its
  address once, as before (measured: a later ?spec= leaves the page on its spec
  on both builds).

Behaviour to know: a plain Queen tab visit writes the Explorer's default card
into the address once the frame has loaded (#/queen?tab=skills becomes
same inside its own frame. A hash without a card written inside the frame
changes nothing, since the frame still shows its card.

qa/queen-embed-address-contract.mjs (npm run check:queen-embed; --origin= for
a build served elsewhere) checks this in headless Chrome against the live DOM
of the Queen and the frame.
Results in headless Chrome at 1440x900, on a heavily loaded machine:
- build of 429499e, served locally: FAIL, 23 of 24. Every card check
  fails, each for its own reason (the default card under a deep link, no
  card in the address after a pick, skill= left behind after a tab switch,
  chapter=zz kept while chapter 1 shows); only "no uncaught exceptions"
  passes. The ?lang= check in D fails there too; that fix is an earlier
  commit.
- build of this commit: PASS, 24 of 24.
Also on this build: the live tabs probe (PROBE_SKIP_FRAMES=1) passes 47 of 47
(every tab still writes and follows its address by click, key, outside hash
and Back); standalone #/skills picks and ladder links behave as before; the
entry chunk is 309347 bytes against 308218 at 429499e. The typecheck
ratchet adds no errors; eslint reports only the react-refresh error that
main.tsx already had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): keep the shell's tab write in the shape the TRI contract pins

Rebasing onto #1011 merged two writers of the shell's tab: TRI builds the
address from the live hash (tabAddress(window.location.hash, next)) and this
branch drops or writes the embedded Explorer's card. The merge first read the
hash into a local, which qa/tri-screens-contract.mjs does not recognise as the
live-hash write it pins. Same behaviour, the literal form restored.

Refs #1013

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Queen: TRI tab with the app.t27.ai screens inside the game

1 participant