fix(#70): harden the tracked-span revert per branch review
- 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>
This commit is contained in:
@@ -320,6 +320,35 @@ suite("F12 inline diff — #70 reject after interior edit (INV-5)", () => {
|
||||
assert.strictEqual(doc.getText(), "# T\n\nOne aaa.\n\nTwo bbb.\n", "document fully restored (INV-5)");
|
||||
assert.strictEqual(p.listProposals(doc).length, 0, "all cleared");
|
||||
});
|
||||
|
||||
// The decision gestures must stay REACHABLE for a tweaked proposal: the ✓/✗
|
||||
// CodeLens pair anchors at the tracked span when the interior tweak orphans
|
||||
// the exact anchor — otherwise the fixed reject path exists only at the API.
|
||||
test("the ✓ Keep / ✗ Reject CodeLens pair survives an interior tweak (tracked-span anchor)", async () => {
|
||||
const ORIGINAL = "Lens target sentence here.";
|
||||
const { doc } = await freshDoc("docs/s70-lens-tweak.md", `# T\n\n${ORIGINAL}\n`);
|
||||
const api = await getApi();
|
||||
const p = api.proposalController;
|
||||
const fp = { text: ORIGINAL, before: "", after: "", lineHint: 2 };
|
||||
const id = await p.propose(doc, fp, "Lens TARGET sentence here.",
|
||||
{ kind: "agent", id: "claude", agent: { sdk: "x", model: "m", sessionId: "s" } }, { granularity: "block" });
|
||||
await p.optimisticApply(doc, id!);
|
||||
await settle();
|
||||
const at = doc.getText().indexOf("TARGET");
|
||||
const we = new vscode.WorkspaceEdit();
|
||||
we.replace(doc.uri, new vscode.Range(doc.positionAt(at), doc.positionAt(at + 6)), "TARGET-tweaked");
|
||||
assert.ok(await vscode.workspace.applyEdit(we), "interior tweak applied");
|
||||
await settle();
|
||||
assert.strictEqual(p.listProposals(doc).find((v) => v.id === id)!.anchorStart, null, "anchor orphaned");
|
||||
const lenses = api.editorProposalController.provideCodeLenses(doc);
|
||||
const titles = lenses.map((l) => l.command?.title);
|
||||
assert.deepStrictEqual(titles, ["✓ Keep", "✗ Reject"], "the pair survives the tweak");
|
||||
assert.deepStrictEqual(lenses[1].command!.arguments, [id], "reject lens still targets the tweaked proposal");
|
||||
// The reject it routes at still restores the original (the Task-1 contract).
|
||||
assert.ok(await p.revertInPlace(p.keyFor(doc), id!), "reject via the surviving gesture succeeds");
|
||||
await settle();
|
||||
assert.strictEqual(doc.getText(), `# T\n\n${ORIGINAL}\n`, "original restored exactly (INV-5)");
|
||||
});
|
||||
});
|
||||
|
||||
// Reload-safety: a proposal that was optimistically applied in a PRIOR session
|
||||
|
||||
Reference in New Issue
Block a user