feat!: sunset the review-panel webview — built-in preview + native diff are the review surfaces (D3/D17, spec §6.10); Keep/Reject lens copy (PUC-2)

Deletes the last bespoke UI surface (TrackChangesPreviewController + its
sealed webview client assets); every entry point re-points at native VS
Code chrome per spec §5: the #41 right-click entries become
cowriting.openReviewPreview (enter coediting if needed -> "Open Preview to
the Side"), Ctrl+Alt+R/Cmd+Alt+R moves to cowriting.reviewChanges (native
diff), and the F12 CodeLens per-proposal titles read "Keep"/"Reject" with
a top-of-file "Keep all (N)"/"Reject all" pair once >=2 proposals are
pending. EditFlow drops its own askClaude/askEditInstruction (the
webview's only caller) and its now-unused constructor params.

Coverage that lived only in the webview's test seams (renderHtmlFor,
receiveMessage, isOpen, ...) moves to direct calls against the surviving
controllers/pure renderReview (test/e2e/suite/helpers.ts gains a shared
renderHtmlFor probe); a genuine gap (pending-proposal + unchanged-block
rendering) is backfilled in test/previewAnnotations.test.ts. README's "how
it works" is rewritten as the native-surface map; the superseded F6/F7/F9/
F10/F11 sections are kept as a marked historical record rather than
deleted outright.

