refactor: extract EditFlow (runEditAndPropose + editDocument) from the review webview ahead of its sunset (spec §6.10)

Moves runEditAndPropose, the askClaude gate/prompt/progress wrapper, the
EditTarget type, the editTurn/askEditInstruction/turnSeq state, and the
cowriting.editDocument command registration out of TrackChangesPreviewController
into a new EditFlow (src/editFlow.ts), reachable from the review webview
(delegates) and, from Task 6 on, the thread controller. extension.ts now
constructs EditFlow before the preview controller and routes
acceptAllProposals/rejectAllProposals directly to ProposalController, dropping
the preview-controller indirection. E2E suites that drove the old
trackChangesPreviewController.{setEditTurnForTest,runEditAndPropose,askEditInstruction}
seams now drive the same seams on editFlow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-07-02 08:22:44 -07:00
parent f23fc4afb3
commit ad5d34b85b
9 changed files with 300 additions and 239 deletions
+37 -15
View File
@@ -12,6 +12,7 @@ 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";
import { isAuthorable, routeEdit, selectionRejection } from "./workspacePath";
@@ -29,6 +30,7 @@ export interface CowritingApi {
versionGuard: VersionGuard;
diffViewController: DiffViewController;
trackChangesPreviewController: TrackChangesPreviewController;
editFlow: EditFlow;
sidecarRouter: SidecarRouter;
liveProgressUi: LiveProgressUi;
editorProposalController: EditorProposalController;
@@ -164,17 +166,25 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
);
context.subscriptions.push(proposalController);
// --- 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 reachable from the thread controller too (Task 6). ---
const editFlow = new EditFlow(proposalController, attributionController, liveProgressUi, coeditingRegistry);
context.subscriptions.push(editFlow);
// --- 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) and proposals (routes
// F4 accept/reject from the webview ✓/✗).
// 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,
liveProgressUi,
coeditingRegistry,
editFlow,
);
context.subscriptions.push(trackChangesPreviewController);
@@ -189,8 +199,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). Reuses the
// batched F4 seam + reports applied-vs-skipped.
// (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).
context.subscriptions.push(
vscode.commands.registerCommand("cowriting.acceptAllProposals", async () => {
const doc = vscode.window.activeTextEditor?.document;
@@ -198,11 +210,17 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
void vscode.window.showWarningMessage("Cowriting: open a Markdown document to accept its proposals.");
return;
}
await trackChangesPreviewController.acceptAll(doc);
const { applied, skipped } = await proposalController.acceptAllProposals(doc);
if (applied === 0 && skipped === 0) return;
const skipNote = skipped > 0 ? `, ${skipped} skipped (target text changed — undo or reject)` : "";
void vscode.window.showInformationMessage(
`Cowriting: accepted ${applied} proposal${applied === 1 ? "" : "s"}${skipNote}.`,
);
}),
);
// #64 (INV-53): reject every pending proposal on the active doc in one gesture.
// #64 (INV-53): reject every pending proposal on the active doc in one gesture
// (native-surfaces migration: routes directly to ProposalController).
context.subscriptions.push(
vscode.commands.registerCommand("cowriting.rejectAllProposals", async () => {
const doc = vscode.window.activeTextEditor?.document;
@@ -210,7 +228,12 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
void vscode.window.showWarningMessage("Cowriting: open a Markdown document to reject its proposals.");
return;
}
await trackChangesPreviewController.rejectAll(doc);
const { reverted } = await proposalController.rejectAll(doc);
if (reverted > 0) {
void vscode.window.showInformationMessage(
`Cowriting: rejected ${reverted} proposal${reverted === 1 ? "" : "s"}.`,
);
}
}),
);
@@ -319,12 +342,10 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
end: document.offsetAt(selection.end),
});
// The instruction prompt is the multi-line split-below webview box (shared
// with the document case, via the preview controller); the document above
// keeps the selection highlighted while it's open. selectedText/fp were
// captured above, so moving focus to the box doesn't affect what we edit.
const instruction = await trackChangesPreviewController.askEditInstruction(
"Ask Claude to Edit This Selection",
);
// with the document case, via EditFlow); the document above keeps the
// selection highlighted while it's open. selectedText/fp were captured
// above, so moving focus to the box doesn't affect what we edit.
const instruction = await editFlow.askEditInstruction("Ask Claude to Edit This Selection");
if (!instruction) return;
const turnId = `turn-${Date.now().toString(36)}`;
try {
@@ -433,6 +454,7 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
versionGuard,
diffViewController,
trackChangesPreviewController,
editFlow,
sidecarRouter,
liveProgressUi,
editorProposalController,