Record what the branch found, and one branch convention
The plan gains the two things executing it taught: link is cut from the toolbar with its reasoning (an href is a value in attribute position and a scheme allowlist, which is a decision rather than a row in a table), and the account of the evening the editor silently discarded every edit. AGENTS.md gains the same finding in the place it belongs — the section on tools that answer wrongly instead of failing. y-prosemirror's `undo` takes no dispatch and undoes immediately, so a toolbar honouring ProseMirror's own "null dispatch means ask, don't act" convention undid every change as it was made. The general form is worth more than the instance: a convention is not a guarantee, and the functions that break one are the ones that look most like they honour it. Two rules alongside it. A new test must be seen to fail — the browser drive was kept only after it was confirmed red on the build whose bug it had just found, because without that a green run means "passed" and "could not see" equally. And rebuilding the editor bundle is not hand-mergeable: it is one committed artifact named after its own hash, assets.go panics on finding two, so whoever merges second rebuilds on top of main rather than reconciling a minified file. That last one is the convention the three sessions working in this repo agreed on 2026-08-31; it is written here rather than in three branch discussions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YYB9fUAfxXNzkeC3fsFYzR
2 files changed,
+79
−3
AGENTS.md+31 −0docs/superpowers/plans/2026-08-31-formatting-and-export.md+48 −3
CI passed — run details
diff --git a/AGENTS.md b/AGENTS.md| index f4f295f..7277bc1 100644 |
| --- a/AGENTS.md |
| +++ b/AGENTS.md |
| @@ -246,6 +246,16 @@ work someone will do, `amadan task advance` when it is done. Commentary |
| belongs in the discussion, not in task titles. A branch with real work on |
| it should have tasks that a reader can scan to see what is left. |
| +**Rebuilding the editor bundle is not hand-mergeable** (branch |
| +convention, agreed 2026-08-31 between the three sessions then working in |
| +this repo). `internal/docs/static/editor.<hash>.js` is a single |
| +committed artifact whose name is the hash of its own contents, and |
| +`assets.go` panics at init if it finds more or fewer than one. Two |
| +branches that both ran `fixtures/build-editor.mjs` therefore conflict |
| +mechanically even when no source line overlaps. Whoever squash-merges |
| +second discards their own bundle from the merge and re-runs the build on |
| +top of `main`. Never reconcile the minified file by hand. |
| + |
| **When something is refused, say so in the discussion rather than working |
| around it.** An `agent` grant is writer-equivalent within one namespace: |
| push, post, manage tasks, nothing beyond. Refusals are usually a missing |
| @@ -311,9 +321,30 @@ time in a single afternoon: |
| because a squashed branch is not an ancestor. Compare content, not |
| ancestry. |
| +- **A library function that ignores its own "dry run" argument.** |
| + `y-prosemirror`'s exported `undo` is `state => ...undoManager?.undo()` |
| + — it takes no `dispatch` and undoes immediately. ProseMirror's command |
| + contract says a null `dispatch` performs nothing and only reports |
| + whether it could, which is how any toolbar decides whether to grey a |
| + button out — on every state update, because that is when the answer |
| + changes. So the formatting bar asked "could I undo?" after every |
| + change and the library answered by undoing it: typing did nothing, |
| + every command did nothing, no error anywhere, Go suite green. |
| + (`undoCommand`/`redoCommand` are the dispatch-aware pair.) The general |
| + form: **a convention is not a guarantee**, and the functions that break |
| + one are the ones that look most like they honour it. Found 2026-09-01 |
| + on the formatting-and-export branch, by a browser drive, after an |
| + evening of looking at the wrong file. |
| + |
| The shape to watch for is a verification that cannot distinguish "no" from |
| "I can't see it". When you catch one, write it here rather than resolving |
| to be more careful — care is not a mechanism. |
| + |
| +**A new test must be seen to fail.** Corollary of the above, and cheap: |
| +after writing one, break the thing it covers and watch it go red, then |
| +put it back. `scripts/browser-drive.sh` was kept only after it was |
| +confirmed to fail on the build whose bug it had just found — without |
| +that step a green run means "passed" and "could not see" equally. |
| - **Re-copy `tokens.css` deliberately on every rastrillo upgrade.** It is |
| frozen into `static/` at scaffold time while the partials upgrade with the |
| module. `rastrillo doctor` will make this mechanical; until it lands it is |
diff --git a/docs/superpowers/plans/2026-08-31-formatting-and-export.md b/docs/superpowers/plans/2026-08-31-formatting-and-export.md| index 4c07e76..0885d97 100644 |
| --- a/docs/superpowers/plans/2026-08-31-formatting-and-export.md |
| +++ b/docs/superpowers/plans/2026-08-31-formatting-and-export.md |
| @@ -152,9 +152,21 @@ the engine and build the engine's other consumers on top of it. |
| ### Task 5 — the formatting bar |
| `fixtures/toolbar.mjs`, a new module under the 300-line cap, wired into |
| -`editor-entry.js` in a few lines. Bold, italic, code, link; paragraph |
| -and headings 1–3; bullet and ordered lists; blockquote; undo and redo; |
| -a font-family control and a size control driving Task 2's marks. |
| +`editor-entry.js` in a few lines. Bold, italic, code; paragraph and |
| +headings 1–3; bullet and ordered lists; blockquote; undo and redo; a |
| +font-family control and a size control driving Task 2's marks. |
| + |
| +**Link is cut, on the same reasoning as D-marks-carry-no-value.** A |
| +`link` mark carries an `href`, and the read view renders server-side |
| +through `fragment.rs` — so supporting it means putting a value from the |
| +CRDT into attribute position, which is the one property Task 2 exists |
| +to preserve, plus a URL-scheme allowlist (`javascript:` is a URL). |
| +Neither is hard; both are decisions rather than rows in a table, and |
| +they are not decisions to make at the end of a branch. Note that |
| +`prosemirror-schema-basic` already *defines* the link mark, so a paste |
| +can still produce one today and `fragment.rs` already renders it as |
| +plain text — that asymmetry predates this branch and is unchanged by |
| +it. |
| Chrome, so it lives outside `[docs-editor]` — ProseMirror's |
| `{mount: el}` takes over everything inside that element. Buttons |
| @@ -193,6 +205,39 @@ golden files, a PDF that is parsed back and asserted structurally |
| the geometry agreement test, and a toolbar test under `node --test` for |
| whatever logic can be tested without a DOM. |
| +## What executing this actually found |
| + |
| +**The editor stopped working entirely, and nothing caught it.** Between |
| +Task 5 being written and Task 5 being finished, typing did nothing and |
| +every toolbar command did nothing — no error in the page, no error in |
| +the server log, a green Go suite, and a screenshot that looked right, |
| +because the bar renders correctly and the document looks untouched when |
| +it *is* untouched. |
| + |
| +The cause: `y-prosemirror`'s exported `undo` ignores its `dispatch` |
| +argument and undoes immediately. A ProseMirror command's contract is |
| +that a null dispatch performs nothing and only reports whether it |
| +could, and that is exactly how a toolbar decides whether to grey a |
| +button out — on every state update, because that is when the answer |
| +changes. So the bar asked "could I undo?" after every change and the |
| +library answered by undoing it. `undoCommand`/`redoCommand` are the |
| +library's own dispatch-aware pair. |
| + |
| +This is the shape AGENTS.md already warns about — a tool that answers |
| +confidently rather than failing — so it is recorded there too. |
| + |
| +**The gate that found it did not exist, so it does now.** |
| +`scripts/browser-drive.sh` + `fixtures/browser-drive.mjs` drive the real |
| +editor in a real headless Chrome over the DevTools protocol, with real |
| +input events (a synthetic DOM Range is not the selection ProseMirror |
| +reads, and an `element.click()` skips the `mousedown` the toolbar |
| +depends on). Twelve checks; it was verified to FAIL on the broken build |
| +before being kept, which is the only way to know a new test can tell |
| +"no" from "I can't see it". It is not in `.amadan/ci.d/10-gate`: it needs |
| +a Chrome on the runner, and that is a decision for whoever owns the |
| +runner rather than a side effect of this branch. Spec §12 asks for this |
| +gate; this is the first half of it. |
| + |
| ## Risks |
| - **The bundle is a single committed artifact and only one may exist** |