F12: inline editable proposed-change diff in the Markdown editor + Accept/Reject control parity (#64) #66

Merged
benstull merged 12 commits from s58-inline-editor-diff into main 2026-06-26 15:28:15 +00:00
Owner

Summary

F12 (#64): show Claude's proposed change inline in the Markdown editor — optimistically applied (editable, tinted insertions + struck-red non-editable deletion hints) — with Accept ▾ / Reject ▾ (and Accept-all / Reject-all) reachable from both the editor (CodeLens + QuickPick) and the review webview. Reverses INV-10 (propose no longer leaves the buffer untouched) and INV-32 (editor no longer unconditionally clean).

Spec: coauthoring-inline-editor-diff.md v0.1.1 (graduated, session 0058). Built in session 0059.

What changed (INV-48…54)

  • Optimistic apply + re-anchor (INV-48): propose writes the replacement into the buffer via the F3 seam with a new landBaseline:false opt (no baseline advance → stays pending); stores Proposal.original + re-fingerprints the anchor to the applied text (so resolve() keeps finding it). Reload-safe: original captured once.
  • Single diff source (INV-49): pure decorationPlan feeds the editor; the webview HTML uses the same wordEditHunks diff.
  • Render-once (INV-50): renderReview diffs against landedTextOf (current minus pending) so a proposed change renders once (as a proposal), not also as a landed diff; author spans + toolbar summary reconciled too.
  • Finalize / revert in place (INV-51): accept = advance baseline + clear (no re-apply); reject = restore original.
  • Decoration roles (INV-52): deletion hints are non-editable after content; insertion tints over real buffer text.
  • Control parity (INV-53): Accept/Reject/Accept-all/Reject-all from editor (CodeLens+QuickPick) and webview (buttons+dropdown); ✓/✗ glyphs superseded; new cowriting.rejectAllProposals.
  • Save persists pending (INV-54).

New EditorProposalController owns the editor surface; ProposalController gains optimistic-apply / finalize / revert / rejectAll.

Tests

  • 249 unit (pure: model, proposalModel, decorationPlan, renderReview reconciliation + coloring alignment).
  • 87 + 5 host-E2E (seam landBaseline, finalize/revert in place, editor surface, control parity, reload-safety, plus existing F4/F10/F11/F12 suites updated for the INV-10 reversal).
  • Test-infra: pinned --user-data-dir to a short /tmp path so the host-E2E harness launches from a worktree (macOS socket-length fix).

Review

A final adversarial review caught one CRITICAL (revert target clobbered on save+reload) — fixed with a reload-safe guard + regression E2E — and a minor toolbar double-count — fixed via the reconciled landedTextOf summary.

Manual smoke (interactive F5) pending operator verification.

Fixes #64

🤖 Generated with Claude Code

## Summary F12 (#64): show Claude's proposed change **inline in the Markdown editor** — optimistically applied (editable, tinted insertions + struck-red non-editable deletion hints) — with `Accept ▾` / `Reject ▾` (and Accept-all / Reject-all) reachable from **both** the editor (CodeLens + QuickPick) and the review webview. Reverses INV-10 (propose no longer leaves the buffer untouched) and INV-32 (editor no longer unconditionally clean). Spec: `coauthoring-inline-editor-diff.md` v0.1.1 (graduated, session 0058). Built in session 0059. ## What changed (INV-48…54) - **Optimistic apply + re-anchor** (INV-48): propose writes the replacement into the buffer via the F3 seam with a new `landBaseline:false` opt (no baseline advance → stays pending); stores `Proposal.original` + re-fingerprints the anchor to the applied text (so `resolve()` keeps finding it). Reload-safe: `original` captured once. - **Single diff source** (INV-49): pure `decorationPlan` feeds the editor; the webview HTML uses the same `wordEditHunks` diff. - **Render-once** (INV-50): `renderReview` diffs against `landedTextOf` (current minus pending) so a proposed change renders once (as a proposal), not also as a landed diff; author spans + toolbar summary reconciled too. - **Finalize / revert in place** (INV-51): accept = advance baseline + clear (no re-apply); reject = restore `original`. - **Decoration roles** (INV-52): deletion hints are non-editable `after` content; insertion tints over real buffer text. - **Control parity** (INV-53): Accept/Reject/Accept-all/Reject-all from editor (CodeLens+QuickPick) and webview (buttons+dropdown); ✓/✗ glyphs superseded; new `cowriting.rejectAllProposals`. - **Save persists pending** (INV-54). New `EditorProposalController` owns the editor surface; `ProposalController` gains optimistic-apply / finalize / revert / rejectAll. ## Tests - 249 unit (pure: model, proposalModel, decorationPlan, renderReview reconciliation + coloring alignment). - 87 + 5 host-E2E (seam landBaseline, finalize/revert in place, editor surface, control parity, reload-safety, plus existing F4/F10/F11/F12 suites updated for the INV-10 reversal). - Test-infra: pinned `--user-data-dir` to a short `/tmp` path so the host-E2E harness launches from a worktree (macOS socket-length fix). ## Review A final adversarial review caught one CRITICAL (revert target clobbered on save+reload) — fixed with a reload-safe guard + regression E2E — and a minor toolbar double-count — fixed via the reconciled `landedTextOf` summary. Manual smoke (interactive F5) pending operator verification. Fixes #64 🤖 Generated with [Claude Code](https://claude.com/claude-code)
benstull added 11 commits 2026-06-26 15:24:04 +00:00
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The default .vscode-test/user-data socket path exceeds macOS's ~103-char UNIX
socket limit when the repo lives at a long path (e.g. a git worktree), so the
host-E2E harness failed to launch (listen EINVAL). Point --user-data-dir at a
short /tmp root so the IPC socket stays under the limit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
renderReview diffs against landedText (current minus pending proposals), so block
offsets are landedText-relative while authorSpans arrive in currentText coords. A
colored block after a length-changing pending proposal was mis-colored. Map the
spans through toLanded so authorship coloring stays aligned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Final-review fixes:
- CRITICAL: optimisticApply no longer re-captures `original` from an
  already-applied buffer after a window reload (in-memory `applied` set is
  empty post-reload). A proposal already carrying `original` is marked applied
  and skipped, so Reject's revert target survives save+reload (INV-51/54).
  Adds a reload-safety host-E2E that reproduces the prior-session state.
- MINOR: the preview toolbar +N/-N summary now counts against the landed text
  (current minus pending proposals) via the new pure landedTextOf(), so a
  pending change is shown once (as a proposal), not double-counted (INV-50).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
benstull added 1 commit 2026-06-26 15:28:05 +00:00
benstull merged commit 7b98249286 into main 2026-06-26 15:28:15 +00:00
benstull deleted branch s58-inline-editor-diff 2026-06-26 15:28:15 +00:00
Sign in to join this conversation.