292 unit + 91 E2E green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-07-02 14:51:18 -07:00
parent 17fc01e8d8
commit 2170a0d282
23 changed files with 412 additions and 1475 deletions
+43 -34
View File
@@ -11,7 +11,6 @@ import { GlobalSidecarStore } from "./globalSidecarStore";
import { SidecarRouter } from "./sidecarRouter";
import { DiffViewController } from "./diffViewController";
import { GitBaselineAdapter } from "./gitBaseline";
import { TrackChangesPreviewController } from "./trackChangesPreview";
import { EditFlow } from "./editFlow";
import { LiveProgressUi } from "./liveProgressUi";
import { EditorProposalController } from "./editorProposalController";
@@ -29,7 +28,6 @@ export interface CowritingApi {
proposalController: ProposalController;
versionGuard: VersionGuard;
diffViewController: DiffViewController;
trackChangesPreviewController: TrackChangesPreviewController;
editFlow: EditFlow;
sidecarRouter: SidecarRouter;
liveProgressUi: LiveProgressUi;
@@ -160,8 +158,7 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
const attributionController = new AttributionController(sidecarRouter, root, versionGuard, coeditingRegistry);
context.subscriptions.push(attributionController);
// --- F4: propose/accept (Feature #12) — constructed before the preview so F10
// can route ✓/✗ through it ---
// --- F4: propose/accept (Feature #12) ---
const proposalController = new ProposalController(
sidecarRouter,
attributionController,
@@ -174,10 +171,10 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
// --- F11/F12 (native-surfaces migration, spec §6.10): the edit flow — the
// `cowriting.editDocument` command, the instruction-prompt/progress-wrapped
// "ask Claude" gesture (INV-10 gate), and the turn→proposal(s) cut
// (`runEditAndPropose`, INV-39/40). Constructed BEFORE the review preview (which
// delegates to it) and BEFORE the thread controller, which now consumes it too
// (Task 6: the comment-loop's "make this edit" offer calls runEditAndPropose). ---
const editFlow = new EditFlow(proposalController, attributionController, liveProgressUi, coeditingRegistry);
// (`runEditAndPropose`, INV-39/40). Constructed BEFORE the thread controller,
// which consumes it too (Task 6: the comment-loop's "make this edit" offer
// calls runEditAndPropose). ---
const editFlow = new EditFlow(proposalController);
context.subscriptions.push(editFlow);
// --- F2/F10 (Task 6, D19/D10/D8): comments-first ask + the comment→reply→
@@ -186,20 +183,6 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
const threadController = new ThreadController(sidecarRouter, root, versionGuard, coeditingRegistry, editFlow, liveProgressUi);
context.subscriptions.push(threadController);
// --- F7/F10: the review preview is the single interactive review surface ---
// Workspace-INDEPENDENT (works on any markdown doc, reuses the F6 baseline,
// INV-20). Constructed AFTER attribution (reads F3 spans), proposals (routes
// F4 accept/reject from the webview ✓/✗), and editFlow (delegates the webview's
// askClaude toolbar intent to it).
const trackChangesPreviewController = new TrackChangesPreviewController(
diffViewController,
context.extensionUri,
attributionController,
proposalController,
editFlow,
);
context.subscriptions.push(trackChangesPreviewController);
// --- F12 (#64): the editor surface — optimistic-apply proposals into the buffer,
// decorate the diff, and provide Accept ▾/Reject ▾ CodeLens (INV-48/49/52/53). ---
const editorProposalController = new EditorProposalController(
@@ -211,10 +194,10 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
context.subscriptions.push(editorProposalController);
// #46 (INV-42): accept every pending proposal on the active doc in one gesture
// (also reachable from the preview toolbar's "Accept all" button, which routes
// through its own webview-facing acceptAll). Reuses the batched F4 seam +
// reports applied-vs-skipped (native-surfaces migration: routes directly to
// ProposalController, no preview-controller indirection).
// (also reachable from the top-of-file "Keep all (N)" CodeLens, Task 8 PUC-2).
// Reuses the batched F4 seam + reports applied-vs-skipped, routing directly to
// ProposalController (native-surfaces migration: no preview-controller
// indirection).
context.subscriptions.push(
vscode.commands.registerCommand("cowriting.acceptAllProposals", async () => {
const doc = vscode.window.activeTextEditor?.document;
@@ -223,7 +206,6 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
return;
}
const { applied, skipped } = await proposalController.acceptAllProposals(doc);
trackChangesPreviewController.refresh(doc);
if (applied === 0 && skipped === 0) return;
const skipNote = skipped > 0 ? `, ${skipped} skipped (target text changed — undo or reject)` : "";
void vscode.window.showInformationMessage(
@@ -242,7 +224,6 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
return;
}
const { reverted } = await proposalController.rejectAll(doc);
trackChangesPreviewController.refresh(doc);
if (reverted > 0) {
void vscode.window.showInformationMessage(
`Cowriting: rejected ${reverted} proposal${reverted === 1 ? "" : "s"}.`,
@@ -333,11 +314,13 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
);
// Code-review fix (Finding 1, session native-surfaces-exec): `cowriting.editDocument`
// used to delegate to `EditFlow.askClaude(doc, {kind:"document"})`, which prompts via
// `askEditInstruction` — a rejecting stub since Task 6 sunset the input webview
// (spec §6.10). Reached with no selection (the common case for `cowriting.edit`),
// that was a SILENT dead end: the reject fired before EditFlow.askClaude's own try/
// catch and was `void`'d, so no toast, no thread, nothing. This command now resolves
// used to delegate to `EditFlow.askClaude(doc, {kind:"document"})`, which prompted via
// `askEditInstruction` — a rejecting stub after Task 6 sunset the input webview
// (spec §6.10; both `askClaude`/`askEditInstruction` were deleted from EditFlow in
// Task 8 once their only caller, the review webview, died too). Reached with no
// selection (the common case for `cowriting.edit`), that was a SILENT dead end: the
// reject fired before EditFlow.askClaude's own try/catch and was `void`'d, so no
// toast, no thread, nothing. This command now resolves
// its target document exactly as before (a tab's clicked URI, falling back to the
// active editor), focuses it, and hands off to the SAME comments-first ask as
// `cowriting.editSelection` — `ThreadController.askClaude()` top-anchors a
@@ -386,6 +369,33 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
}),
);
// Task 8 (native-surfaces migration, spec §6.10): the minimal gateway replacing
// the sunset review-webview's #41 right-click entries. Resolves the clicked
// doc (or falls back to the active editor, mirroring editDocument/#41), enters
// coediting if not already (so a fresh doc gets a baseline to review against —
// no-op if already entered or not authorable), then hands off to VS Code's
// OWN "Open Preview to the Side" — the built-in preview IS the review surface
// now (Task 7's annotations render inside it).
context.subscriptions.push(
vscode.commands.registerCommand("cowriting.openReviewPreview", async (uri?: vscode.Uri) => {
const doc = uri
? vscode.workspace.textDocuments.find((d) => d.uri.toString() === uri.toString()) ??
(await vscode.workspace.openTextDocument(uri))
: vscode.window.activeTextEditor?.document;
if (!doc || doc.languageId !== "markdown") {
void vscode.window.showWarningMessage("Cowriting: open a Markdown document to review it.");
return;
}
if (vscode.window.activeTextEditor?.document.uri.toString() !== doc.uri.toString()) {
await vscode.window.showTextDocument(doc);
}
if (!coeditingRegistry.isCoediting(doc.uri) && isAuthorable(doc.uri.scheme)) {
await vscode.commands.executeCommand("cowriting.coeditDocument");
}
await vscode.commands.executeCommand("markdown.showPreviewToSide", doc.uri);
}),
);
// Render threads + attributions for already-open editors, and on future opens.
// INV-10: gated on the registry too — belt-and-suspenders with the per-controller
// gates (renderAll/loadAll already early-return), but keeps the intent explicit
@@ -456,7 +466,6 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
proposalController,
versionGuard,
diffViewController,
trackChangesPreviewController,
editFlow,
sidecarRouter,
liveProgressUi,