fix(#70): INV-5 — reject after an interior tweak restores the retained original #73

Merged
benstull merged 6 commits from s70-inv5-reject-gap into main 2026-07-03 01:19:47 +00:00
Owner

Closes #70.

Rejecting an optimistically-applied proposal after typing inside its pending range silently skipped the revert (the exact-substring anchor orphans per INV-11, and revertInPlace skipped the WorkspaceEdit yet removed the proposal and reported success) — INV-5 did not hold on that path.

Fix — issue direction (a) with an honest (b) fallback; (c) fuzzy resolve rejected (INV-11: never guess):

  • DocState.appliedSpans is the real F12 applied-range bookkeeping: written at optimistic-apply, rebuilt from an exact resolve when absent (reload/reopen), shifted through interior-safe edits via the new pure shiftTracked (anchorer.ts, unit-tested), and distrusted (deleted) when an edit straddles a span boundary or the document closes.
  • revertInPlace: exact resolve → tracked span → loud failure (warning with a Discard proposal (leave text) action, proposal kept, false returned). Read-only sidecars (INV-16) now refuse finalize/revert too.
  • rejectAll orders by the same fallback and returns { reverted, skipped }; the command and the QuickPick batch menu both report skips.
  • The ✓ Keep / ✗ Reject CodeLens pair anchors at the tracked span when a tweak orphans the anchor, so the fixed path is reachable from the primary gesture.

Tests: +11 shiftTracked unit tests (312 unit total); +5 host E2E (repro, gesture routing, distrusted hard-fail, rejectAll parity, lens reachability); fullLoop reject leg now tweaks inside the pending range first. 312 unit + 94/5 E2E green (1 occasionally pending = the known #54 undo-capability probe).

Follow-up (pre-existing, filed separately): resolve() accepts a single exact occurrence without a context check — a duplicated block can hijack any anchor consumer.

Plan: docs/superpowers/plans/2026-07-02-70-inv5-reject-after-interior-edit.md (session 0066).

🤖 Generated with Claude Code

Closes #70. Rejecting an optimistically-applied proposal after typing inside its pending range silently skipped the revert (the exact-substring anchor orphans per INV-11, and `revertInPlace` skipped the WorkspaceEdit yet removed the proposal and reported success) — INV-5 did not hold on that path. **Fix — issue direction (a) with an honest (b) fallback; (c) fuzzy resolve rejected (INV-11: never guess):** - `DocState.appliedSpans` is the real F12 applied-range bookkeeping: written at optimistic-apply, rebuilt from an exact resolve when absent (reload/reopen), shifted through interior-safe edits via the new pure `shiftTracked` (anchorer.ts, unit-tested), and **distrusted** (deleted) when an edit straddles a span boundary or the document closes. - `revertInPlace`: exact resolve → tracked span → loud failure (warning with a **Discard proposal (leave text)** action, proposal kept, `false` returned). Read-only sidecars (INV-16) now refuse finalize/revert too. - `rejectAll` orders by the same fallback and returns `{ reverted, skipped }`; the command and the QuickPick batch menu both report skips. - The ✓ Keep / ✗ Reject CodeLens pair anchors at the tracked span when a tweak orphans the anchor, so the fixed path is reachable from the primary gesture. **Tests:** +11 `shiftTracked` unit tests (312 unit total); +5 host E2E (repro, gesture routing, distrusted hard-fail, rejectAll parity, lens reachability); fullLoop reject leg now tweaks inside the pending range first. 312 unit + 94/5 E2E green (1 occasionally pending = the known #54 undo-capability probe). **Follow-up (pre-existing, filed separately):** `resolve()` accepts a single exact occurrence without a context check — a duplicated block can hijack any anchor consumer. Plan: `docs/superpowers/plans/2026-07-02-70-inv5-reject-after-interior-edit.md` (session 0066). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
benstull added 6 commits 2026-07-03 01:19:33 +00:00
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- shiftTracked (pure, anchorer.ts + unit tests): interior/outside edits shift,
  boundary-straddling edits distrust; an insertion exactly at the span start
  lands BEFORE the span (never absorbed into the revert target)
- clear tracked spans on doc close (a closed buffer can change on disk with no
  change events — a kept span could revert over unrelated text, INV-11)
- renderAll only REBUILDS an absent span from an exact resolve; a present entry
  is continuously-tracked ground truth (duplicate-text resolve can't clobber it)
- revertInPlace/finalizeInPlace gain the guard.isReadOnly check (INV-16)
- the hard-fail warning offers 'Discard proposal (leave text)' so a reloaded
  session can still dismiss an unlocatable proposal record
- ✓/✗ CodeLens pair anchors at the tracked span when a tweak orphans the exact
  anchor (the fixed reject path stays reachable from the primary gesture)
- QuickPick batch menu routes through the reporting accept-all/reject-all
  commands (a discarded skip tally re-created the silent failure)
- one clearProposal helper replaces the thrice-copied clear sequence;
  reject() now cleans applied/appliedSpans too

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
benstull merged commit 7583165354 into main 2026-07-03 01:19:47 +00:00
Sign in to join this conversation.