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>
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 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>
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>
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>