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

This commit was merged in pull request #66.
This commit is contained in:
2026-06-26 15:28:14 +00:00
parent d2ef9457c4
commit 7b98249286
24 changed files with 2689 additions and 57 deletions
+23 -6
View File
@@ -13,7 +13,7 @@ import * as vscode from "vscode";
import type { DiffViewController } from "./diffViewController";
import type { AttributionController } from "./attributionController";
import type { ProposalController } from "./proposalController";
import { renderReview, renderPlain, diffBlocks, diffToBlockHunks, type BlockOp } from "./trackChangesModel";
import { renderReview, renderPlain, diffBlocks, diffToBlockHunks, landedTextOf, type BlockOp } from "./trackChangesModel";
import { buildFingerprint } from "./anchorer";
import { isAuthorable } from "./workspacePath";
import type { EditTurnResult, RunEditTurnOptions } from "./liveTurn";
@@ -44,7 +44,8 @@ type ToolbarMsg =
| { type: "pinBaseline" }
| { type: "askClaude"; scope: "document" }
| { type: "askClaude"; scope: "selection"; start: number; end: number }
| { type: "acceptAll" };
| { type: "acceptAll" }
| { type: "rejectAll" };
export class TrackChangesPreviewController implements vscode.Disposable {
private readonly disposables: vscode.Disposable[] = [];
@@ -196,8 +197,7 @@ export class TrackChangesPreviewController implements vscode.Disposable {
.acceptById(this.proposals.keyFor(document), m.proposalId)
.then(() => this.refresh(document));
} else if (m?.type === "reject" && m.proposalId) {
this.proposals.rejectById(this.proposals.keyFor(document), m.proposalId);
this.refresh(document);
void this.proposals.rejectByIdInPlace(this.proposals.keyFor(document), m.proposalId).then(() => this.refresh(document));
} else if (m?.type === "pinBaseline") {
// F6 baseline store re-render arrives via the onDidChangeBaseline subscription.
this.diffView.pin(document);
@@ -208,6 +208,8 @@ export class TrackChangesPreviewController implements vscode.Disposable {
} else if (m?.type === "acceptAll") {
// #46 (INV-42): batch-accept every pending proposal on this doc, then report.
void this.acceptAll(document);
} else if (m?.type === "rejectAll") {
void this.rejectAll(document);
}
}
@@ -228,6 +230,17 @@ export class TrackChangesPreviewController implements vscode.Disposable {
);
}
/** #64 (INV-53): revert every pending proposal on the document; report the count. */
async rejectAll(document: vscode.TextDocument): Promise<void> {
const { reverted } = await this.proposals.rejectAll(document);
this.refresh(document);
if (reverted > 0) {
void vscode.window.showInformationMessage(
`Cowriting: rejected ${reverted} proposal${reverted === 1 ? "" : "s"}.`,
);
}
}
/**
* F11 (PUC-3/4): prompt host-side for the instruction (keeps the LLM/secret
* surface out of the sealed webview, INV-8/35), run the edit turn, and surface
@@ -359,9 +372,13 @@ export class TrackChangesPreviewController implements vscode.Disposable {
}
const spans = this.attribution.spansFor(document);
const proposals = this.proposals.listProposals(document);
// F12/#64 (INV-50): count added/removed against the LANDED text (current minus
// pending proposals), matching the body — a pending change shows once, as a
// proposal, and is not also tallied as a landed add/remove.
const landedOps = diffBlocks(baselineText, landedTextOf(current, proposals));
const summary = {
added: ops.filter((o) => o.kind === "added").length,
removed: ops.filter((o) => o.kind === "removed").length,
added: landedOps.filter((o) => o.kind === "added").length,
removed: landedOps.filter((o) => o.kind === "removed").length,
proposals: proposals.length,
};
void panel.webview.postMessage({