oficina / docs Public

Review fixes: a guard that runs, an assertion that can fail, a cue an icon can show

Three findings from branch review. None changes behaviour.

1. alignmentsAreCovered ran inside createToolbar, after the `if (!root)`
   early-out — browser mount time only, and nothing in the gate imported
   the module, so in CI it was dead code while its own comment claimed
   "it runs at module load". A drifted command table would have passed
   the whole gate and thrown at startup for every user.

   The alignment commands are now a module-level table, the guard runs
   as a top-level statement, and align.test.mjs imports toolbar.mjs so
   Node executes it. The comment says what happens, including that a
   module-load throw beats editor-entry.js's try/catch to it and so
   loses the data-editor-failed marker.

   It also checks each command's NAME against the VALUE it sets, which
   closes the gap the reviewer flagged as comment-only: `align_center:
   setAlign('centre')` has the right key, sits under the right button,
   and satisfied both the old guard and TestToolbarOptionValuesMatch-
   TheBundle — which passes with that typo present, verified.

2. `assert.equal(state.doc.eq(doc), true)` could not fail: same object
   reference, and a node is equal to itself even after its attrs are
   mutated in place. Proved rather than argued — a probe with an
   in-place mutation printed `eq -> true` for a document whose first
   paragraph had just become centred. Now a toJSON snapshot taken
   before the call, deepEqual after, which catches that mutation.

   Seventh in this codebase. The shape is a value compared to itself or
   to something derived from itself.

3. `font-weight: 700` does nothing to an SVG icon, and pressed shared
   :hover's exact `--rst-accent-soft` background — so six icon buttons
   signalled aria-pressed by icon hue alone, under two comments
   asserting weight covered it. Both comments corrected, and one
   box-shadow inset bar on [docs-tool][aria-pressed="true"] repairs all
   six at once.

   TestThePressedToolbarStateDoesNotRelyOnColour compares the pressed
   and hover rules and requires a distinguishing property an icon can
   actually show. Its first draft PASSED against the shipped defect,
   because it excluded only colour and so counted the useless
   font-weight — nearly an eighth unfailable test. font-weight is now
   in the exclusion set, and the test was watched failing with the cue
   removed before the cue went back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev d562753601fe26bbf0a32d2472382ee0e53804ab parent 56b99c1
5 files changed, +247 −58
  • fixtures/align.test.mjs +36 −1
  • fixtures/toolbar.mjs +82 −39
  • internal/docs/export_test.go +93 −0
  • internal/docs/static/{editor.b48adc3ff1090476.js => editor.98a16ab4d0c731c1.js} +13 −13
  • internal/docs/views/show.html +23 −5

CI passed — run details

Intent

Human

<task-notification> <task-id>a31c459b9aa9162e4</task-id> <tool-use-id>toolu_01MjpX8Jtc7GTZdPJVWrrGEB</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-docs/4a30c557-129a-4e57-8aa3-0c7518c36cf1/tasks/a31c459b9aa9162e4.output</output-file> <status>completed</status> <summary>Agent "Branch 2: paragraph alignment" finished</summary> <note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note> <result>**Status:** complete. Gate exit 0. Working tree clean. Report at `.superpowers/align-report.md`. **Commits** (on `worktree-align`, base `8777e55`): - `b16381e` Core: a paragraph's alignment, through a closed allowlist - `9ae7500` Export: centre and right in the PDF, dropped in Markdown - `56b99c1` Editor: three alignment controls, and the vocabulary they share **Tests:** 24 Rust (4 new), 12 new Node, 4 new Go, all watched failing first — Rust/Go new-behaviour tests against no feature; the three guard tests (byte-identity, hostile value, unknown-value drop) against deliberately naive implementations, one of which rendered `&lt;p class="docs-align-left" onmouseover="alert(1)" data-x="&lt;script&gt;"&gt;` and another of which centred `"CENTER"` in the PDF while the read view dropped it; the 12 JS tests against three naive variants until every one had been seen to fail. **PDF:** offsets each line by `(contentWidthPt - ctx.indent) - lineWidth`, the identical expression `paragraph` wraps to — margin-relative, so a quote's centred line centres in the indented column. Tests derive the line's width from where its runs actually landed rather than restating the formula. **Markdown:** dropped, with the reasoning in the package comment; the test asserts aligned output *equals* unaligned rather than equalling a literal. **Concerns:** (1) the three toolbar labels have not been through `copy-review` — not runnable non-interactively; "Align centre" vs the `center` attribute value wants a second opinion. (2) `aria-pressed` on icon-only buttons leans on background+colour; the `font-weight` half of §10.3's answer does nothing for an SVG — pre-existing for the list/quote buttons, now in three more places. (3) `TestToolbarOptionValuesMatchTheBundle` checks command names by substring, so a generated name would make it pass by seeing nothing; I wrote the three out literally and added a load-time guard, but the test itself is still that shape. (4) No end-to-end browser run: each seam is pinned separately, the round trip through the save queue is not.</result> <usage><subagent_tokens>222258</subagent_tokens><tool_uses>151</tool_uses><duration_ms>1726443</duration_ms></usage> </task-notification>

