feat(ux): unify Ask-Claude-to-Edit on a split-below multi-line webview (#65)

This commit was merged in pull request #65.
This commit is contained in:
2026-06-26 13:50:31 +00:00
parent 56cc9eb6ad
commit 7e42d115c0
7 changed files with 180 additions and 147 deletions
+14 -24
View File
@@ -18,7 +18,7 @@ import { buildFingerprint } from "./anchorer";
import { isAuthorable } from "./workspacePath";
import type { EditTurnResult, RunEditTurnOptions } from "./liveTurn";
import type { LiveProgressUi } from "./liveProgressUi";
import type { InlineAskController } from "./inlineAsk";
import { promptEditInstruction } from "./editInstructionInput";
/**
* F11: a host edit turn (selection/document text + instruction → rewrite).
@@ -63,6 +63,13 @@ export class TrackChangesPreviewController implements vscode.Disposable {
const { runEditTurn } = await import("./liveTurn");
return runEditTurn(instruction, text, opts);
};
/**
* The instruction prompt (the multi-line split-below webview box) for BOTH the
* selection and document cases. A field so host E2E can stub it — the webview
* DOM can't run in CI (mirrors `editTurn`). Also used by the editor's
* `cowriting.editSelection` command (via this controller).
*/
askEditInstruction: (header: string) => Promise<string | undefined> = promptEditInstruction;
/** Monotonic per-session counter minting a stable turnId for each Ask-Claude gesture. */
private turnSeq = 0;
private nextTurnSeq(): number {
@@ -75,7 +82,6 @@ export class TrackChangesPreviewController implements vscode.Disposable {
private readonly attribution: AttributionController,
private readonly proposals: ProposalController,
private readonly liveProgressUi: LiveProgressUi,
private readonly inlineAsk: InlineAskController,
) {
this.disposables.push(
// F11 (SLICE-5): the editor/title gateway passes the tab's resource Uri;
@@ -222,34 +228,18 @@ export class TrackChangesPreviewController implements vscode.Disposable {
);
}
/**
* Where the inline Ask-Claude input anchors: a range edit pins to its selection;
* a whole-document edit pins to the editor's cursor line if this document is the
* active editor, else to the document start.
*/
private editTargetAnchor(document: vscode.TextDocument, target: EditTarget): vscode.Range {
if (target.kind === "range") {
return new vscode.Range(document.positionAt(target.start), document.positionAt(target.end));
}
const active = vscode.window.activeTextEditor;
if (active && active.document.uri.toString() === document.uri.toString()) {
const line = active.selection.active.line;
return new vscode.Range(line, 0, line, 0);
}
return new vscode.Range(0, 0, 0, 0);
}
/**
* 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
* the result as F4 proposal(s). UI wrapper around `runEditAndPropose`.
*/
private async askClaude(document: vscode.TextDocument, target: EditTarget): Promise<void> {
// The instruction prompt opens INLINE (inlineAsk) at the target: the selected
// range for a range edit, or the editor's cursor line / the document start for
// a whole-document edit — never the top-center QuickInput.
const anchor = this.editTargetAnchor(document, target);
const instruction = await this.inlineAsk.prompt(document.uri, anchor);
// Both scopes use the same multi-line split-below webview box; only the header
// (and the downstream proposal logic) differs. For a selection the document
// above keeps the selection highlighted while the box is open.
const header =
target.kind === "document" ? "Ask Claude to Edit This Document" : "Ask Claude to Edit This Selection";
const instruction = await this.askEditInstruction(header);
if (!instruction) return;
try {
const ids = await vscode.window.withProgress(