Files
vscode-cowriting-plugin/docs/superpowers/plans/2026-06-12-f11-preview-toolbar.md
T
Ben Stull 8b9e61a1da feat(f11): SLICE-1 — Pin baseline toolbar button + reachability (#43)
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>
2026-06-12 13:40:21 -07:00

5.7 KiB

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

Spec: docs/superpowers/specs/2026-06-12-f11-preview-toolbar-interaction-surface.md Anchor: Feature benstull/vscode-cowriting-plugin#43 (F11, type/feature, priority/P1) Session: vscode-cowriting-plugin-0037

This plan transcribes the spec's §7.2 slicing plan into concrete, file-level tasks. Each slice is independently green (unit + host E2E) before the next. Host E2E is this app's required tier (no browser/deploy stage — a VS Code extension); no LLM in CI (edit turns stubbed). The webview's visual rendering, the adaptive label, and the selection→source DOM lookup are manual-smoke only.


SLICE-1 — Pin baseline button + reachability (the immediate win)

Homes the orphaned cowriting.pinDiffBaseline command and gives the writer a reachable Pin control in the preview toolbar.

Tasks

  1. Host message routing (src/trackChangesPreview.ts): extract the inline onDidReceiveMessage body into a private handleWebviewMessage(document, m) method; add a pinBaseline branch that calls this.diffView.pin(document) (the previewed document — not activeTextEditor). The existing onDidChangeBaseline subscription already re-renders with cleared marks.
  2. Test seam: add receiveMessage(uriString, m) that resolves the doc and calls handleWebviewMessage, so host E2E can simulate the raw webview message and exercise the real routing.
  3. Webview (media/preview.ts + .css): add a ⌖ Pin baseline button in #cw-header; click → postMessage({ type: "pinBaseline" }); theme-aware CSS.
  4. Shell HTML (shellHtml): add the <button id="cw-pin"> to the header row.
  5. Reachability (package.json): flip cowriting.pinDiffBaseline's commandPalette when from false to editorLangId == markdown.

Host E2E (test/e2e/suite/trackChangesPreview.test.ts or new f11 suite): open a markdown fixture with a divergent baseline → some block marked; simulate {type:"pinBaseline"} via receiveMessagegetLastModel shows every block unchanged (marks cleared) and getBaseline(key).reason === "pinned".

Unit: none new (pure layer untouched this slice).


SLICE-2 — Block-offset emission (INV-36 data layer)

Shared pure helper wrapping each rendered block with data-src-start/-end (source char offsets from BlockWithRange) in both renderReview and renderPlain. vscode-free, DOM-free, deterministic (extends INV-22/33). No UI.

Tasks

  1. src/trackChangesModel.ts: a shared internal helper that prepends data-src-start="N" data-src-end="M" to each block's wrapping element, routed from both render paths using the existing splitBlocksWithRanges offsets.
  2. Skip blocks with no live-source range (deletion-only / proposal blocks).

Unit (test/trackChangesModel.test.ts): data-src-start/end present and correct on every block for both modes (offsets equal BlockWithRange ranges); determinism (same inputs → identical HTML).


SLICE-3 — Edit Document button + hunk path (INV-37 document half)

Tasks

  1. src/trackChangesModel.ts: pure diffToHunks(currentText, rewrittenText): Array<{ start; end; replacement }> — vscode-free, deterministic.
  2. src/trackChangesPreview.ts: runEditAndPropose(document, target, instruction) private routine; askClaude/document branch → host showInputBoxrunEditTurn over full text → diffToHunks → one F4 propose() per hunk.
  3. package.json: register cowriting.editDocument (document-scoped), routed through runEditAndPropose({kind:"document"}); for #42 reuse.
  4. Webview: ✦ Ask Claude to Edit Document button (no-selection state) → postMessage({ type: "askClaude", scope: "document" }).

Unit: diffToHunks over fixtures (single hunk → one range; multi-hunk → disjoint ranges + correct replacements; unchanged → zero hunks; whole-doc replacement → one full-range hunk).

Host E2E: simulate {askClaude, scope:"document"} with a stubbed multi-hunk rewrite → N proposals matching the hunks.


SLICE-4 — Adaptive Edit Selection (INV-37 selection half; INV-36 consumer)

Tasks

  1. Webview: selectionchange listener flips the Ask-Claude label (Edit Selection ⇆ Edit Document); selection→nearest-data-src ancestor resolution → postMessage({ type:"askClaude", scope:"selection", start, end }).
  2. Host: askClaude/selection branch → runEditAndPropose({kind:"range", start, end}) → one runEditTurn → one F4 propose() over the block-union.
  3. Edge: selection resolving to no live block → fall back to document scope.

Host E2E: simulate {askClaude, scope:"selection", start, end} with a stubbed edit turn → exactly one proposal over the resolved range, anchored inline.


SLICE-5 — Gateway, edges, tests & docs

Tasks

  1. package.json: add cowriting.showTrackChangesPreview to editor/title with when: editorLangId == markdown (minimal right-click gateway).
  2. Non-authorable disabling: Pin + Ask-Claude controls render disabled when !isAuthorable(document); annotations toggle stays active.
  3. Host E2E: gateway command opens the panel; controls inert on non-authorable.
  4. docs/MANUAL-SMOKE-F11.md (live smoke script per spec §6.8).
  5. README F11 section.

Done = #43 acceptance (spec §7.3)

Preview toolbar hosts annotations checkbox + Pin baseline + single adaptive Ask-Claude (Edit Selection ⇆ Edit Document) routing through existing F4/F3/F6; edits surface as proposals (one for a selection, per-hunk for a document rewrite); a right-click entry opens the preview; the pin command is no longer orphaned; unit + host E2E green; live smoke performed once.