Human

<task-notification> <task-id>a91706feb4d6581ec</task-id> <tool-use-id>toolu_01LYbww5H43Vr4TkP5Cv55ko</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-docs/4a30c557-129a-4e57-8aa3-0c7518c36cf1/tasks/a91706feb4d6581ec.output</output-file> <status>completed</status> <summary>Agent "Fable review of alignment branch" finished</summary> <note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note> <result>The worktree is clean, all suites pass, and both adversarial mutations behaved exactly as the implementer claimed. The review is complete. --- # Whole-branch review: `worktree-align` (3 commits on 8777e55) ## Verdict: **Approve with changes** — the changes are small and none is functional. The security design is sound and genuinely tested; what needs fixing is two comments that claim things the code does not deliver, one CI-dead guard, and one decorative assertion. ## The property under attack: no document byte reaches the rendered page **Holds, on every path — verified by reading and by mutation.** - **HTML** (`core/src/fragment.rs:162-167`): the attribute value is only ever a key into `ALIGNMENTS` (line ~105); what is interpolated is the table's right-hand side, a `&amp;'static str` literal. I mutated the paragraph arm to naive interpolation (`format!("&lt;p class=\"docs-align-{v}\"&gt;")` from `get_attribute`) and ran `cargo test --test fragment`: **2 failed**, with the hostile value in attribute position verbatim — `&lt;p class="docs-align-left" onmouseover="alert(1)" data-x="&lt;script&gt;"&gt;` — precisely the output the implementer reported. The test exercises the real renderer (`fragment_html` on a real yrs `Doc`), and its `contains` sweep (`onmouseover`, `alert(1)`, `&lt;script`) also catches the escape-and-emit near-miss, since escaping preserves those substrings. Reverted; tree clean. - **JSON** (`fragment.rs:446-457`): `alignment()` returns the table's own key, another `'static` literal; unknown values emit no `attrs` at all, and the test asserts `!json.contains("attrs")` on the hostile doc. - **PDF/Markdown**: consume only `fragment_json`, so nothing unknown ever arrives; `alignOf` (`internal/export/document.go`) is an exact `switch` used only as a switch key, never drawn. Markdown never reads `align`. - **Editor**: `toDOM` emits a class only from the closed table; `alignClass` uses `hasOwnProperty` so `align: "constructor"` cannot resolve through `Object.prototype` (tested); `parseDOM` maps class names back through the same table. The button `title`/`aria-label` values are Go-template-escaped catalog strings, not document bytes. ## Backward compatibility — pinned, not asserted `a_document_with_no_alignment_renders_exactly_as_it_did_before` (`core/tests/fragment.rs`) asserts byte-exact HTML and JSON over the **pre-existing** fixtures `simple.bin` and `styled.bin`; the naive build additionally broke six pre-existing byte-exact tests, so identity is pinned from two directions. On the Go side an absent attribute decodes to `Align == ""`, which is the untouched left-aligned code path. Solid. ## Forward compatibility — dropped, and nothing worse `justify` is dropped by all four allowlists (Rust HTML test, Rust JSON test, JS `alignClass`/`currentAlign` tests, `TestPDFDropsAnAlignmentItDoesNotKnow`, including case-folded `"CENTER"`). One thing the report doesn't state that I confirmed by reading the schema path: the editor **preserves** an unknown value rather than destroying it — `align` is a declared attr with default `null`, so y-prosemirror round-trips `"justify"` unchanged on unrelated edits; it is only cleared when a person deliberately picks an alignment. That is the right behaviour for a document shared with a future build. ## The PDF — verified correct, including the quote case `internal/export/pdf.go:607-623`: `x := MarginPt + ctx.indent`, `free := (contentWidthPt - ctx.indent) - lineWidth`. Right edge lands at `PageWidthPt - MarginPt` exactly; the column is character-for-character the box `paragraph` wraps to (`maxWidth := contentWidthPt - ctx.indent`, line 496). I mutated `free` to drop `- ctx.indent` (centring against the full content width): `TestPDFAlignsInsideAQuoteAgainstTheIndentedColumn` **failed** ("191.168 of air on its left and 179.168 on its right… right-aligned line ends at x=552, want 540") while the top-level test stayed green — so the quote test carries its own weight and is not redundant with the page-level one. Reverted. Three details checked beyond the report: `flush()` (line 500) trims trailing spaces and never opens a line with one, so the offset measures exactly the visible text; the `free &gt; 0` guard keeps an unbreakable-word overflow out of the left margin; and the oversized-word chunks drawn directly via `drawLine` get `free ≈ 0` or negative, i.e. left. Consistent with the previous branch's `MarginPt + ctx.indent` convention throughout. ## Findings **1. `alignmentsAreCovered` never runs in CI, and its comment misstates when it runs — `fixtures/toolbar.mjs` (guard below `buildCommands`).** The comment says "It runs at module load and throws." It does not: it runs inside `createToolbar`, after the `if (!root) return` early-out — browser mount time only. No Node test imports `toolbar.mjs` (`align.test.mjs` only mentions it in comments), so the guard is dead code in the gate. Failure scenario: a future mismatch between the command table and `ALIGNMENTS` that the other two tests miss passes the entire gate and then fails at editor startup for every user — degrading (via editor-entry's outer `.catch`, which I confirmed exists) to the read-only view, in production instead of in CI. Smallest fix: run the check at module load over a module-level literal name list, and add a bare `import './toolbar.mjs'` (or a stub-root `createToolbar` call) to `align.test.mjs` so Node executes it; correct the comment either way. Assessment of the substring problem itself: the guard does close the loop-generated-name hole the implementer describes, but note what neither it nor `TestToolbarOptionValuesMatchTheBundle` can catch: a binding typo such as `align_center: setAlign('centre')` — right key, wrong value — passes the substring check, the key check, and all twelve JS tests. Only a test executing the toolbar's own command table would see it. Worth recording, not blocking. **2. The seventh assertion that cannot fail — `fixtures/align.test.mjs`, "with no dispatch the command reports and changes nothing".** `assert.equal(state.doc.eq(doc), true)` is tautological: `doc` and `state.doc` are the same object reference (`EditorState.create({doc})`), transactions never mutate their source state, and even a hypothetical in-place `node.attrs` mutation leaves `eq(self)` true. The test as a whole is not vacuous — its dispatch-count half is real and was watched failing (variant 3, "actual: 2 expected: 1") — but the named property "changes nothing" is guarded by a dead assertion. Smallest fix: snapshot `doc.toJSON()` before the call and `deepEqual` after, which also catches in-place mutation. **3. The §10.3 comments claim compliance the icon buttons don't get — `internal/docs/views/show.html` ~480 (CSS) and ~686 (template comment).** Both say the pressed state "changes weight as well as colour"; `font-weight: 700` does nothing to an SVG icon, so the three new buttons (and the existing list/quote trio) signal `aria-pressed` by background + icon hue only. Worse than the report states: hover uses the *same* background (`--rst-accent-soft`) as pressed, so a hovered unpressed button differs from the pressed one by icon hue alone — the cleanest 1.4.1 "colour alone" case in the group. Under WCAG 2.2 AA strictly this is contested territory (state-by-fill is often accepted at ≥3:1 contrast between states; `aria-pressed` conveys it programmatically), but the repo's own bar is "no exemptions", and the comments assert the mechanism satisfies §10.3 when it doesn't. Not blocking — it extends shipped precedent — but fix the comments now and add one non-colour cue on `[docs-tool][aria-pressed="true"]` (inset ring or underline bar), which repairs all six buttons in one rule, as the report itself suggests. **4. Process: the three catalog strings bypassed the mandatory `copy-review` gate** (`internal/docs/render.go` — "Align left"/"Align centre"/"Align right"). The implementer flagged it honestly; it needs Paul's pass before or at merge, including the "centre" (label) vs `center` (attribute) spelling split. ## Worth recording, no action - The end-to-end browser seam (click → CRDT → save queue → server render) is untested, as the report says; each side is individually pinned (`aligned.bin`, the JS tests, the Rust tests, `TestEveryClassTheRendererEmitsIsStyled` through the real wasm store). - `TestEveryClassTheRendererEmitsIsStyled` guards its own vacuity (fatals if fewer than 2 classes scrape out, and explicitly requires both align classes) — good pattern. - Runs on this machine: `cargo test --test fragment` 24/24, `go test ./internal/export ./internal/docs` ok (132s), `node --test align.test.mjs` 12/12. ## Summary The design does exactly what it exists to do: the first document *value* in the pipeline never reaches the output on any of the five paths, the byte-identity and forward-drop guarantees are pinned by tests I watched fail under mutation, and the PDF arithmetic is column-correct including inside indents. The four changes above are comment truthfulness, one CI wiring line, one assertion, and a copy gate — none touches behaviour. Sound work; merge once those are addressed.</result> <usage><subagent_tokens>113454</subagent_tokens><tool_uses>39</tool_uses><duration_ms>616718</duration_ms></usage> </task-notification>

Attributed to this commit: 20.9 g CO₂e (2767427 tokens). This is an attribution, not a measurement — usage counted from this commit's own marker forward to the next one on worktree-align, never backward from it. A turn that produced more than one commit books all of its tokens to the last one, and a concurrent session on this branch can add to the same figure. Some of it was priced using the shared default rate, because no model-specific factor row exists yet for at least one model involved. The Carbon view has the repo total, which is never derived from this attribution.

fixtures/align.test.mjs

Loading diff…

fixtures/toolbar.mjs

Loading diff…

internal/docs/export_test.go

Loading diff…

internal/docs/static/{editor.b48adc3ff1090476.js => editor.98a16ab4d0c731c1.js}

Loading diff…

internal/docs/views/show.html

Loading diff…