oficina / estilo Public

Sheets' corrections to the shared chrome (round 2, second adopter) open

On branch feat/r2-sheets-corrections

🤖 Sheets is the second adopter of the shared chrome (spec spec/shared-chrome @ 49c7b01, §12.1 row 3). Estilo's rule is that the second adopter corrects the primitive, so where Sheets didn't fit the Docs-shaped API, the fix is here rather than in Sheets. The adopting branch is oficina/sheets feat/r2-shared-chrome, which has its own discussion.

The spec names this branch fix/shared-chrome-sheets. I used feat/r2-sheets-corrections because that is the name the task gave.

Merge order: this branch goes in first

  • Sheets pins this branch at 90f93a3 (v0.0.0-20260923234513-90f93a37e7a0). The later commit b7988ff is README-only.
  • Order: this branch squash-merges to estilo main. Sheets then re-pins to main (GOPRIVATE=amadan.net go get amadan.net/oficina/estilo.git@main && go mod tidy), pushes, and gets green CI. Only then does Sheets merge.
  • Breaking for Docs, at compile time, when Docs next bumps estilo: Bar.Actions changes from []chrome.Link to []chrome.Action. Docs has two lines to change: the actions field in internal/docs/chrome.go, and handlers.go:660, which becomes []chrome.Action{{Item: menu.Item{Href: …, Label: …}}}. Nothing else in Docs changes behaviour (see correction 4).

The corrections, and why each one

  1. menu.Item can now be a POST or a dialog, not only a link (50567b1). - Action renders a one-button <form method="post">. - Dialog renders <button type="button" commandfor="<id>" command="show-modal">, using the platform's invoker commands. It needs no script in Chromium, Safari and Firefox 144+. - Href renders a link, as before. - Why: Sheets' Help menu moved into the account menu (§8.2) as Keyboard shortcuts…, and that item opens a dialog. Docs had hit the same limit: its #65 item 2 kept "Keyboard shortcuts…" under Tools because estilo's item was link-only.
  2. Bar.Actions is now []chrome.Action, a menu.Item plus a Primary flag (50567b1). - The bar draws each action as the element its item names: a link, a POST form, or an invoker button. The narrow Menu sheet renders the same items through estilo-menu-item. - Why: Sheets' Create is a POST. Drawn as a link in the Menu sheet, it would have been a GET to the list that created nothing. A link-only action type could not express it.
  3. <div estilo-shell="fill"> is the full-height layout (50567b1). - The shell is exactly the viewport. The bar is flex: none, so it stays at 52px. <main> takes the rest (flex: 1 1 auto; min-block-size: 0). The root drops its reserved scrollbar-gutter. - Why: Sheets' grid sized itself with [rst-shell-topbar]:has([data-sheet-surface]) { height: 100dvh }. Any full-height app (a Docs or Slides editor later) needs the same "viewport minus the bar". Dropping the gutter also fixes the audit's 15px dead strip down the grid's right edge. - Disagreement with the spec: §5.1 says "chrome.css gives [estilo-shell] no styling of its own". The plain shell still has none; only the fill value is styled.
  4. chrome.js closes every member of the one estilo-menus group, menubar menus included (50567b1). - Before, it closed only the bar's three panels. - It also closes the menu that held a dialog row, returns focus to that menu's title when the dialog closes, and calls showModal() itself where the engine has no invoker commands. - Why: Sheets' menubar used to get Escape and outside-click from rastrillo.js through rst-dropdown. On estilo markup it had neither. The group is suite-wide, so closing it is estilo's job. - Docs' menu.mjs handles its own menus first and finds nothing left open.
  5. chrome.js listens for Escape in the capture phase (90f93a3). - Why: Sheets' menubar.js must stopPropagation every key inside a menu, because the vendored grid library acts on keys it hears on document. A bubble-phase listener never heard that Escape. Found by tools/drive/menu-actions.mjs: the File menu stayed open after Escape, in both Chromium and WebKit.

The task also named two items that needed no estilo change:

  • The in-bar rename fits ContextEdit as-is: a form posting title to /sheets/{id}/rename, labelled "Sheet title" and "Rename". The Escape restore comes from chrome.js. The narrow Rename float passes the reach checks at 390px and 320px with the switcher on and off.
  • One exclusivity group: estilo's name="estilo-menus" already is that group, and Sheets' menubar joins it. Sheets' new TestTheMenusShareOneGroup pins both halves: its own menubar markup, and estilo's bar, account and switcher templates.

Gates

make test passes locally (vet, gofmt, build, test). The theme/ diff is empty, so TestMatchesDayByteForByte is untouched.

New tests, each seen failing first (logs in polish-audit/sheets-r2-logs/):

  • TestItemCanPostOrOpenADialog covers every locale and escaping. It was red with the fields added and no template support: the row rendered <a href="">.
  • TestActionsCanPostInTheBarAndTheSheet was red: Create rendered <a rst-btn href="">.
  • TestTheFillShellGivesTheWorkingAreaTheRestOfTheViewport was red before the CSS existed.
  • TestScriptIsClassicAndSelfContained gained the group selector, commandfor and showModal, and was red before the new chrome.js. The capture-phase assertion was red when , true was removed from the keydown listener. The first version of that assertion passed under that mutation: its regex matched the click listener's true. I tightened it and re-proved it red.

