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
+12 -12
View File
@@ -80,18 +80,18 @@ suite("F11 preview toolbar (host E2E — message → seam wiring, no LLM)", () =
"# F11 doc\n\nThe quick brown fox jumps over the lazy dog.\n",
);
const api = await getApi();
const ctl = api.trackChangesPreviewController;
const editFlow = api.editFlow;
await vscode.commands.executeCommand("cowriting.showTrackChangesPreview");
await settle();
// Stub the host edit turn (no LLM in CI): rewrite two distinct words.
ctl.setEditTurnForTest(async () => ({
editFlow.setEditTurnForTest(async () => ({
replacement: "# F11 doc\n\nThe quick RED fox jumps over the lazy CAT.\n",
model: "sonnet",
sessionId: "e2e-f11-doc",
}));
const ids = await ctl.runEditAndPropose(doc, { kind: "document" }, "swap brown→RED and dog→CAT");
const ids = await editFlow.runEditAndPropose(doc, { kind: "document" }, "swap brown→RED and dog→CAT");
await settle();
assert.strictEqual(ids.length, 1, "two changed words in one block → ONE block proposal (INV-39)");
@@ -114,19 +114,19 @@ suite("F11 preview toolbar (host E2E — message → seam wiring, no LLM)", () =
const body = "# F11 sel\n\nThe target paragraph Claude will rewrite.\n\nAnother untouched paragraph.\n";
const { doc, key } = await freshDoc("docs/f11sel.md", body);
const api = await getApi();
const ctl = api.trackChangesPreviewController;
const editFlow = api.editFlow;
await vscode.commands.executeCommand("cowriting.showTrackChangesPreview");
await settle();
const target = "The target paragraph Claude will rewrite.";
const start = doc.getText().indexOf(target);
const end = start + target.length;
ctl.setEditTurnForTest(async (_instruction, text) => {
editFlow.setEditTurnForTest(async (_instruction, text) => {
assert.strictEqual(text, target, "the turn receives exactly the selected source range");
return { replacement: "The REWRITTEN paragraph from Claude.", model: "sonnet", sessionId: "e2e-f11-sel" };
});
const ids = await ctl.runEditAndPropose(doc, { kind: "range", start, end }, "rewrite this paragraph");
const ids = await editFlow.runEditAndPropose(doc, { kind: "range", start, end }, "rewrite this paragraph");
await settle();
assert.strictEqual(ids.length, 1, "a selection yields exactly one proposal");
const views = api.proposalController.listProposals(doc);
@@ -144,13 +144,13 @@ suite("F11 preview toolbar (host E2E — message → seam wiring, no LLM)", () =
test("runEditAndPropose(range) where Claude returns the selection unchanged → no proposal", async () => {
const { doc } = await freshDoc("docs/f11noop.md", "# noop\n\nLeave me exactly as I am.\n");
const api = await getApi();
const ctl = api.trackChangesPreviewController;
const editFlow = api.editFlow;
await vscode.commands.executeCommand("cowriting.showTrackChangesPreview");
await settle();
const target = "Leave me exactly as I am.";
const start = doc.getText().indexOf(target);
ctl.setEditTurnForTest(async (_i, text) => ({ replacement: text, model: "sonnet", sessionId: "e2e-noop" }));
const ids = await ctl.runEditAndPropose(doc, { kind: "range", start, end: start + target.length }, "no change");
editFlow.setEditTurnForTest(async (_i, text) => ({ replacement: text, model: "sonnet", sessionId: "e2e-noop" }));
const ids = await editFlow.runEditAndPropose(doc, { kind: "range", start, end: start + target.length }, "no change");
assert.strictEqual(ids.length, 0, "an unchanged replacement proposes nothing");
});
@@ -162,12 +162,12 @@ suite("F11 preview toolbar (host E2E — message → seam wiring, no LLM)", () =
const rewrite = "# F11 accept\n\nThe brown fox QUIETLY sleeps today.\n";
const { doc } = await freshDoc("docs/f11accept.md", original);
const api = await getApi();
const ctl = api.trackChangesPreviewController;
const editFlow = api.editFlow;
await vscode.commands.executeCommand("cowriting.showTrackChangesPreview");
await settle();
ctl.setEditTurnForTest(async () => ({ replacement: rewrite, model: "sonnet", sessionId: "e2e-accept" }));
const ids = await ctl.runEditAndPropose(doc, { kind: "document" }, "expand the sentence");
editFlow.setEditTurnForTest(async () => ({ replacement: rewrite, model: "sonnet", sessionId: "e2e-accept" }));
const ids = await editFlow.runEditAndPropose(doc, { kind: "document" }, "expand the sentence");
await settle();
assert.ok(ids.length >= 1, "the rewrite produced at least one proposal");