F11 — Preview toolbar as the primary interaction surface (#43) #44

Merged
benstull merged 6 commits from f11-preview-toolbar into main 2026-06-12 21:19:02 +00:00
Owner

F11 — Preview Toolbar as the Primary Interaction Surface (#43)

Makes the rendered review preview's header toolbar the primary interaction surface. Per the graduated Solution Design docs/superpowers/specs/2026-06-12-f11-preview-toolbar-interaction-surface.md (brainstorming session 0036); built in planning-and-executing session 0037.

Beside the existing Annotations checkbox the toolbar gains:

  • ⌖ Pin baseline — pins the previewed doc's review baseline to now, clearing the change-marks (homes the previously-orphaned cowriting.pinDiffBaseline, reachable from the palette again).
  • ✦ Ask Claude… — one adaptive button: Edit Selection when text is selected in the rendered preview, Edit Document otherwise. The host prompts for the instruction and runs the LLM turn host-side (the sealed webview gains no LLM/credential surface), surfacing the result as F4 proposals — one for a selection (mapped to its source block-union), or one per changed hunk for a whole-document rewrite.

A minimal right-click → Open Review Preview gateway (editor/title) makes the surface reachable end to end (#41/#42 expand it). No new model, no new persistence — pin via the F6 store, edits via the F4 propose/accept seam with F3 attribution. INV-35..37.

Slices (each green before the next)

  • SLICE-1 Pin baseline button + unhide pinDiffBaseline.
  • SLICE-2 Block-level data-src-start/end emission in both render modes (INV-36).
  • SLICE-3 Edit Document button + diffToHunks → per-hunk F4 proposals (INV-37).
  • SLICE-4 Single adaptive Ask-Claude button + selection→source mapping.
  • SLICE-5 editor/title gateway, non-authorable disabling, docs.

Code review (self, pre-merge) — fixes applied

  • Critical: pure-insertion hunks were born-orphaned (zero-width range → empty fingerprint → resolve orphans → proposal could never be accepted). diffToHunks now anchors insertions to an adjacent source token; new E2E accepts a rewrite-with-insertion end to end and asserts accept-all reconstructs the intended document.
  • Important: renderPlain per-block rendering can't resolve cross-block reference-link defs — documented as a conscious tradeoff of the operator-locked block-level mapping (renderReview already rendered per-block; this keeps both modes consistent) with a characterization test.
  • Minor: F11 proposals now carry a turnId (one per gesture).

Tests

  • 208 unit (vitest) green.
  • 9/9 F11 host E2E green; full host E2E green except a pre-existing, F11-independent undoMarks timing flake — proven by isolation (it fails identically with all F11 tests removed; root cause is executeCommand("undo") not reverting programmatic WorkspaceEdits in test-electron). Recommend a dedicated follow-up to redesign that test off executeCommand("undo").

Live smoke: docs/MANUAL-SMOKE-F11.md (manual, sealed-webview territory).

🤖 Generated with Claude Code

## F11 — Preview Toolbar as the Primary Interaction Surface (#43) Makes the rendered review preview's **header toolbar** the primary interaction surface. Per the graduated Solution Design `docs/superpowers/specs/2026-06-12-f11-preview-toolbar-interaction-surface.md` (brainstorming session 0036); built in planning-and-executing session **0037**. Beside the existing **Annotations** checkbox the toolbar gains: - **⌖ Pin baseline** — pins the previewed doc's review baseline to now, clearing the change-marks (homes the previously-orphaned `cowriting.pinDiffBaseline`, reachable from the palette again). - **✦ Ask Claude…** — one **adaptive** button: *Edit Selection* when text is selected in the rendered preview, *Edit Document* otherwise. The host prompts for the instruction and runs the LLM turn host-side (the sealed webview gains no LLM/credential surface), surfacing the result as F4 proposals — one for a selection (mapped to its source block-union), or one per changed hunk for a whole-document rewrite. A minimal right-click → **Open Review Preview** gateway (`editor/title`) makes the surface reachable end to end (#41/#42 expand it). No new model, no new persistence — pin via the F6 store, edits via the F4 propose/accept seam with F3 attribution. INV-35..37. ### Slices (each green before the next) - **SLICE-1** Pin baseline button + unhide `pinDiffBaseline`. - **SLICE-2** Block-level `data-src-start/end` emission in both render modes (INV-36). - **SLICE-3** Edit Document button + `diffToHunks` → per-hunk F4 proposals (INV-37). - **SLICE-4** Single adaptive Ask-Claude button + selection→source mapping. - **SLICE-5** `editor/title` gateway, non-authorable disabling, docs. ### Code review (self, pre-merge) — fixes applied - **Critical:** pure-insertion hunks were born-orphaned (zero-width range → empty fingerprint → `resolve` orphans → proposal could never be accepted). `diffToHunks` now anchors insertions to an adjacent source token; new E2E **accepts** a rewrite-with-insertion end to end and asserts accept-all reconstructs the intended document. - **Important:** `renderPlain` per-block rendering can't resolve cross-block reference-link defs — documented as a conscious tradeoff of the operator-locked block-level mapping (renderReview already rendered per-block; this keeps both modes consistent) with a characterization test. - **Minor:** F11 proposals now carry a `turnId` (one per gesture). ### Tests - **208 unit** (vitest) green. - **9/9 F11 host E2E** green; full host E2E green **except** a pre-existing, F11-independent `undoMarks` timing flake — proven by isolation (it fails identically with all F11 tests removed; root cause is `executeCommand("undo")` not reverting programmatic `WorkspaceEdit`s in test-electron). **Recommend a dedicated follow-up** to redesign that test off `executeCommand("undo")`. Live smoke: `docs/MANUAL-SMOKE-F11.md` (manual, sealed-webview territory). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
benstull added 6 commits 2026-06-12 21:18:35 +00:00
Homes the orphaned cowriting.pinDiffBaseline command and gives the writer a
reachable Pin control in the preview toolbar. Per spec §7.2 SLICE-1
(docs/superpowers/specs/2026-06-12-f11-preview-toolbar-interaction-surface.md).

- trackChangesPreview: extract onDidReceiveMessage into handleWebviewMessage;
  add the F11 `pinBaseline` intent → DiffViewController.pin(previewedDoc) (the
  bound doc, not activeTextEditor — §6.7); ToolbarMsg union; receiveMessage test
  seam exercising the real message→seam wiring (INV-35).
- webview: ⌖ Pin baseline button in #cw-header posting { type: "pinBaseline" };
  theme-aware toolbar-button CSS (light/dark/high-contrast, disabled state).
- package.json: unhide pinDiffBaseline — commandPalette `when` false →
  editorLangId == markdown (resolves the #34 orphan from the command side).
- host E2E (test/e2e/suite/f11Toolbar.test.ts): pinBaseline message clears the
  change-marks + advances the baseline to `pinned`; palette `when` is reachable.

Also archives the F11 implementation plan to docs/superpowers/plans/.

197 unit + 47 host E2E green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pure render layer now emits data-src-start/data-src-end (source char offsets
from BlockWithRange) on every LIVE-source rendered block, in BOTH modes — the
contract the webview's selection→source mapping (SLICE-4) walks the DOM for.
Per spec §6.1 INV-36 / §7.2 SLICE-2.

- trackChangesModel: shared `srcAttr(blk)` helper; threaded through renderOp +
  renderReviewOp + the renderReview loop (removed blocks → "" / no data-src, as
  they have no live source). renderPlain switches from a single whole-document
  markdown pass to per-block `<div data-src-start/end>` wrappers — bare divs (no
  cw- class) so the off/clean preview stays visually clean while becoming a
  selection→source surface. Pure, vscode-free, deterministic (extends INV-22).
- unit (test/trackChangesModel.test.ts): data-src offsets equal
  splitBlocksWithRanges in both modes; removed + proposal blocks carry none;
  off-mode stays cw--free; determinism.

200 unit + 47 host E2E green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A whole-document Ask-Claude rewrite is diffed into hunks and surfaced as N
independent F4 proposals (one per changed hunk) — reusing the F4 single-range
model N times, no new model. Per spec §6.4/§7.2 SLICE-3.

- trackChangesModel: pure `diffToHunks(currentText, rewrittenText)` →
  EditHunk[] (vscode-free, deterministic; diffWordsWithSpace, coalescing
  adjacent add/remove runs; offsets index currentText).
- trackChangesPreview: `runEditAndPropose(document, target, instruction)` — the
  shared host routine (selection → one single-range propose; document → diff →
  one propose per hunk; never mutates the doc, INV-10); `askClaude` UI wrapper
  (host showInputBox keeps LLM/secrets out of the sealed webview, INV-8/35);
  injectable `editTurn` + `setEditTurnForTest` seam (no LLM in CI); the
  `askClaude` inbound message branch; `cowriting.editDocument` command for #42
  reuse.
- package.json: register cowriting.editDocument, palette-guarded on markdown.
- webview: ✦ Ask Claude to Edit Document button → { askClaude, scope:"document" }.
- unit: diffToHunks fixtures (zero/one/multi-hunk, wholesale, determinism).
- host E2E: stubbed multi-hunk rewrite → N matching proposals, doc untouched;
  editDocument command registered + markdown-guarded.

205 unit + 49 host E2E green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The one Ask-Claude toolbar button now adapts: its label flips on selectionchange
(Edit Selection when live text is selected in the preview, Edit Document
otherwise), and a click resolves the preview selection to a SOURCE range via the
nearest data-src ancestors (INV-36) — the webview's sole mapping duty. A
selection touching no live-source block falls back to document scope. Per spec
§6.5 PUC-2/3 / §7.2 SLICE-4.

- webview (media/preview.ts): nearestSrc() DOM walk + selectionSrcRange()
  block-union; updateAskLabel() on selectionchange + after each render; the
  adaptive click posts { askClaude, scope:"selection", start, end } or falls back
  to document scope. Sealed (INV-21): reads data-src only, posts intent.
- host: runEditAndPropose's range branch (already shared from SLICE-3) records one
  single-range F4 proposal over the resolved block-union.
- host E2E: stubbed selection turn → exactly one proposal over the resolved range
  (turn receives exactly the selected source; replaced == the range; doc
  untouched); an unchanged replacement proposes nothing. (The webview DOM
  selection→data-src lookup is sealed-sandbox → manual smoke, spec §6.8.)

205 unit + 51 host E2E green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the F11 feature: makes the toolbar surface reachable end to end and
guards the edit controls. Per spec §6.4/§6.5/§7.2 SLICE-5.

- package.json: cowriting.showTrackChangesPreview added to editor/title
  (when: editorLangId == markdown) — the minimal right-click → Open Review
  Preview gateway (#41/#42 expand it later).
- trackChangesPreview: the gateway command accepts the tab's resource Uri
  (palette/keybinding still fall back to the active editor); refresh() sends an
  `authorable` flag on both render messages; `editControlsEnabled` test seam.
- webview: disable Pin + Ask-Claude on a non-authorable doc (Annotations stays
  active — reading is always allowed); RenderMessage.authorable.
- host E2E: the editor/title gateway opens the preview + is markdown-guarded;
  edit controls disabled on a non-authorable (read-only-scheme) markdown doc.
- docs: docs/MANUAL-SMOKE-F11.md (live smoke, 10 steps) + README F11 section +
  intro line.

205 unit + 53 host E2E green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code-review follow-up on the F11 branch (5966907..1564ef5). Three real findings:

- CRITICAL — pure-insertion hunks were born-orphaned. A document rewrite that
  INSERTS text produced a zero-width hunk (start==end) → buildFingerprint yields
  empty fp.text → anchorer.resolve orphans an empty needle → the proposal could
  never be accepted (silently). diffToHunks now anchors every zero-width
  insertion to an adjacent source token (anchorInsertion): the range absorbs the
  token and the replacement keeps it, so net text is identical but fp.text is
  non-empty and resolvable. New unit tests: applying hunks always reconstructs
  the rewrite (substitute/delete/insert/multi); insertions are never zero-width.
  New host E2E ACCEPTS a rewrite-with-insertion end to end and asserts accept-all
  reconstructs the intended document (also covers sequential multi-hunk accept).

- IMPORTANT — renderPlain cross-block fidelity. The per-block off-mode rendering
  (SLICE-2) can't resolve a reference-link definition in a separate block.
  Documented as a conscious tradeoff of the operator-locked block-level mapping
  (§6.7) — renderReview already rendered per-block, so this keeps both modes
  consistent — with a characterization test + a docstring note.

- MINOR — F11 proposals now carry a turnId (one per Ask-Claude gesture, shared
  across a document rewrite's N hunks), matching the editor-menu editSelection
  path so a rewrite groups as one agent turn.

208 unit + 9/9 F11 host E2E green. (The lone red E2E is a pre-existing,
F11-independent undoMarks timing flake — proven by isolation: it fails
identically with all F11 tests removed.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
benstull merged commit 62a2229c25 into main 2026-06-12 21:19:02 +00:00
Sign in to join this conversation.