diff --git a/src/attributionController.ts b/src/attributionController.ts index a9f4947..c3e7066 100644 --- a/src/attributionController.ts +++ b/src/attributionController.ts @@ -249,7 +249,7 @@ export class AttributionController implements vscode.Disposable { range: vscode.Range, newText: string, provenance: Provenance, - opts?: { expectedVersion?: number; turnId?: string }, + opts?: { expectedVersion?: number; turnId?: string; landBaseline?: boolean }, ): Promise { if (!this.isTracked(document)) return false; if (opts?.expectedVersion !== undefined && document.version !== opts.expectedVersion) return false; @@ -290,15 +290,25 @@ export class AttributionController implements vscode.Disposable { "(host minimized differently?) — the edit may be mis-attributed (INV-9).", ); } - if (ok) { - // F6 (INV-18): a real machine landing — signal the baseline to advance so - // this text never shows as a change in the diff view. Fire regardless of - // attribution-match bookkeeping above; the landing happened either way. + if (ok && opts?.landBaseline !== false) { + // F6 (INV-18): a real machine landing — advance the baseline. F12 (INV-48) + // suppresses this for optimistic apply: the proposed text is in the buffer + // but the change stays PENDING (baseline at pre-proposal) until accept. this.applyEmitter.fire({ document }); } return ok; } + /** + * F12/#64 (INV-51): fire the machine-landing signal WITHOUT applying text — used + * by finalize-in-place, where the proposed text already landed in the buffer via + * optimistic apply (`landBaseline:false`) and accept only needs to advance the + * F6 baseline so the now-accepted change stops reading as pending. + */ + signalLanded(document: vscode.TextDocument): void { + if (this.isTracked(document)) this.applyEmitter.fire({ document }); + } + // ---- PUC-4: persistence on save ---------------------------------------------------- private onDidSave(document: vscode.TextDocument): void { diff --git a/test/e2e/suite/f12InlineDiff.test.ts b/test/e2e/suite/f12InlineDiff.test.ts new file mode 100644 index 0000000..3a9c749 --- /dev/null +++ b/test/e2e/suite/f12InlineDiff.test.ts @@ -0,0 +1,48 @@ +import * as assert from "assert"; +import * as fs from "fs"; +import * as path from "path"; +import * as vscode from "vscode"; +import type { CowritingApi } from "../../../src/extension"; + +const WS = process.env.E2E_WORKSPACE!; +const settle = () => new Promise((r) => setTimeout(r, 400)); + +async function getApi(): Promise { + const ext = vscode.extensions.getExtension("benstull.vscode-cowriting-plugin")!; + const api = (await ext.activate()) as CowritingApi; + assert.ok(api?.trackChangesPreviewController && api?.proposalController, "exports controllers"); + return api; +} + +async function freshDoc(rel: string, body: string): Promise<{ doc: vscode.TextDocument; key: string }> { + const abs = path.join(WS, rel); + fs.mkdirSync(path.dirname(abs), { recursive: true }); + fs.writeFileSync(abs, body, "utf8"); + const uri = vscode.Uri.file(abs); + const doc = await vscode.workspace.openTextDocument(uri); + await vscode.window.showTextDocument(doc); + await settle(); + return { doc, key: uri.toString() }; +} + +suite("F12 inline diff — seam landBaseline (#64, INV-48)", () => { + test("applyAgentEdit with landBaseline:false applies text but does NOT advance the baseline", async () => { + const { doc, key } = await freshDoc("docs/f12-land.md", "# T\n\nalpha here.\n"); + const api = await getApi(); + await vscode.commands.executeCommand("cowriting.showTrackChangesPreview"); + await settle(); + const before = api.diffViewController.getBaseline(key)?.text ?? doc.getText(); + const start = doc.getText().indexOf("alpha"); + const ok = await api.attributionController.applyAgentEdit( + doc, + new vscode.Range(doc.positionAt(start), doc.positionAt(start + "alpha".length)), + "ALPHA", + { kind: "agent", id: "claude", agent: { sdk: "x", model: "m", sessionId: "s" } }, + { landBaseline: false }, + ); + await settle(); + assert.ok(ok, "edit applied"); + assert.ok(doc.getText().includes("ALPHA"), "text in buffer"); + assert.strictEqual(api.diffViewController.getBaseline(key)?.text ?? doc.getText(), before, "baseline unchanged"); + }); +});