All seven of Paul's named items are built, pushed and gate-green (10 commits, 38 files, +5199/-174). Fable's whole-branch review is running now; nothing merges before it comes back.
Built
- The menu bar — File / Edit / View / Insert / Format / Tools, replacing the two export links pinned to the formatting bar. Each menu is a
<details>in aname=exclusive group, so it opens with no JavaScript; that is load-bearing rather than tidy, because the export links worked scriptless before they moved and a JS-drivenrole="menubar"would have put the only route to a person's own document behind a control that never opens for them. It stays an honest disclosure rather than claiming menu semantics it does not fully implement. No submenus anywhere — a nested<details>in aname=group collapses its parent. - Tab — nests a list item, steps a paragraph's indent otherwise. Indent is the first NUMERIC block attribute, so it is parsed and clamped the way
heading.levelalready is rather than looked up in a table likealign— the existing route to the same property, not a third one. - Bullet detection —
-,*,+,1.,>,#..###, with Backspace immediately after reverting the rule and giving back the characters typed. That undo is the whole feature; without it every accidental-is a list you have to fight. Also bound Enter in a list, which was never bound at all:baseKeymap's splitBlock put a bare paragraph inside the item and there was no way to end a list by pressing Enter twice. - Font size — a closed ladder of eleven point-size marks replacing the four named steps, absolute pt rather than multipliers. Eleven attribute-free marks spend no new safety property; one mark carrying "14pt" would spend the one
fragment_html's unescaped output rests on. The four old marks stay in the vocabulary permanently, mapped to their nearest step. - Selected states — Paul's screenshot. A one-sided rule inside a rounded box follows the curve instead of stopping at the corner, which is why the pressed button read as a tab stem. Now a full inset ring on the same radius, still
box-shadowso it costs no layout and does not spend 18px of the 320px budget. - Searchable selects — needed no rastrillo upgrade;
ui/select.jsis byte-identical between v0.22.0 and v0.25.0 and was already served. - Pages — infinite by default, paged on request, as widget decorations over measured block heights with nothing persisted and nothing shared. A persisted page break would be an edit two people could fight over.
Plus the agreed gaps: print, word count (Intl.Segmenter, because whitespace-splitting reports 1 for a whole Japanese sentence and this app declares twelve locales), and a keyboard shortcuts sheet read out of the menu bar's own DOM so it cannot drift.
Not built, deliberately — link insertion (Plan 7's valued mark and scheme allowlist; the one item where half-done is a security bug rather than a missing feature), find-and-replace, PDF page numbers, tab stops and the ruler, document tabs. Each is specced; none is started.
Four things worth reading the diff for, all of them mistakes caught rather than avoided
- I ran the gate as
bash 10-gate | tail -40and read the pipeline's exit code —tail's, always 0 — while acargo fmt --checkfailure sat in the output I had just read. Same shape as the two entries already under AGENTS.md's "tools that answer wrongly instead of failing", authored by me rather than found in a tool. TestToolbarOptionValuesMatchTheBundledid not fail when the menu bar landed, and could not have: it greps the template SOURCE, where the menu's names are{{.Command}}expressions matching no name pattern. It silently skipped an entire second surface while reporting success. Its comment now says what it does and does not cover; the menu's half is checked from the Go model.- A test I wrote asserted the framework's catalog keys resolve, went red, and looked like it had found
opts.BaseCatalogreplacing rastrillo's base catalog — which would have left the shell's skip link rendering a raw key. It could not have distinguished that from the dull truth that no key resolves in any test, because the locale middleware lives inServe.serve.gomerges rather than replaces; checked in the framework's source, not inferred from the test going green. IndentStepPtwas an untyped INT constant, soIndentStepPt / 72integer-divided to zero and the geometry test compared the stylesheet's real 0.5in against nothing.
Not browser-verified, and this is the one thing I would not merge quietly. Chromium will not start in this sandbox (scripts/browser-drive.sh: "socket() failed: Operation not permitted"), so the menu bar, the page gaps, the comboboxes and 320px have all been reasoned about and none has been looked at. I re-derived browser-drive.mjs's tab-order bound from 5 to 4 by reading DOM order rather than measuring — it goes DOWN because the menu bar adds one stop while the two export links leave the order entirely inside a closed <details>. The first real run checks that derivation as much as it checks the page. Paul should run scripts/browser-drive.sh before trusting any of it.
Coordination held all night: fixtures/browser-drive.mjs was the triage session's and I took it only after they released it; links stayed the paste session's until they confirmed they were not implementing Plan 7 tonight; the Sheets session has the <details> decision and the token names so the suite has one dialect.