feat(f11): SLICE-3 — Edit Document button + per-hunk proposal path (#43, INV-37)
A whole-document Ask-Claude rewrite is diffed into hunks and surfaced as N independent F4 proposals (one per changed hunk) — reusing the F4 single-range model N times, no new model. Per spec §6.4/§7.2 SLICE-3. - trackChangesModel: pure `diffToHunks(currentText, rewrittenText)` → EditHunk[] (vscode-free, deterministic; diffWordsWithSpace, coalescing adjacent add/remove runs; offsets index currentText). - trackChangesPreview: `runEditAndPropose(document, target, instruction)` — the shared host routine (selection → one single-range propose; document → diff → one propose per hunk; never mutates the doc, INV-10); `askClaude` UI wrapper (host showInputBox keeps LLM/secrets out of the sealed webview, INV-8/35); injectable `editTurn` + `setEditTurnForTest` seam (no LLM in CI); the `askClaude` inbound message branch; `cowriting.editDocument` command for #42 reuse. - package.json: register cowriting.editDocument, palette-guarded on markdown. - webview: ✦ Ask Claude to Edit Document button → { askClaude, scope:"document" }. - unit: diffToHunks fixtures (zero/one/multi-hunk, wholesale, determinism). - host E2E: stubbed multi-hunk rewrite → N matching proposals, doc untouched; editDocument command registered + markdown-guarded. 205 unit + 49 host E2E green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
* DOM — `markdown-it` and `diff` are libraries; mermaid runs later in the webview.
|
||||
*/
|
||||
import MarkdownIt from "markdown-it";
|
||||
import { diffArrays, diffWords } from "diff";
|
||||
import { diffArrays, diffWords, diffWordsWithSpace } from "diff";
|
||||
import { diffMermaid } from "./mermaidDiff";
|
||||
|
||||
export type BlockType = "prose" | "code" | "mermaid";
|
||||
@@ -193,6 +193,50 @@ export function diffBlocks(baselineText: string, currentText: string): BlockOp[]
|
||||
return ops;
|
||||
}
|
||||
|
||||
/** A contiguous changed region of `currentText` and its replacement (F11). */
|
||||
export interface EditHunk {
|
||||
/** char offset of the hunk's first changed char in currentText. */
|
||||
start: number;
|
||||
/** char offset one past the hunk's last changed char (start==end → a pure insertion). */
|
||||
end: number;
|
||||
/** the text that replaces currentText.slice(start, end). */
|
||||
replacement: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* F11 (INV-37, §6.4): diff a whole-document rewrite into per-hunk replacement
|
||||
* ranges — each becomes one independent F4 single-range proposal (so a document
|
||||
* edit surfaces as N independently ✓/✗-able blue blocks, no new model). Pure,
|
||||
* vscode-free, deterministic. Offsets index into `currentText`: removed +
|
||||
* unchanged parts reconstruct it exactly, so advancing on those yields true
|
||||
* source offsets. Adjacent added/removed runs coalesce into one hunk; an
|
||||
* unchanged part flushes the open hunk.
|
||||
*/
|
||||
export function diffToHunks(currentText: string, rewrittenText: string): EditHunk[] {
|
||||
const hunks: EditHunk[] = [];
|
||||
let offset = 0;
|
||||
let open: EditHunk | null = null;
|
||||
const flush = () => {
|
||||
if (open) hunks.push(open);
|
||||
open = null;
|
||||
};
|
||||
for (const part of diffWordsWithSpace(currentText, rewrittenText)) {
|
||||
if (part.added) {
|
||||
open ??= { start: offset, end: offset, replacement: "" };
|
||||
open.replacement += part.value;
|
||||
} else if (part.removed) {
|
||||
open ??= { start: offset, end: offset, replacement: "" };
|
||||
offset += part.value.length;
|
||||
open.end = offset;
|
||||
} else {
|
||||
flush();
|
||||
offset += part.value.length;
|
||||
}
|
||||
}
|
||||
flush();
|
||||
return hunks;
|
||||
}
|
||||
|
||||
const md = new MarkdownIt({ html: true, linkify: false, breaks: false });
|
||||
// mermaid fences → <pre class="mermaid">SRC</pre> for client-side rendering; all
|
||||
// other fences fall through to markdown-it's default (escaped <pre><code>).
|
||||
|
||||
Reference in New Issue
Block a user