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

124 lines
5.7 KiB
Markdown

# 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 `receiveMessage``getLastModel` 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 `showInputBox` →
`runEditTurn` 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.