oficina / docs Public

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
Paul Campbell pushed by paul@keymail.dev b8c299e41b4ad494920deb36a48284e981ec8946 parent 7bc0e86
2 files changed, +79 −3
  • AGENTS.md +31 −0
  • docs/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**