oficina / docs Public

Plan 7 drives a browser before it merges

The header fix this commit used to carry is gone: the word-processor
session landed the same correction on main as 1859534, and did it
better. Theirs names both symptoms and says explicitly that neither is
the diagnosis, which generalises the point mine made about the quoted
error being stale, and it narrows the open CI question to "may a gate
step run unsandboxed" rather than "can this run here". Taking theirs
whole rather than reconciling two paragraphs we agree about.

What is left is the consequence for this plan, which is not a comment
but a requirement.

Plan 7's UI is the exact shape a green gate cannot see: a disabled menu
item that must become enabled, a NodeView that shows a preview and then
swaps it for the real image, and an upload that can fail in front of a
person. 250db1c's merge went 44/44 to 39/44 on that drive with the gate
green throughout, including a real WCAG SC 1.4.10 failure at 320px. So
D3 and the risk table now require a browser drive before merge instead
of leaving it to judgement.

With the trap that comes with it. Enabling insert-link and insert-image
gives each command a second home, and menu items precede the toolbar in
document order — so a bare [data-docs-command="insert-link"] returns the
MENU item, which sits inside a closed <details> with a zero-size rect,
every click lands at the page corner, and the failure surfaces thirty
seconds later as "timed out waiting for the link". Drive selectors scope
to [docs-toolbar] or to the dialog's own root. clickSelector now refuses
a zero-size target and refuses to pick the first of several matches, but
only for code that goes through it.

Verified rather than asserted: the drive runs from here at 44/44,
exit 0, on 68273a4 plus this branch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev c1ac560edab7ea8065339dd7cee1f5284837fe83 parent 9b728c3
1 file changed, +22 −0
  • docs/superpowers/plans/2026-09-02-images-links-tables.md +22 −0

CI passed — run details

diff --git a/docs/superpowers/plans/2026-09-02-images-links-tables.md b/docs/superpowers/plans/2026-09-02-images-links-tables.md
index a342cb8..670109e 100644
--- a/docs/superpowers/plans/2026-09-02-images-links-tables.md
+++ b/docs/superpowers/plans/2026-09-02-images-links-tables.md
@@ -380,6 +380,27 @@ disabled**, reserving `insert-link`, `insert-image` and `insert-table` in
`toolbar.mjs`'s command table. Implement against those names and no menu
changes are needed — the items simply become enabled.
+**Every one of these tasks is browser-driven before it merges, and the
+drive is scoped.** `scripts/browser-drive.sh`, sandbox off. Two things
+make this non-optional here rather than a nicety:
+
+- The UI surface is exactly the shape a green gate cannot see: a
+ disabled state that must become enabled, a NodeView that shows a
+ preview and then swaps it for the real image, and an upload that can
+ fail in front of a person. `250db1c`'s own merge took the drive from
+ 44/44 to 39/44 with the gate green throughout, including a real WCAG
+ SC 1.4.10 failure at 320px.
+- **`insert-link` and `insert-image` will exist in two places** the
+ moment they are enabled — the menu bar and the toolbar — and menu
+ items come first in document order. So a bare
+ `[data-docs-command="insert-link"]` selects the menu item, which lives
+ inside a closed `<details>` with a zero-size rect, and every click
+ lands in the page corner. Scope every drive selector to
+ `[docs-toolbar] …` or to the dialog's own root. `clickSelector` now
+ refuses a zero-size target and refuses to pick the first of several
+ matches, so it will say so rather than time out — but only if it is
+ the thing doing the clicking.
+
### Task 6 — the sealing key, the blob store and the route
**Blocked on Paul's key decision.** Then `models.go` plus a migration for
`document_blobs`; `blobs.Sealed` over `blobs.S3FromEnv()` in
@@ -424,6 +445,7 @@ but not silence.
| `validate: "string"` on `image.src`/`link.href` | All | A raw update with a non-string attr makes y-prosemirror **delete the type**. Pre-existing, but links and images widen it from "attrs the editor never writes" to "attrs every paste writes". Not this plan's to fix; recorded so it is not rediscovered as a mystery. |
| Two `prosemirror-model` copies | Task 1 | `npm ls prosemirror-model` after install. Breaks silently. |
| Two branches inventing the same vocabulary | All | Discussion #20 (`worktree-import-documents`) builds DOCX import, which produces these same nodes. Agree names and attrs there first. |
+| A green gate on UI a browser would have failed | Tasks 3, 5, 8, 9 | `scripts/browser-drive.sh` with the sandbox off, before each merge, with selectors scoped per D3. Not optional: this is the one surface the gate is blind to. |
## What this plan does not do