diff --git a/docs/superpowers/specs/2026-06-12-f11-preview-toolbar-interaction-surface.md b/docs/superpowers/specs/2026-06-12-f11-preview-toolbar-interaction-surface.md
new file mode 100644
index 0000000..d1a065c
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-12-f11-preview-toolbar-interaction-surface.md
@@ -0,0 +1,602 @@
+---
+status: graduated
+---
+# Solution Design: Preview Toolbar as the Primary Interaction Surface (F11)
+
+| | |
+| --- | --- |
+| **Author(s)** | Ben Stull (with Claude) |
+| **Reviewers / approvers** | Ben Stull |
+| **Status** | `draft` |
+| **Version** | v0.1.0 |
+| **Source artifacts** | Feature `benstull/vscode-cowriting-plugin#43` (F11, `type/feature`, `priority/P1`) · Epic `#1` (closed) · Capture session `vscode-cowriting-plugin-0035` (2026-06-12) · Brainstorming session `vscode-cowriting-plugin-0036` · Builds on (all shipped): F3 `#6` (live attribution), F4 `#12` (propose/accept), F6 `#17`/`#19` (baseline + diff view), F7 `#21`/`#22` (rendered preview), F9 `#27` (authorship preview), F10 `#29` (interactive review preview), F10-followups `#31` (inline-at-anchor proposals) · Coexists with `#41` (right-click → Open Review Panel) and `#42` (right-click → Ask Claude to Edit), both blocked-by this · Parent specs (graduated): `coauthoring-inner-loop.md`, `coauthoring-attribution.md`, `coauthoring-propose-accept.md`, `coauthoring-diff-view.md`, `coauthoring-rendered-preview.md`, `coauthoring-interactive-review.md` · Lineage: `ben.stull/rfc-app#48` |
+
+**Change log**
+
+| Date | Version | Change | By |
+| --- | --- | --- | --- |
+| 2026-06-12 | v0.1.0 | Initial draft — brainstorming session 0036 (from the capture in session 0035). Three forks locked with the operator: block-level preview-selection→source mapping; document edit diffed into per-hunk F4 proposals; #43 lands a minimal right-click→open-preview gateway. | Ben Stull + Claude |
+
+---
+
+## 1. Business Context
+
+### 1.1 Executive Summary
+
+F10 made the rendered preview the **single review surface** — clean editor on the
+left, annotated review on the right, with an annotations on/off toggle and
+inline ✓/✗ on Claude's pending proposals. But the writer still cannot *act* from
+the preview beyond accepting/rejecting proposals: to **ask Claude to edit**, they
+jump back to the editor and use a selection-gated context-menu item; to **pin a
+fresh review baseline** they have *no* reachable control at all (the
+`cowriting.pinDiffBaseline` command is registered but `when:false`, orphaned
+since `#34` removed its two-pane host); and whole-document editing doesn't exist.
+
+F11 makes the **preview toolbar the primary interaction surface**. Beside the
+existing annotations checkbox — the one control the writer already loves — the
+toolbar gains a **Pin baseline** button and a **single adaptive "Ask Claude…"
+button** that reads *Edit Selection* when text is selected in the preview and
+*Edit Document* when nothing is. The writer reads, asks Claude to edit, and
+resets the baseline all in one place, mouse-first, without leaving the rendered
+document. A minimal right-click entry opens the preview, making it the surface
+the `#41`/`#42` gateways will lead into.
+
+### 1.2 Background
+
+The inner loop shipped F2–F5 (threads · attribution · propose/accept ·
+cross-rung). F6 added the baseline + a native diff toggle; F7 the rendered
+track-changes preview; F9 an authorship mode; F10 (`#29`) collapsed those into the
+**single interactive review preview** (clean editor; annotations on/off; ✓/✗ on
+F4 proposals surfaced in the rendered view); `#31` then placed proposals
+**inline at their resolved anchor** in that preview.
+
+Capture session 0035 filed `#43` (this feature) plus `#41` (right-click → Open
+Review Panel) and `#42` (right-click → Ask Claude to Edit). The operator's ask:
+*"Can we set it up so all interactions — Ask Claude to Edit Selection / Edit
+Document, annotations off/on, Pin new baseline — are via the preview window? I
+like the annotations checkbox up there; make the others buttons, with one 'Ask
+Claude…' button that changes depending on whether some of the markdown preview is
+selected."* That session also surfaced the stranded `pinDiffBaseline` command
+(`when:false` since `#34`). This spec is the Solution Design for `#43`.
+
+### 1.3 Business Actors / Roles
+
+- **Coauthor (human)** — the markdown writer/engineer (PP-1); F11's sole user.
+- **Coauthor (machine)** — Claude via `@cline/sdk`; not a user of F11, but the
+ target of the toolbar's "Ask Claude…" gesture and the author of the proposals
+ that result.
+
+### 1.4 Problem Statement
+
+The plugin's interactions are scattered across surfaces and inconsistently
+reachable. "Ask Claude to Edit Selection" is only a selection-gated **editor**
+context-menu item; **whole-document editing doesn't exist**; and **Pin Review
+Baseline** is **unreachable from any UI**. The one control the writer loves — the
+annotations on/off checkbox in the preview — proves the toolbar is the natural
+home for these gestures, but it stands alone. A writer reviewing in the preview
+has to leave it and hunt through editor menus / the palette to act.
+
+### 1.5 Pain Points
+
+- **No edit gesture in the preview** — to ask Claude to change anything, the
+ writer leaves the review surface for the editor's right-click menu.
+- **Whole-document editing is missing** — there is no "edit the whole document"
+ path at all; only a selection-scoped editor command exists.
+- **Pin baseline is stranded** — the command exists but no menu, keybinding, or
+ palette entry reaches it (`when:false` since `#34`).
+- **Mouse-first review is broken mid-flow** — the preview is mouse-driven, but
+ acting forces a context-switch to keyboard/menus elsewhere.
+
+### 1.6 Targeted Business Outcomes
+
+The preview becomes a **self-contained cockpit** for the inner loop. From its
+toolbar a writer can toggle annotations (today), **ask Claude to edit** (selection
+or whole document, via one button that adapts to what's selected), and **pin a
+fresh baseline** — no context-switching to the editor or command palette. The
+interaction model consolidates around the surface the writer already prefers, and
+the stranded pin command gets a real home.
+
+### 1.7 Scope (business)
+
+**In scope:** preview-webview toolbar controls — a **Pin baseline** button and a
+**single adaptive "Ask Claude…" button** (Edit Selection ⇆ Edit Document) — beside
+the existing annotations checkbox; wiring those controls to the **existing** F4
+edit seam, F3 attribution, and F6 baseline command; **block-level**
+preview-selection → source-range mapping (the central design risk); a **new
+whole-document edit path** whose result is **diffed into per-hunk F4 proposals**;
+a **minimal right-click → Open Review Preview** entry so the surface is reachable
+end-to-end; resolving the pin-baseline reachability gap; unit + host-E2E coverage;
+manual webview smoke.
+
+**Out of scope (deferred, not forgotten):** **char-precise sub-block** selection
+mapping (block granularity is the locked v1 — §6.7); the **richer `#41`/`#42`
+menu sets** (this feature lands only the minimal gateway; `#41`/`#42` expand it);
+preview→source **scroll-sync** (`#32`); multi-file / batch editing; the Explorer
+tree affordance; any export / print / copy gesture.
+
+**Non-goals (firm):** **no new edit / attribution / proposal *model*** — F11
+reuses F3 `spansFor`, the F4 `propose`/`accept` single-range model, and the F6
+baseline store; no change to the **sidecar**, the **cross-rung contract**, or
+`SCHEMA_VERSION`; **no document mutation from the webview** (INV-20/21/34 hold —
+the sealed webview posts intent only); no LLM/network/credential surface added to
+the webview (INV-8 untouched — the edit turn runs host-side as today).
+
+### 1.8 Assumptions · Constraints · Dependencies
+
+- **Anchor:** Feature `#43` (F11). Builds directly on shipped work: the F7/F10
+ rendered preview + annotations toggle + host↔webview message bus, the F3
+ attribution + F4 propose/accept inner loop (including `#31`'s inline-at-anchor
+ proposal placement), and the F6 baseline store (`cowriting.pinDiffBaseline`,
+ currently unreachable — this feature gives it a home).
+- **Central design risk (locked):** the preview is a **rendered** sealed webview
+ (markdown-it HTML, strict CSP — F7 INV-21), so "Edit Selection" must map a
+ selection in the rendered preview back to a **source markdown range**. The
+ rendered HTML carries **no** source positions today; only internal block
+ char-offsets exist (`splitBlocksWithRanges` → `BlockWithRange.start/end`). The
+ locked approach is **block-level** mapping: the pure render layer emits
+ `data-src-start`/`data-src-end` on each rendered block; a selection resolves to
+ the union of the live-source blocks it intersects (§6.7, fork 1).
+- **Constraint (sealed webview):** interactive controls post messages to the
+ extension host, which applies edits/pins via the existing F4 / F6 paths; the
+ webview **never** edits the document, sidecar, or baseline directly. The edit
+ **turn** (LLM call) and the **instruction prompt** run host-side, keeping the
+ webview free of LLM/network/credential surface.
+- **Coexistence:** the native editor context-menu "Ask Claude to Edit Selection"
+ (`cowriting.editSelection`) stays unchanged; `#41`/`#42` add right-click
+ gateways *into* the preview. F11 lands only a minimal gateway (§6.7, fork 3).
+- No new persisted artifact; nothing in `.threads/`, the contract, or
+ `SCHEMA_VERSION` changes.
+
+### 1.9 Business Use Cases
+
+- **BUC-1 (edit from the preview)** Reviewing in the preview, the writer selects a
+ paragraph in the rendered document and clicks **"Ask Claude to Edit
+ Selection"**; Claude's proposed change appears as a blue ✓/✗ block at that
+ spot — without the writer ever leaving the preview.
+- **BUC-2 (edit the whole document)** With nothing selected, the writer clicks
+ **"Ask Claude to Edit Document"**, types an instruction, and Claude's rewrite
+ surfaces as **several** independently-acceptable blue proposal blocks (one per
+ changed hunk) inline in the preview.
+- **BUC-3 (pin a fresh baseline)** After accepting a batch of changes, the writer
+ clicks **"Pin baseline"**; the change-marks clear and "what changed" now counts
+ from this moment — the stranded command finally has a button.
+
+---
+
+## 2. Solution Proposal
+
+F11 is a **thin increment** on F10's preview: it adds two controls to the
+existing header toolbar and routes their intent through machinery that already
+exists. No new model, no new persisted state.
+
+**The pure render layer (`trackChangesModel.ts`) learns one new thing:** a shared
+helper wraps each rendered block in `
`
+using the existing `splitBlocksWithRanges` offsets. It is applied to **both**
+render paths — `renderReview` (annotations on) and `renderPlain` (annotations
+off) — so selection→source mapping works in either mode. The helper is pure,
+vscode-free, DOM-free, and deterministic (extends INV-22/33). The render layer
+gains **no** selection or DOM logic.
+
+**The webview (`media/preview.ts` + `.css`)** header becomes:
+
+```
+[ ☑ Annotations ] [ ⌖ Pin baseline ] [ ✦ Ask Claude to Edit Document ▾ ]
+```
+
+The **Ask-Claude button morphs its own label** on `selectionchange`: a non-empty
+selection inside the rendered body → **"Ask Claude to Edit Selection"**; an empty
+/ collapsed selection → **"Ask Claude to Edit Document"**. On click it walks the
+selection's start and end nodes up to the nearest ancestor carrying
+`data-src-start`/`data-src-end` and posts the resolved offsets. That
+nearest-ancestor lookup is the webview's **only** mapping duty (manual-smoke
+territory); everything downstream is host-side and testable. The webview stays
+**sealed** (INV-21): nonce'd inline script, no network, no document mutation.
+
+**New webview→host messages (intent only):**
+
+```ts
+type ToolbarMsg =
+ | { type: "pinBaseline" }
+ | { type: "askClaude"; scope: "selection"; start: number; end: number }
+ | { type: "askClaude"; scope: "document" };
+```
+
+**The host (`trackChangesPreview.ts`)** routes each intent through the existing
+seams:
+
+- **`pinBaseline`** → pins the **previewed** document (calls
+ `DiffViewController.pin(document)` directly — not the `activeTextEditor`-based
+ command, which may not point at the previewed doc) → `onDidChangeBaseline` →
+ re-render with cleared marks.
+- **`askClaude`** → host `showInputBox` for the instruction (keeps the LLM /
+ secret surface out of the sealed webview), then one shared host routine
+ `runEditAndPropose(document, target, instruction)`:
+ - **selection** → `target` = the block-union range `[firstBlock.start …
+ lastBlock.end]`; one `runEditTurn` → one F4 `propose()` over that range (the
+ existing Edit-Selection shape).
+ - **document** → `target` = whole document; one `runEditTurn` over the full
+ text → **diff Claude's result against the current text → one `propose()` per
+ changed hunk** (multiple proposals, each its own blue ✓/✗ block). Reuses the
+ F4 single-range model N times; no model change.
+
+**The right-click gateway:** `cowriting.showTrackChangesPreview` is added to the
+`editor/title` menu (markdown only) so right-clicking the tab opens the preview —
+the minimal entry that makes the toolbar surface reachable end-to-end and lets
+`#41`/`#42` expand the menu set later.
+
+**Reachability cleanup:** `cowriting.pinDiffBaseline` gets a real palette `when`
+(`editorLangId == markdown`), resolving the orphan from the command side too; a
+new `cowriting.editDocument` command is registered (document-scoped edit) so
+`#42`'s gateway can reuse it.
+
+Everything downstream of *(intent) → (existing seam)* is the existing F4/F6/F3
+machinery; the only genuinely new pure code is the block-offset wrapper and the
+document-rewrite hunk-diff. Both are unit-testable with no vscode and no webview.
+
+---
+
+## 3. Product Personas
+
+- **PP-1 Inner-loop coauthor** — the human markdown writer/engineer (as F2–F10);
+ the only persona F11 serves.
+
+## 4. Product Use Cases
+
+- **PUC-1 (toolbar present)** Opening the review preview for a markdown document
+ shows the header with **three** controls: the annotations on/off checkbox
+ (existing), a **Pin baseline** button, and an adaptive **Ask Claude…** button.
+ Controls are inert (disabled) for non-authorable documents.
+- **PUC-2 (adaptive label)** With a non-empty selection in the rendered preview
+ body, the Ask-Claude button reads **"Ask Claude to Edit Selection"**; with no
+ selection it reads **"Ask Claude to Edit Document"**. The label flips live as
+ the selection changes.
+- **PUC-3 (edit selection)** The writer selects rendered text, clicks **Ask
+ Claude to Edit Selection**, and enters an instruction. The selection resolves to
+ the union of the source blocks it touches; Claude proposes a change over that
+ range; a single blue ✓/✗ proposal block appears inline at that anchor (`#31`).
+- **PUC-4 (edit document)** With nothing selected, the writer clicks **Ask Claude
+ to Edit Document**, enters an instruction; Claude rewrites the whole document;
+ the rewrite is diffed into hunks and surfaces as **N** independent blue ✓/✗
+ proposal blocks inline. Accepting/rejecting each is the F10 path unchanged.
+- **PUC-5 (pin baseline)** The writer clicks **Pin baseline**; the previewed
+ document's review baseline is pinned to now; the change-marks clear and the
+ `Since ` label updates. (No confirmation prompt — matches the existing
+ command's behavior; re-pinning is the recovery.)
+- **PUC-6 (right-click into the preview)** Right-clicking a markdown editor tab
+ shows **Open Review Preview**; choosing it opens the preview (the gateway
+ `#41`/`#42` will build upon).
+- **PUC-7 (graceful edges)** A selection confined to a deletion (struck) or
+ proposal block — which carries no live-source range — falls back to **document**
+ scope. An empty document or a selection that resolves to no live block → the
+ button stays in **Edit Document** mode. A non-authorable document → toolbar edit
+ controls are disabled. The LLM turn failing → the existing `runEditTurn`
+ error handling (no proposal created); the preview is unchanged.
+
+---
+
+## 5. UX Layout
+
+The F10 preview is unchanged except for its **header bar**, which now hosts three
+controls in a single row:
+
+- **☑ Annotations** — the existing on/off checkbox (kept first; the operator's
+ preferred control).
+- **⌖ Pin baseline** — a button; pins the previewed document's review baseline to
+ now and clears the change-marks.
+- **✦ Ask Claude to Edit Document ▾** — a single button whose label and behavior
+ adapt to the preview's selection state (Edit **Selection** when text is
+ selected, Edit **Document** otherwise). Clicking it opens a host input box for
+ the instruction.
+
+Buttons are styled with theme CSS variables (light / dark / high-contrast),
+matching the existing toolbar chrome; they sit in the same `#cw-toggle` header
+region as the annotations checkbox. When the previewed document is not authorable
+(F8 `isAuthorable`), the **Pin baseline** and **Ask Claude…** controls render
+**disabled** (the annotations toggle stays active — reading is always allowed).
+
+The rendered body is unchanged from F10/`#31`: green human additions, blue
+LLM-authored text, struck deletions, and pending Claude proposals as inline blue
+blocks with ✓/✗ at their resolved anchors. Proposals produced via the new toolbar
+edit gestures appear exactly as proposals do today.
+
+---
+
+## 6. Technical Design
+
+### 6.1 Invariants
+
+Parent invariants INV-1..INV-34 carry over unchanged. F11 adds:
+
+- **INV-35 (toolbar gestures route through existing seams; webview never
+ mutates)** The Pin baseline and Ask-Claude toolbar controls post **intent**
+ messages to the host; **all** mutation goes through the existing machinery — pin
+ via the F6 baseline store (`DiffViewController.pin`), edits via the F4
+ `propose` → `accept`/`applyAgentEdit` (`WorkspaceEdit`) seam with F3 attribution.
+ No divergent edit or baseline path is introduced. The sealed webview never
+ edits the document, sidecar, or baseline directly (INV-20/21/34 hold); the LLM
+ turn and the instruction prompt run host-side (INV-8 untouched).
+- **INV-36 (block-granular preview-selection → source mapping)** The pure render
+ layer emits `data-src-start`/`data-src-end` (source char offsets from
+ `BlockWithRange`) on **every** rendered block, in **both** the on (`renderReview`)
+ and off (`renderPlain`) modes. A preview selection resolves to the **union of
+ the live-source blocks it intersects** (`[min start … max end]`); blocks with no
+ live-source range (deletion-only / proposal blocks) are skipped, and a selection
+ that resolves to no live block falls back to **document** scope. The DOM
+ selection → nearest-`data-src` lookup is the webview's **sole** mapping duty;
+ the offsets and everything downstream (fingerprint, turn, propose) are host-side
+ and testable. The wrapping is deterministic — same inputs → identical HTML
+ (extends INV-22/33).
+- **INV-37 (single adaptive Ask-Claude button; scope-aware)** One toolbar button
+ serves both scopes. A non-empty live-source selection → **Edit Selection**: one
+ F4 proposal over the block-union range. An empty selection → **Edit Document**:
+ one `runEditTurn` over the whole document, its result **diffed into hunks**, one
+ F4 `propose()` per changed hunk. Both scopes call the same host
+ `runEditAndPropose` routine and reuse the F4 **single-range** proposal model
+ (the document case issues multiple single-range proposals — **no new model**).
+
+### 6.2 High-level architecture
+
+```mermaid
+flowchart LR
+ wv["webview header\n☑ Annotations · ⌖ Pin · ✦ Ask Claude (adaptive)"] -- "postMessage{pinBaseline | askClaude(scope,start?,end?)}" --> ctl["trackChangesPreview\n(vscode layer)"]
+ ctl -- "pin(document)" --> base["F6 DiffViewController\nbaseline store (INV-18)"]
+ ctl -- "showInputBox → runEditAndPropose" --> turn["runEditTurn\n(host-side LLM turn)"]
+ turn -- "selection: 1 replacement\ndocument: rewrite" --> ctl
+ ctl -- "selection → 1 propose()\ndocument → diff → N propose()" --> prop["F4 ProposalController\npropose() (single-range model)"]
+ prop -- "onDidChangeProposals" --> ctl
+ base -- "onDidChangeBaseline" --> ctl
+ ctl -- "(baseline, current, spans, proposals)" --> model["renderReview / renderPlain\n(pure)\n+ wrapBlocksWithSrc (NEW)"]
+ model -- "annotated HTML w/ data-src on blocks" --> ctl
+ ctl -- "postMessage{render}" --> wv
+```
+
+The dashed-in NEW pieces are: `wrapBlocksWithSrc` (pure), the
+`runEditAndPropose` host routine with its document-rewrite hunk-diff, the three
+inbound toolbar messages, and the `editor/title` gateway menu. Everything else is
+the existing F6/F4/F3/F10 machinery.
+
+### 6.3 Data model & ownership
+
+**No new persisted artifact** (INV-20). F11 adds only transient on-the-wire
+messages (the `ToolbarMsg` union in §2) and reuses F10's `RenderMsg`. Baseline is
+owned by F6, proposals by F4 (sidecar), attribution by F3 — all untouched. The
+block-offset `data-src` attributes are render-time only (not stored).
+
+### 6.4 Interfaces & contracts
+
+- **`trackChangesModel`** (vscode-free, pure): new
+ `wrapBlocksWithSrc(blocks: BlockWithRange[], renderedPerBlock: string[]):
+ string` (illustrative) — or, more precisely, both `renderReview` and
+ `renderPlain` route their per-block rendered HTML through a shared internal
+ helper that prepends `data-src-start`/`data-src-end` to each block's wrapping
+ element. Plus `diffToHunks(currentText: string, rewrittenText: string):
+ Array<{ start: number; end: number; replacement: string }>` — the pure
+ document-rewrite → per-hunk proposal-range list (vscode-free, deterministic).
+- **`TrackChangesPreviewController`** (vscode layer): handles the three new
+ inbound messages; gains a `runEditAndPropose(document, target: { kind:
+ "range"; start; end } | { kind: "document" }, instruction)` private routine;
+ takes (or reaches) the `DiffViewController` to pin the previewed doc and the
+ edit-turn entry. New test seams as needed (`getLastModel` already exists for
+ asserting marks without webview DOM).
+- **`DiffViewController`** (F6): `pin(document)` is reused as-is (the controller
+ already exposes pinning by document); `cowriting.pinDiffBaseline`'s
+ `package.json` `when` flips from `false` to `editorLangId == markdown`.
+- **`ProposalController`** (F4): `propose(...)` reused unchanged (called once for
+ selection, N times for a diffed document). No signature change.
+- **`AttributionController`** (F3): unchanged (`applyAgentEdit` reused on accept).
+- **Commands / menus (`package.json`):**
+ - `cowriting.showTrackChangesPreview` — added to `editor/title` with
+ `when: editorLangId == markdown` (the minimal gateway). Existing palette +
+ `ctrl+alt+r` kept.
+ - `cowriting.pinDiffBaseline` — `when` flips to `editorLangId == markdown`
+ (no longer orphaned).
+ - `cowriting.editDocument` ("Ask Claude to Edit Document", document-scoped) —
+ new command registered, routed through `runEditAndPropose({kind:"document"})`;
+ available for `#42` to reuse. (The preview's selection-scoped edit is driven
+ by the `askClaude` message carrying webview-resolved offsets, not a command,
+ since the offsets originate in the webview.)
+- **Webview asset** (`media/preview.ts` + `.css`): header gains the two buttons;
+ a `selectionchange` listener updates the Ask-Claude label; click handlers post
+ the `ToolbarMsg` intents; the selection→nearest-`data-src` lookup helper. Stays
+ sealed (nonce'd inline script, CSP unchanged).
+
+### 6.5 Per–Product-Use-Case design
+
+- **PUC-1 (toolbar present):** render the two buttons in the header next to the
+ annotations checkbox; disable Pin + Ask-Claude when `!isAuthorable(document)`.
+- **PUC-2 (adaptive label):** webview `selectionchange` → if the selection is
+ non-empty and within the rendered body, label = "Edit Selection"; else "Edit
+ Document". Pure webview-local state.
+- **PUC-3 (edit selection):** webview resolves selection → `{start, end}` from the
+ nearest `data-src` ancestors → `postMessage{askClaude, selection, start, end}` →
+ host `showInputBox` → `runEditAndPropose({kind:"range", start, end})` →
+ `runEditTurn` → one `propose()` → `onDidChangeProposals` → re-render (inline
+ blue block at the anchor, `#31`).
+- **PUC-4 (edit document):** `postMessage{askClaude, document}` → host input box →
+ `runEditAndPropose({kind:"document"})` → `runEditTurn` over full text →
+ `diffToHunks(current, rewritten)` → one `propose()` per hunk → re-render (N blue
+ blocks).
+- **PUC-5 (pin baseline):** `postMessage{pinBaseline}` → `DiffViewController.pin(
+ previewedDocument)` → `onDidChangeBaseline` → re-render (marks cleared).
+- **PUC-6 (right-click gateway):** `editor/title` entry invokes
+ `cowriting.showTrackChangesPreview` for the tab's document.
+- **PUC-7 (edges):** selection resolving to no live block → document scope;
+ non-authorable → controls disabled; `runEditTurn` failure → existing error path,
+ no proposal; empty doc → Edit Document over empty range (no-op-safe).
+
+### 6.6 Non-functional requirements & cross-cutting concerns
+
+The webview stays **sealed** (INV-21): local assets, strict CSP with a per-load
+nonce, no network; the new inline handlers only read `data-src`/`data-proposal-id`
+and post intent (no eval, no remote, no document mutation). The instruction prompt
+and the LLM turn remain **host-side** — the webview gains **no** LLM, network, or
+credential surface (INV-8 untouched). `diffToHunks` and the block wrapping are
+O(document), run on a host gesture (not per-keystroke), fine at inner-loop scale.
+No telemetry, nothing persisted.
+
+### 6.7 Key decisions & alternatives considered
+
+| Decision | Chosen | Alternatives rejected |
+| --- | --- | --- |
+| **Preview-selection → source mapping granularity** | **Block-level** — pure layer emits `data-src-start/end` from existing `BlockWithRange`; selection → union of intersected live-source blocks. Robust, reuses what exists, ships the full adaptive button now. *(Operator decision, session 0036.)* | **Char-precise** sub-block mapping — needs per-inline-token source offsets markdown-it doesn't reliably give; rendered text ≠ source (syntax stripped) → fragile, risks the whole feature on the hardest part. **Document-only first** — defers the headline adaptive button; punts the risk. |
+| **Document-edit proposal granularity** | **Diff Claude's rewrite into hunks → one F4 proposal per changed hunk** — independent ✓/✗ per change; reuses the single-range model N times (no model change). *(Operator decision, session 0036.)* | **One whole-document proposal** — a single giant blue block, all-or-nothing accept/reject; poor UX for a real rewrite. |
+| **`#43` vs `#41`/`#42` scope** | **`#43` lands a minimal right-click → Open Review Preview gateway** (`editor/title`), so the toolbar surface is reachable end-to-end and its E2E is real; `#41`/`#42` expand the menu set. *(Operator decision, session 0036.)* | **Toolbar only; all menus in `#41`/`#42`** — `#43`'s "a right-click entry opens the preview" acceptance/E2E couldn't be satisfied within `#43`. |
+| **Instruction prompt location** | **Host `showInputBox`** — keeps LLM/secret surface out of the sealed webview; reuses the existing edit-turn flow. | **In-webview text field** — pushes prompt handling toward the sandbox; no benefit. |
+| **Pin button target** | **The previewed document** (`DiffViewController.pin(document)`) — the preview knows its bound doc. | **`activeTextEditor`-based command** — may not point at the previewed doc; the source of the orphan. |
+| **Pin confirmation** | **No confirm** — matches the existing command; re-pinning recovers. | **Confirm dialog** — friction for a routine, recoverable gesture. |
+
+### 6.8 Testing strategy
+
+- **Unit (vitest, vscode-free):** `data-src-start/end` present and correct on every
+ block for **both** `renderReview` and `renderPlain` (offsets equal the
+ `BlockWithRange` ranges; determinism — same inputs → identical HTML);
+ `diffToHunks` over fixtures — a single-hunk rewrite → one range; a multi-hunk
+ rewrite → the expected disjoint ranges with correct replacements; an unchanged
+ rewrite → zero hunks; whole-document replacement → one full-range hunk.
+- **Host E2E (`@vscode/test-electron`, no LLM, extends the F10 suite):** open a
+ markdown fixture → `cowriting.showTrackChangesPreview`. Simulate
+ `{type:"pinBaseline"}` → `getLastModel` shows cleared change-marks + advanced
+ epoch. Simulate `{type:"askClaude", scope:"selection", start, end}` with a
+ stubbed edit turn → exactly **one** proposal over the resolved range, anchored
+ inline. Simulate `{type:"askClaude", scope:"document"}` with a stubbed
+ multi-hunk rewrite → **N** proposals matching the hunks. Invoke the
+ `editor/title` gateway command → panel opens. Non-authorable document → toolbar
+ edit controls disabled (asserted via the model/flags the host exposes). The
+ webview DOM, real button clicks, the `selectionchange` label flip, and the
+ selection→`data-src` lookup are **not** E2E-asserted (sealed sandbox) — manual
+ smoke.
+- **Live smoke (manual — `docs/MANUAL-SMOKE-F11.md`):** open a markdown doc; open
+ the review preview; confirm the three header controls; select a paragraph →
+ button reads "Edit Selection", click → enter instruction → a blue ✓/✗ block
+ appears at that paragraph; clear the selection → button reads "Edit Document",
+ click → instruction → several blue blocks appear; click **Pin baseline** → marks
+ clear, `Since` label updates; right-click the tab → **Open Review Preview** opens
+ the panel; verify light/dark theming and that `git status` shows nothing
+ unexpected.
+
+### 6.9 Failure modes, rollback & flags
+
+A selection that resolves to no live-source block → **Edit Document** scope (never
+an error). `runEditTurn` failing → existing error handling, no proposal created,
+preview unchanged. `diffToHunks` producing zero hunks (rewrite == current) → no
+proposals, a brief "no changes proposed" host notice. Webview disposed mid-gesture
+→ the host routine completes against the document; the next open re-renders.
+**No feature flag** — the toolbar controls are additive UI; nothing persists.
+Rollback is reverting the PR with **zero** data migration (nothing persisted; the
+F6 baseline, F4 sidecar, F3 attribution data are untouched; the unhidden pin
+command and the gateway menu simply disappear).
+
+---
+
+## 7. Delivery Plan
+
+### 7.1 Approach / strategy
+
+One planning-and-executing session (F11 = `#43`), plan written just-in-time from
+this spec — the F2–F10 precedent. Host-E2E tier (a VS Code extension has no
+browser/deploy stage); no LLM in CI (edit turns stubbed). The webview's visual
+rendering, the adaptive label, and the selection→source DOM lookup are verified by
+the manual smoke; the automated seams are the pure block-wrapping + `diffToHunks`
+model and the host's message→seam wiring.
+
+### 7.2 Slicing plan
+
+- **SLICE-1 — Pin baseline button + reachability.** Webview header **Pin
+ baseline** button → `{type:"pinBaseline"}` → host `DiffViewController.pin(
+ previewedDoc)`; unhide `cowriting.pinDiffBaseline` (`when: editorLangId ==
+ markdown`). Host E2E: pin message clears marks. *(Immediate win — homes the
+ orphaned command.)*
+- **SLICE-2 — Block-offset emission.** Shared pure helper wrapping each block with
+ `data-src-start/end` in `renderReview` **and** `renderPlain`; vitest for both
+ modes + determinism. No UI yet. (INV-36 data layer.)
+- **SLICE-3 — Edit Document button + hunk path.** Webview **Ask Claude to Edit
+ Document** button (no-selection state) → `{type:"askClaude", scope:"document"}`;
+ host `runEditAndPropose({document})` → `runEditTurn` → `diffToHunks` → N
+ `propose()`; register `cowriting.editDocument`; vitest for `diffToHunks`; host
+ E2E for the N-proposal path. (INV-37 document half.)
+- **SLICE-4 — Adaptive Edit Selection.** Webview `selectionchange` label flip +
+ selection→nearest-`data-src` resolution → `{type:"askClaude", scope:"selection",
+ start, end}`; host single-range `propose()`. Host E2E for the selection message →
+ one anchored proposal. (INV-37 selection half; INV-36 consumer.)
+- **SLICE-5 — Gateway, edges, tests & docs.** `editor/title` → Open Review Preview
+ gateway; non-authorable disabling; host E2E (gateway opens panel; controls
+ inert on non-authorable); `docs/MANUAL-SMOKE-F11.md`; README F11 section.
+
+E2E are first-class plan tasks (handbook §9/§4); this app's required tier is host
+E2E (the F2–F10 precedent).
+
+### 7.3 Rollout / launch plan
+
+Non-shippable (no marketplace publish). "Done" = `#43` acceptance: the preview
+toolbar hosts the annotations checkbox + a Pin baseline button + a single adaptive
+Ask-Claude button (Edit Selection ⇆ Edit Document) that route through the existing
+F4/F3/F6 machinery; 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.
+
+### 7.4 Risks & mitigations
+
+| Risk | Mitigation |
+| --- | --- |
+| Block-level selection feels coarse vs the editor's char-precise Edit Selection | Locked v1 decision (§6.7); a rendered surface is naturally block-grained; char-precise is a deferred follow-up if the coarseness bites |
+| `data-src` attributes perturb markdown-it output or the F10 proposal/diff rendering | Wrapping is applied at the block boundary (outside inline parsing); covered by determinism + both-mode unit tests; per-block `try/catch` error chip (F7) on render failure |
+| `diffToHunks` produces awkward hunk boundaries on a large rewrite | Pure + unit-tested over fixtures; hunks are line/block-aligned; worst case is more/fewer blocks, all independently ✓/✗-able — never wrong, just granular |
+| Selection inside a deletion/proposal block has no live-source range | Falls back to Document scope by design (INV-36); manual-smoke verified |
+| The webview selection→`data-src` lookup isn't E2E-testable (sealed) | The host half (offsets→fingerprint→propose) is E2E'd via simulated messages; the DOM lookup is the only manual-smoke-only seam, kept deliberately thin |
+| Unhiding pin / adding `editDocument` widens the command surface | Both guard on `editorLangId == markdown`; both route through existing seams; no new model or persisted state |
+
+---
+
+## 8. Traceability matrix
+
+| Requirement (`#43`) | Use case | Design | Slice |
+| --- | --- | --- | --- |
+| Pin baseline button in the preview toolbar | PUC-5 | INV-35, §6.4 (`DiffViewController.pin`) | SLICE-1 |
+| Resolve the orphaned `pinDiffBaseline` reachability | PUC-5 | §6.4 (`when` flip) | SLICE-1 |
+| Single adaptive Ask-Claude button (Selection ⇆ Document) | PUC-2/3/4 | INV-37, §6.2 | SLICE-3/4 |
+| Preview-selection → source range mapping (block-level) | PUC-3 | INV-36, §6.7 | SLICE-2/4 |
+| Edit Document path (new whole-document edit) | PUC-4 | INV-37, §6.5 (hunk diff) | SLICE-3 |
+| Edits route through existing F4/F3 (no divergent path) | PUC-3/4 | INV-35, §6.4 | SLICE-3/4 |
+| Right-click entry opens the preview (minimal gateway) | PUC-6 | §6.4 (`editor/title`) | SLICE-5 |
+| Controls only active for supported (authorable) docs | PUC-1/7 | §6.5 | SLICE-5 |
+| Sealed webview, no document mutation / LLM surface | — | INV-21/35, §6.6 | all |
+| No new edit/attribution/proposal model | — | §1.7, INV-37 | all |
+| Unit + host E2E + right-click-opens-preview coverage | — | §6.8 | SLICE-1..5 |
+
+## 9. Open Questions & Decisions log
+
+- **RESOLVED (session 0036, operator):** preview-selection → source mapping =
+ **block-level** (`data-src` attributes from `BlockWithRange`; union of
+ intersected blocks); document edit = **diffed into per-hunk F4 proposals**;
+ `#43` **lands a minimal right-click → Open Review Preview gateway** (`#41`/`#42`
+ expand the menus).
+- **RESOLVED (this spec, autonomous):** instruction prompt = **host `showInputBox`**
+ (LLM/secrets stay out of the webview); Pin targets the **previewed document**
+ (`DiffViewController.pin`); **no confirmation** on pin (matches existing); a new
+ `cowriting.editDocument` command is registered for `#42` reuse; `pinDiffBaseline`
+ is unhidden (`editorLangId == markdown`).
+- **OPEN → later:** **char-precise** sub-block selection mapping (deferred — block
+ granularity is v1); the **richer `#41`/`#42` menu sets** (this lands only the
+ minimal gateway); preview→source **scroll-sync** (`#32`); whether a large
+ document rewrite should cap/segment its hunks (only if real rewrites prove
+ noisy); the repo rename to `vscode-markdown-cowriting-plugin` (`#35`, deferred).
+
+## 10. Glossary & References
+
+- **Preview toolbar** — the review preview's header row: the annotations on/off
+ checkbox (existing) plus F11's Pin baseline and adaptive Ask-Claude buttons.
+ **Adaptive Ask-Claude button** — one button reading "Edit Selection" (non-empty
+ preview selection) or "Edit Document" (none). **Block-level selection mapping** —
+ resolving a rendered-preview selection to the union of source blocks it
+ intersects, via `data-src-start/end` attributes emitted by the pure render
+ layer. **Hunk-diffed document edit** — Claude's whole-document rewrite split
+ into changed hunks, each surfaced as its own F4 proposal. **Pin baseline** — F6's
+ `DiffViewController.pin` applied to the previewed document. **Gateway** — a
+ right-click entry that opens the preview (this feature lands the minimal one;
+ `#41`/`#42` expand them).
+- Feature `#43` (F11) · Epic `#1` · builds on F3 `#6`, F4 `#12`, F6 `#17`/`#19`,
+ F7 `#21`/`#22`, F9 `#27`, F10 `#29`/`#31` · coexists with `#41`/`#42` · parent
+ specs `coauthoring-inner-loop.md`, `coauthoring-attribution.md`,
+ `coauthoring-propose-accept.md`, `coauthoring-diff-view.md`,
+ `coauthoring-rendered-preview.md`, `coauthoring-interactive-review.md` · capture
+ session 0035 · lineage `ben.stull/rfc-app#48`.