Repository navigation
feat(website): TRI tab, the app at app.t27.ai inside the Queen - #1011
Conversation
…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>
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>
|
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, 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.
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 ( Not verified: Safari, Firefox, Telegram WebViews; identity inside TRI (the frame is a guest by design until single sign-on ships). |
…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>
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>
Description
A thirteenth Queen tab, TRI (key
r): the app at app.t27.ai inside thegame. 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, pluspath=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 andthe 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-pathallowlist, the message filter and the address builder. Frame URLs are always
APP_ORIGINplus a listed route. Only plain-object messages from app.t27.aiand 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, anda strip when the app has not answered 8 s after load (a frame refused by
frame-ancestorsstill fires load). One frame element for the life of theview, moved with
contentWindow.location.replace, so a screen click adds nohistory 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 andTRI'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 TRIframes nothing and says so.
ids); the CRM is addressed at its list.
qa/tri-screens-contract.mjs(pure, in website-checks) andqa/queen-tri-contract.mjs(built site in Chrome, 14 checks). The viewportgate derives its command count from
HUD_VIEWS, and agents-spec-contractknows the new module.
🔥 TOXIC VERDICT
What Works
queen-tri-contract14 of 14 on the build. Checks 5, 12 and 14 fail on theprevious build with the real defects (a CRM id in the hash, Back stuck twice,
an embedded Queen framing the app).
tri-screens-contractkills 12 of 12 independent mutants oftriScreens.ts,including the three a reviewer found surviving.
What Doesn't Work / Limits
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.
pushed in the frame before leaving TRI for another tab stay dead Back steps.
frame; out-of-process iframe behaviour is not measured.
check:toolsandcheck:docsfail identically on the base commit.Overall Assessment: APPROVE after the player is live. Self-Score: 7/10.
🌳 TECH TREE Options
{ "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