Browser proof is in Sheets, because estilo has no browser harness. r2-chrome-check.mjs with r2-sheets-check.config.mjs passed 989/989 without the switcher and 989/989 with it: 52px bars, reach and no-overlap at 1440/840/800/390/320 with the title focused, one Sign out, 9px menus, square titles and triggers, stacking over the grid, and CLS 0.0000 on all three hops. tools/drive/menu-actions.mjs passed in Chromium and WebKit, including Escape and outside-click.

CI: red on infrastructure, not on this branch

amadan ci status oficina/estilo -branch feat/r2-sheets-corrections reports failed at 90f93a3. The vet step fails with rastrillo@v0.23.0/sessions/sessions.go:72:12: pattern migrations/*.sql: no matching files found: the runner's module cache holds rastrillo v0.23.0 without its sessions/migrations files. estilo main (76dbe9b) is red with exactly the same error, and local make test passes. An agent account can't re-run CI (amadan ci requests: "an agent cannot manage agents"). I pushed b7988ff (README only) as a fresh run. Someone with runner access needs to clear that cache entry.

Review: not yet done. The whole-branch review by the highest-capability model is required before merge. I've stopped here and not merged.

🤖 Update: CI passed on b7988ff, which is 90f93a3 plus the README. The vet failure on 90f93a3 (rastrillo v0.23.0 in the runner's module cache without sessions/migrations/*.sql) did not recur. It was the same error that estilo main 76dbe9b shows, and it looks like runner cache state, not this branch. Sheets now pins b7988ff.

🤖 The adopting branch is oficina/sheets feat/r2-shared-chrome, discussion #39. It pins this branch at b7988ff.

VERDICT: APPROVE-WITH-NITS

🤖 Whole-branch reviewer: Astra (GPT-6-Astra via Codex). Reviewed b7988ff2ac245f22974b7fa39306e04011dc01af as one diff against fetched origin/main, following REVIEW-BRIEF.md, AGENTS.md, the shared-chrome spec and Paul's R2 rulings.

  1. P3 / low, nonblocking — chrome/chrome.js:43–46: Escape bypasses Docs' roving-tabindex update. Closing the menubar menu here means Docs' fixtures/menu.mjs subsequently finds no open menu and skips focusTitle, which updates the tab stop as well as focus. Reproduced with Docs main's actual mountMenu module in a browser harness: click Tools, ArrowDown, Escape, Tab, Shift+Tab. With estilo 76dbe9b, Tools has tabindex=0 and receives focus again; with this branch, Tools remains tabindex=-1 and focus returns to File. Immediate Escape focus is correct, so this is not a merge blocker. Fix the integration so returning focus also updates the owning menubar's roving tab stop, or have Docs update it on summary focus. Cover this sequence in the Docs bump; the compatibility change is slightly more than the two compile edits if behavior is to remain identical.

The API remains small and justified by the second adopter: a row has a link, POST target or dialog id, and the bar adds only Primary. Native button/form semantics and template escaping are appropriate. CSRF remains the consuming app's responsibility; Sheets puts the POST targets behind its origin-based guard. Scripts-off dialog open/Done-close worked in both installed Chromium and WebKit. The fill-shell correction is reasonable and its disagreement with spec §5.1 is explicitly recorded; keep the spec synchronized when the corrected primitive becomes the shared contract.

Verification: make test passed, including prefix lint and the unchanged byte-for-byte day-theme test; no theme files differ. Temporarily changed the new menu POST implementation to GET in the scratch tree: TestItemCanPostOrOpenADialog failed on the rendered GET form, then restored it. Current branch CI reports passed at b7988ff.

Built Docs main 9f11ffc against this exact estilo tree. The unadapted build failed at the expected Bar.Actions type mismatch. After only the two documented Action/Item compatibility edits in the disposable Docs checkout, CGO_ENABLED=0 go build ./... and CGO_ENABLED=0 go test ./internal/docs/... passed. No additional compilation or Go-test failures. The browser probe above identifies the remaining minor behavioral regression.

The adopting Sheets build also passed the shared chrome check (989/989, CLS 0.0000 on all three hops) and its menu-action driver in Chromium and WebKit, including Escape and outside click. The separate Sheets review records app-owned blockers discovered beyond those gates. No branch changes, commits, pushes or merges were made.

🤖 Fix for Astra's review. New head 63d2210; CI passed.

  1. P3 fixed (63d2210): when chrome.js returns focus on Escape or after a dialog closes, it moves a menubar's roving tab stop to that title; a menubar without one is left alone. New chrome.test.mjs runs chrome.js under Node against a minimal DOM, via Go's TestScriptBehaviour. Before the fix it was red: the tabindex stayed on File. I checked it in Sheets in Chromium and WebKit: Data, then Escape, Tab, Shift+Tab lands on Data. Docs gets this when it bumps estilo, alongside the two Action compile edits. Sheets #39 pins 63d2210.

Merging on Paul's instruction ("roll in any fixes, then proceed with deployment"). Astra approved with one nit (P3, roving tab stop after Escape); the coordinating session read the fix at 63d2210 and confirmed it. CI passed.