fix(#70): reject after an interior tweak restores the original via tracked applied span (INV-5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -204,6 +204,60 @@ suite("F12 inline diff — control parity (#64, INV-53)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// #70 (INV-5): rejecting a proposal AFTER typing inside its pending range must
|
||||
// still restore the retained original. The exact-substring anchor is orphaned by
|
||||
// the interior tweak (INV-1/INV-11) — the revert falls back to the F12
|
||||
// applied-range bookkeeping (appliedSpans), which shift-tracks the span through
|
||||
// interior edits.
|
||||
suite("F12 inline diff — #70 reject after interior edit (INV-5)", () => {
|
||||
test("revertInPlace restores the original after a tweak inside the pending range", async () => {
|
||||
const ORIGINAL = "The quick brown fox jumps.";
|
||||
const { doc } = await freshDoc("docs/s70-reject-tweaked.md", `# T\n\n${ORIGINAL}\n`);
|
||||
const api = await getApi();
|
||||
const p = api.proposalController;
|
||||
const docKey = p.keyFor(doc);
|
||||
const fp = { text: ORIGINAL, before: "", after: "", lineHint: 2 };
|
||||
const id = await p.propose(doc, fp, "The rapid brown fox jumps.",
|
||||
{ kind: "agent", id: "claude", agent: { sdk: "x", model: "m", sessionId: "s" } }, { granularity: "block" });
|
||||
await p.optimisticApply(doc, id!);
|
||||
await settle();
|
||||
assert.ok(doc.getText().includes("The rapid brown fox jumps."), "optimistically applied");
|
||||
// Human types INSIDE the pending range → orphans the exact anchor (INV-11).
|
||||
const at = doc.getText().indexOf("rapid");
|
||||
const we = new vscode.WorkspaceEdit();
|
||||
we.replace(doc.uri, new vscode.Range(doc.positionAt(at), doc.positionAt(at + "rapid".length)), "RAPID-ish");
|
||||
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 by the tweak");
|
||||
// ✗ Reject: must restore the retained original EXACTLY (INV-5), not skip.
|
||||
assert.ok(await p.revertInPlace(docKey, id!), "reject reports success");
|
||||
await settle();
|
||||
assert.strictEqual(doc.getText(), `# T\n\n${ORIGINAL}\n`, "original restored exactly (INV-5)");
|
||||
assert.strictEqual(p.listProposals(doc).length, 0, "proposal cleared");
|
||||
});
|
||||
|
||||
test("rejectByIdInPlace routes the tweaked-applied case the same way", async () => {
|
||||
const ORIGINAL = "A steady closing line.";
|
||||
const { doc } = await freshDoc("docs/s70-reject-route.md", `# T\n\n${ORIGINAL}\n`);
|
||||
const api = await getApi();
|
||||
const p = api.proposalController;
|
||||
const docKey = p.keyFor(doc);
|
||||
const fp = { text: ORIGINAL, before: "", after: "", lineHint: 2 };
|
||||
const id = await p.propose(doc, fp, "A wobbly closing line.",
|
||||
{ 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("wobbly");
|
||||
const we = new vscode.WorkspaceEdit();
|
||||
we.replace(doc.uri, new vscode.Range(doc.positionAt(at), doc.positionAt(at)), "very-");
|
||||
assert.ok(await vscode.workspace.applyEdit(we), "interior insertion applied");
|
||||
await settle();
|
||||
assert.ok(await p.rejectByIdInPlace(docKey, id!), "gesture-level reject 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
|
||||
// persists with `original` set and its fp re-anchored to the applied text. A fresh
|
||||
// controller (empty in-memory `applied` set) must NOT re-capture `original` from the
|
||||
|
||||
Reference in New Issue
Block a user