Commit Graph

95 Commits

Author SHA1 Message Date
benstull 3e7ad58247 Surface polish: batched Minor findings from the native-surfaces final review (#71) (#75) 2026-07-03 03:20:23 +00:00
benstull 7583165354 fix(#70): INV-5 — reject after an interior tweak restores the retained original (PR #73)
Reject on an optimistically-applied proposal now restores the retained original even after interior tweaks: appliedSpans tracked-range fallback (pure shiftTracked, boundary-straddle distrust, close-clears, rebuild-only resync), honest hard failure with a Discard action, INV-16 read-only guards, rejectAll {reverted,skipped} reporting on all batch surfaces, CodeLens reachability at the tracked span. 312 unit + 94/5 host E2E.

Closes #70.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 01:19:46 +00:00
benstull 935fcc35ee Native surfaces migration — evolve the extension onto VS Code's native review surfaces (9-task plan, spec v0.2.1) (#72) 2026-07-02 23:09:37 +00:00
BenStullsBets c3cecdfaa2 fix: editor renders standalone committed deletions neutral; doc/comment cleanups (final review)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 18:10:25 -07:00
BenStullsBets 02fbe9c77e chore: sweep retired semantic-diff markup; full suite green
- Delete dead `authorBadge` function (no callers; grep-verified)
- Merge adjacent duplicate imports from trackChangesModel in test file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 17:57:16 -07:00
BenStullsBets ae19df78fb feat: overlap renders stacked author marks (insert + delete) in both panes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 17:50:39 -07:00
BenStullsBets 7b59c324d5 feat: Task 7 — decorateCommitted fills committed author-colored changes in the editor (reverses INV-32)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 17:40:15 -07:00
BenStullsBets c579f67a43 test(e2e): assert author-colored track changes render in the preview
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 14:37:29 -07:00
BenStullsBets 2955e8d929 feat: color pending proposal blocks by author (Claude blue/purple)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 14:17:40 -07:00
BenStullsBets 44ef0a21db fix: render added blocks via markdown-safe sentinel path emitting cw-ins-* (heading/list structure preserved)
Defect: an ADDED block was colored via `render(wordDiffByAuthor("", raw, ...))`, which
wrapped the whole raw markdown in an inline `<ins>` BEFORE markdown-it parsed it — so
`## X` rendered as literal text, not `<h2>`. Same structural bug for list, blockquote,
and thematic-break blocks.

Fix (three-part, pure / vscode-free):
1. `blockContentStart(raw)`: new helper returning the offset past block-level markers
   (ATX headings `## `, unordered/ordered list markers, blockquotes). Open sentinels must
   not precede these markers or markdown-it cannot recognise the block construct.
2. `injectSentinels`: clamp the open-sentinel lower-bound to `blockContentStart(raw)`,
   so for `## New Section` the sentinel lands AFTER `## ` (at position 3) instead of at
   position 0, letting the heading parse correctly.
3. `sentinelsToSpans` / `colorByAuthor`: add optional `kind: "by" | "ins" = "by"`
   parameter so the sentinel path can emit `cw-ins-{author}` for added blocks while all
   existing `colorByAuthor`/`cw-by-*` call-sites are unchanged.
4. `renderReview` ADDED block path: `render(wordDiffByAuthor("", raw, ...))` →
   `colorByAuthor(raw, blk.start, landedSpans, render, "ins")`. The CHANGED non-atomic
   prose path (`wordDiffByAuthor(before, after, ...)`) is untouched — it correctly keeps
   the block markers at column 0 of the unchanged prefix.
5. `renderReviewOp`: removed the now-vestigial `colored` parameter (was always `render`
   after the added-block path moved to `changedHtml`); unchanged blocks now call
   `render(op.block.raw)` directly.

Regression test added: "renderReview: an added heading block renders as a heading AND
is author-colored as an insertion" — RED on old code, GREEN after fix. All 259 tests
pass; typecheck clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 14:09:19 -07:00
BenStullsBets 08557827da feat: author-colored changed/added blocks in review; unchanged blocks render plain 2026-06-26 13:54:24 -07:00
BenStullsBets 0ad040711f feat: authorAt + wordDiffByAuthor pure helpers (author-colored word diff w/ adjacency del) 2026-06-26 13:34:12 -07:00
benstull e671c4bf03 fix(#59): disable claude-code IDE auto-connect to stop spurious macOS Apple-Events prompt (#68) 2026-06-26 20:12:24 +00:00
benstull 7b98249286 F12: inline editable proposed-change diff in the Markdown editor + Accept/Reject control parity (#64) (#66) 2026-06-26 15:28:14 +00:00
benstull 7e42d115c0 feat(ux): unify Ask-Claude-to-Edit on a split-below multi-line webview (#65) 2026-06-26 13:50:31 +00:00
benstull 9432300e3c feat(ux): unify "Ask Claude to Edit" + inline prompt at selection; fix keybindings (#62) 2026-06-26 12:11:43 +00:00
benstull 644885c6ec #60: live turn progress (activity line + token count + OutputChannel stream + cancel) (#61)
Surface Claude live output/progress during the asking-Claude status: notification activity line + token count, a Cowriting: Claude OutputChannel streaming assistant text, and a cancellable turn. Pure turnProgress reducer + runEditTurn onProgress/AbortSignal (vscode-free) + both call sites. INV-43..47.

Fixes #60
2026-06-26 11:53:10 +00:00
Ben Stull e53f0c30ad #54: make undo-dependent E2E resilient to environments where undo is broken
`executeCommand("undo")` is non-functional in some headless `.vscode-test`
instances (it does not restore the buffer — see #54's diagnosis), which
false-fails every undo-dependent host E2E and turns `main`'s E2E red there even
though the product code is fine.

Add a RUNTIME preflight probe (`undoCapable.ts`): edit a scratch buffer, undo,
and report whether the buffer was actually restored (memoized per run). The #38
undoMarks suite gates on it via `suiteSetup` — running normally where undo works
(full coverage preserved, real regressions still caught) and skipping with a LOUD
console warning where it doesn't (no false red, no silent loss — the skip is
logged and shows as `pending`).

This fixes the "main E2E red" symptom. The underlying headless-undo limitation is
documented in #54; #40's undo-behavior coverage runs wherever undo works.

Test-infra only — no product code changed. Verified: with undo broken locally the
#38 suite skips (1 pending) and both E2E passes exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 09:16:40 -07:00
Ben Stull ba7623f813 #33: harden author-coloring PUA sentinels against intra-emphasis markdown
The F9/F10 author-coloring technique injects paired PUA sentinels into prose
source at span offsets, renders, then maps sentinels → cw-by-* spans. Two failure
modes when a span boundary met emphasis markup (characterized in session 0032):
- CASE1: a boundary strictly inside a delimiter run (a**b**c, between the two *)
  split `**`, breaking markdown-it's delimiter pairing (stray <em></em>, raw **).
- CASE3: a boundary inside an emphasis run (**bold** span covering **bo) rendered
  the emphasis but MISNESTED span/element (<strong>bo</span>ld</strong>).

Token-aware fix (both, per the issue #33 comment):
- injectSentinels: clamp any sentinel offset that lands strictly inside a
  delimiter run (* _ ~ `) to the run's start, so a sentinel never splits a run
  (CASE1). Delimiters are invisible once rendered, so this only shifts the colored
  boundary across markup. Drop spans that clamp to empty.
- sentinelsToSpans: replace the naive global split/join with a walker over the
  rendered HTML that emits the cw-by-* span only around TEXT runs — closing it
  before any <tag> and reopening after — so a span is always well-nested within
  inline elements (one span segment per text run, CASE3). Tags are copied
  verbatim with any stray sentinel stripped (no Private-Use-Area char leaks).

Pure, vscode-free, deterministic (INV-33). No regression: existing colorByAuthor /
renderReview cases stay green; the common no-emphasis case is byte-identical.

222 unit + 74/5 host E2E green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 08:41:31 -07:00
Ben Stull fdd743490d #48: pinning the baseline leaves the review panel fully un-annotated
A pinned baseline with no changes since should read as a clean starting point,
but the F10 on-render still author-colored every block (colorByAuthor), painting
the whole document green/blue right after a pin. Now, when the baseline reason is
"pinned" and there are zero changes since (every diffBlocks op unchanged), the
on-render is fully clean — no change marks and no authorship coloring — while the
data-src block mapping (INV-36) and any pending proposals (review actions, not
annotations) are kept.

Scoped to the PIN specifically, not all zero-diff: a baseline advanced by a
machine-landing (accept) is also zero-diff but keeps its authorship coloring so
accepted Claude text stays blue (F10 INV-33). renderReview takes a `pinned`
option; the controller passes baseline.reason === "pinned" from both refresh and
the renderHtmlFor test seam.

- trackChangesModel.ts: renderReview gains the `pinned` RenderOption; when pinned
  + zero-diff, blocks render plain (no colorByAuthor).
- trackChangesPreview.ts: pass { pinned } through refresh + renderHtmlFor.
- unit: pinned+zero-diff → no cw-by-*; pinned+zero-diff still shows proposals;
  zero-diff WITHOUT pin (machine-landing) keeps coloring (INV-33); real changes
  after a pin re-color.
- s48PinClean host E2E: type → colored; pin → clean; edit → annotations return.

218 unit + 74/5 host E2E green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 08:28:52 -07:00
Ben Stull c94b9ccfe7 #46 (SLICE-3, accept): Accept all pending proposals in one gesture
Document-edit flow SLICE-3 — completes reach→review→accept
(specs/coauthoring-document-edit-flow.md §7.2, INV-42). A single "Accept all"
gesture applies every pending proposal on the current document through the
existing F4 acceptById seam (block proposals take the INV-40 word-precise path
automatically), in descending anchor order so an earlier accept never invalidates
a later one, skipping (never force-applying) proposals that can't anchor and
reporting applied-vs-skipped. Batched application of the existing accept path — no
new mechanism; the webview posts intent only (INV-35). No confirmation dialog
(undo restores).

- proposalController.ts: acceptAllProposals(document) → {applied, skipped}
  (descending order, orphan-skip); accept/acceptById gain a silent opt so the
  batch suppresses N per-proposal orphan warnings in favour of one report.
- trackChangesPreview.ts: ToolbarMsg += {type:"acceptAll"}; handleWebviewMessage
  routes it to a public acceptAll(document) that batches + reports.
- extension.ts + package.json: cowriting.acceptAllProposals command (active doc,
  markdown-gated palette entry) for the non-webview path.
- media/preview.ts + shellHtml: "✓✓ Accept all" toolbar button, posting the
  intent, shown only with ≥2 pending proposals (authorable, on-state).
- trackChangesModel.ts: diffToBlockHunks now emits one block-aligned hunk per
  CHANGED block even when changed blocks are ADJACENT (treats changed blocks as
  1:1 anchors alongside unchanged ones; gap-spans only cover add/remove runs
  between anchors) — fixes adjacent changed blocks collapsing into one proposal.
- f12Accept host E2E (apply-all reconstructs; orphan skip + report; single
  proposal; command registered/gated); MANUAL-SMOKE-F12 §3.

214 unit + 73/5 host E2E green. Completes the document-edit-flow cluster
(#42 reach + #47 review + #46 accept).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 08:15:55 -07:00
Ben Stull 2f6008ba2b #47 (SLICE-2, review): per-block document proposals + word-precise accept
Document-edit flow SLICE-2 (specs/coauthoring-document-edit-flow.md §7.2,
INV-39/40/41 — P1 "too much to review"). A whole-document rewrite now proposes
ONE F4 proposal per CHANGED BLOCK (the unit a human reviews), but accepting a
block reconciles attribution at WORD granularity (the unit F3 records). Block =
decision unit; word = attribution unit. Supersedes INV-37's per-word cut for
document edits; selection edits unchanged.

- trackChangesModel.ts: new pure diffToBlockHunks(current, rewritten) — block-key
  alignment (reusing diffArrays/diffBlocks keying): an isolated changed block →
  one block-aligned hunk → the rewritten block raw (a code/mermaid fence is one
  atomic whole-fence hunk, INV-23); insert/delete runs → one gap-span hunk over
  the inter-anchor region (separators included) so reconstruction stays exact; a
  zero-width gap-span is anchored (INV-41). Also split diffToHunks into the raw,
  un-anchored wordEditHunks + the anchoring wrapper (the anchoring could grow an
  insertion to overlap an adjacent hunk, corrupting a batch apply — a latent bug
  that only surfaced once hunks are applied as a batch).
- trackChangesPreview.ts: runEditAndPropose document branch uses diffToBlockHunks
  and tags each proposal granularity:"block".
- model.ts / proposalModel.ts: additive optional Proposal.granularity
  ("block"|"single"; absent ⇒ single, back-compat — no migration).
- proposalController.ts: accept of a block proposal runs an intra-block word
  sub-diff (wordEditHunks, disjoint) and applies one applyAgentEdit per changed
  run, descending offset — only the words Claude changed land Claude-attributed;
  unchanged spans keep prior authorship (INV-40).
- Tests: diffToBlockHunks unit (reconstruction + fence atomic + add/remove);
  f12Review host E2E (M blocks→M proposals, unchanged→none, fence atomic, INV-40
  attribution, INV-41 insertion accept); updated the f11 document-path E2E to
  per-block (INV-39 supersedes INV-37); MANUAL-SMOKE-F12 §2.

Seam note: pendingEdits.matchEvent resolves one registration per change event, so
INV-40's per-run attribution is sequential applyAgentEdit calls (N undo steps),
not one multi-replace WorkspaceEdit — see transcript Deferred decisions.

214 unit + 69/5 host E2E green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 08:01:16 -07:00
Ben Stull 9c3770d26a #42 (SLICE-1, reach): selection-aware Ask-Claude from editor body + tab
Document-edit flow SLICE-1 (specs/coauthoring-document-edit-flow.md §7.2,
INV-38): make "Ask Claude to Edit" reachable from the editor body AND the
editor tab, selection-aware — a selection routes to editSelection, no
selection to editDocument — both gated to markdown/authorable docs, both
flowing through the single runEditAndPropose path (no divergent edit code).

- package.json: add editSelection + editDocument to editor/context (selection-
  aware, markdown + file/untitled) and editor/title/context (selection-aware,
  resourceLangId == markdown). Markdown-gate the existing editor/context
  editSelection entry to match (its command handler is unchanged; the palette
  still reaches any authorable doc — see transcript Deferred decisions).
- trackChangesPreview.ts: cowriting.editDocument accepts the clicked tab's
  resource Uri (editor/title/context), targeting THAT document (opening it if
  needed) and falling back to the active editor when invoked with no arg —
  mirroring showTrackChangesPreview's #41 clicked-doc resolution.
- E2E (test/e2e/suite/f12Reach.test.ts): menu entries present, selection-aware,
  markdown-gated; editDocument(uri) targets the tab doc not the active editor;
  no-arg falls back to the active editor.
- docs/MANUAL-SMOKE-F12.md: SLICE-1 reach smoke steps.

208 unit + 65/5 host E2E green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 07:26:14 -07:00
Ben Stull 695b51f903 feat(#41): Open Cowriting Review Panel from markdown file/tab right-click
The review preview (`cowriting.showTrackChangesPreview`) was only reachable from
the command palette and `ctrl+alt+r` — neither where a writer's hand naturally
goes. Add the obvious right-click entry points to the plugin's central surface:

- `explorer/context` + `editor/title/context` menu items gated on
  `resourceLangId == markdown`, both invoking `showTrackChangesPreview`.
- The command now resolves the *clicked* document: it opens the passed Uri if
  it isn't already an open document (the Explorer case), instead of falling back
  to the active editor. No-arg invocation (palette / keybinding) is unchanged.
- Retitle the command to "Open Cowriting Review Panel" so the menus read the
  operator's wording (palette shows "Cowriting: Open Cowriting Review Panel").

E2E: clicked-doc resolution (open + not-yet-open + no-arg fallback), both menu
contributions present + markdown-gated, title, and keybinding unchanged.

Closes #41.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 16:04:02 -07:00
Ben Stull 47cc733026 fix(f11): address code review — insertion anchoring, turnId, accept-all coverage (#43)
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>
2026-06-12 14:16:49 -07:00
Ben Stull 1564ef562b feat(f11): SLICE-5 — gateway, non-authorable disabling, docs (#43)
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>
2026-06-12 13:57:28 -07:00
Ben Stull 03b61ed43e feat(f11): SLICE-4 — single adaptive Ask-Claude button + selection mapping (#43, INV-37)
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>
2026-06-12 13:53:14 -07:00
Ben Stull 0d1a5635cb feat(f11): SLICE-3 — Edit Document button + per-hunk proposal path (#43, INV-37)
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>
2026-06-12 13:51:07 -07:00
Ben Stull 1ef9451e89 feat(f11): SLICE-2 — block-level data-src emission (#43, INV-36)
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>
2026-06-12 13:45:02 -07:00
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
Ben Stull 469c8c11fc fix #38: don't mis-attribute undo/redo as fresh human authorship in the preview
Undo in the editor rendered wrong marks in the F10 review preview: restored
baseline text (and reverted Claude text) was colored as human-authored. Root
cause — attributionController.onDidChange attributed every non-seam document
change to currentAuthor() (always human) and ignored e.reason, so an undo/redo
that re-inserts text created a fresh human span over it. renderReview is pure;
the wrong marks came from these false author spans.

Fix: on e.reason === Undo|Redo, reconcile span geometry but do NOT attribute the
re-inserted chars — an undo is history navigation, not authorship, so restored
text stays neutral (unattributed) rather than falsely claimed by the human.
applyChange gains an `attributeInserted` flag (default true; false on undo/redo);
seam matching is also skipped on undo/redo (the seam only applies forward edits).

Restored text becomes neutral rather than recovering its exact prior provenance
(e.g. undoing a deletion of Claude's text shows it unattributed, not blue) —
perfect restoration would need an attribution history stack synced to the editor
undo stack (fragile due to edit coalescing); filed mentally as a follow-up. The
neutral behavior removes the misleading marks, which is the reported defect.

Tests: E2E reproduces the mid-edit-undo case (buffer stays dirty so the disk-sync
guard doesn't mask it) and asserts restored text is unattributed; +3 unit tests
for the geometric-only applyChange path. 197 unit + 50 E2E green; typecheck clean.

Closes #38

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 03:37:22 -07:00
Ben Stull 3520397e41 F6 #34: delete the dead two-pane diff-view UI, keep the baseline data layer
F10 (#29) made the rendered preview the single review surface and hid the F6
two-pane vscode.diff view (command + ctrl+alt+d set when:false). This removes
that now-unreachable view code:

- DiffViewController: drop toggle/findDiffTab/epochLabel/isDiffOpen, the
  `cowriting-baseline:` TextDocumentContentProvider + BASELINE_SCHEME + baselineUri
  + the content-provider change emitter, and the toggleDiffView command. The
  baseline DATA layer is fully intact — ensureBaseline/advance/pin/capture,
  getBaseline, baselineFilePath, onDidChangeBaseline, persistence (INV-19), and
  the machine-landing auto-advance (INV-18) that F7/F10 consume.
- package.json: remove the toggleDiffView command, its commandPalette entry, and
  the ctrl+alt+d keybinding.
- E2E: diffView suite keeps the baseline-data-layer tests, drops the two-pane
  view tests; the F10 + no-workspace suites assert toggleDiffView is now absent
  (was: declared-but-hidden).

Deliberate deviation from the issue's literal acceptance: pinDiffBaseline is
KEPT. The canonical Solution Design (coauthoring-interactive-review.md §6.7)
scopes the removal to the two-pane VIEW only ("keep the controller + baseline
store"); pin() lives in the baseline lifecycle (§6.4), never touches vscode.diff,
and is exercised by live F7 baseline-reset tests. Where the P3 capture draft and
the approved spec conflict, the spec wins (documentation-leads-automation).

194 unit + 49 E2E green; typecheck + build clean. No F7/F10 behavior change.

Closes #34

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 02:10:14 -07:00
Ben Stull 34fa31311c F10 #31: render resolved proposals inline at their anchor block
In the F10 review preview, renderReview now emits each pending proposal whose
anchor resolves as a cw-proposal block immediately after the current-side block
its anchorStart falls in — answering "what is Claude proposing, and where?" by
position — instead of appending all proposals as trailing blocks. Proposals in
the same block are ordered by anchorStart then id (deterministic, INV-33); a
proposal whose anchor doesn't resolve (or precedes all blocks) still trails as a
cw-proposal-unanchored block, never dropped (INV-34).

This implements the inline-at-anchor placement the graduated F10 Solution Design
already specified (coauthoring-interactive-review.md §2/§6.2); the trailing-block
behavior shipped in #29 was a recorded v1 deferral.

Unit: mid-document placement, two proposals in distinct blocks, same-block
ordering determinism, anchored-before-trailing, mixed-set determinism.
E2E: the F10 propose test now asserts the proposal renders before the following
block (in place); accept still lands + clears.

194 unit + 51 E2E green; typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 01:31:52 -07:00
Ben Stull cdeb41ede4 F10 SLICE-4: host E2E for interactive review (open/toggle/propose→accept→reject/clean-editor/hidden-F6/status); update obsolete F9/toggle suites (#29)
Adds test/e2e/suite/f10Review.test.ts covering the F10 flow: open preview
(mode "on", fresh baseline all-unchanged), type → cw-by-human span, propose →
cw-proposal block with ✓/✗, accept → lands + baseline advances + block clears,
reject → vanishes + doc untouched, toggle off → renderPlain has no cw- marks,
status-bar PUC-6 (indicator with no panel, hidden once opened), and the
clean-editor INV-32 facts (retired toggleAttribution, F6 ctrl+alt+d hidden).

Rewrites the obsolete F9 authorship-mode test as an F10 review test
(cw-by-claude in the on-state render; getMode defaults to "on"). Updates
attribution.test.ts (drops the retired isVisible/toggleAttribution toggle,
asserts the toggle is gone) and noWorkspace.test.ts (toggleAttribution +
acceptProposal/rejectProposal are retired, preview-only — INV-32).

Honesty fix in the status-bar seam: hideStatus() clears statusItem.text so
statusText() reports undefined when the indicator is hidden (it previously
returned its stale last value after a panel opened).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 00:31:54 -07:00
Ben Stull bda394ea4e F10 SLICE-3: remove superseded public renderAuthorship (salvaged into colorByAuthor) (#29)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 00:16:57 -07:00
Ben Stull bcacbf062d F10 SLICE-2: fix renderReview author-coloring for duplicate blocks; tidy renderReviewOp + escape proposal id (#29)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 00:13:54 -07:00
Ben Stull d59f22b22f F10 SLICE-2: add ProposalView + renderReview combined on-state render (#29)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 00:09:45 -07:00
Ben Stull 0311ad89ba F10 SLICE-2: add renderPlain off-state (#29)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 00:08:31 -07:00
Ben Stull ebe3d4bab5 F10 SLICE-2: extract colorByAuthor from renderAuthorship sentinels (#29)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 00:08:10 -07:00
Ben Stull 3b45e784c1 test(f7.1): host E2E — changed flowchart augments emitted source (#22)
Adds a renderHtmlFor test seam to TrackChangesPreviewController.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:34:42 -07:00
Ben Stull 6702490497 feat(f7.1): render augmented mermaid diff in changed-block branch (#22)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:33:12 -07:00
Ben Stull e938572ebc feat(f7.1): sequence participant/message diff + rect emission (#22)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:32:08 -07:00
Ben Stull 96931f245d feat(f7.1): sequence-diagram parser (#22)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:31:44 -07:00
Ben Stull 1454c7792a feat(f7.1): flowchart node/edge diff + styling emission (#22)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:31:09 -07:00
Ben Stull 78352c2922 feat(f7.1): flowchart parser (#22)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:30:32 -07:00
Ben Stull d8ce4f12e1 feat(f7.1): mermaid diff dispatcher + type detection (#22)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:29:36 -07:00
Ben Stull 4212d90055 test(f9): host E2E — authorship mode marks Claude's landed span
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 14:39:54 -07:00
Ben Stull 95eaf78891 feat(f9): renderAuthorship — inline author spans + atomic fence badges (INV-26/27/28)
PUA sentinel injection through markdown-it; adjacent-span ordering handled;
code/mermaid fences atomic with a block-level author badge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 14:36:19 -07:00
Ben Stull 82d7c52983 feat(f9): splitBlocksWithRanges — block offsets aligned to the source
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 14:34:40 -07:00
Ben Stull 5030cad612 test(f8): host E2E — out-of-folder file + untitled + in-workspace regression; folder-less suite
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 13:14:45 -07:00