Live: stop the preflight writing source, cache the resolution

The polling-rework preflight wrote the variant scaffold into source during the
poll lease, before the agent acted. On source-preview targets (React/Vue/Vite,
everything but the svelte-component path) that write full-reloaded the
framework; a browser caught mid-reload missed the agent's variant write and the
SSE done, and sat stranded at 0/N.

Restore the 3.5 single-atomic-edit semantics: the preflight still resolves the
element location and computes the scaffold, but --defer-source-write leaves
source untouched and hands the agent the wrapper text plus the picked source
range. The agent splices variants into the wrapper and replaces the range in
one write, so the framework reloads exactly once. The svelte-component path is
untouched (it never writes route source). The missed-completion recovery stays
as defense in depth.

Also cache the resolved source file per target signature (locator + route):
the ~7.6s tree search re-ran on every generate for the same element; a hit now
points the helper straight at the file via --file, invalidated when the target
changes or a resolution fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-22 22:49:44 -07:00
co-authored by Claude Fable 5
parent 4cd5ea7547
commit dbe0c12b91
8 changed files with 314 additions and 33 deletions
+24 -6
View File
@@ -131,6 +131,9 @@ Output (JSON):
const query = argVal(args, '--query');
const filePath = argVal(args, '--file');
const text = argVal(args, '--text');
// See live-wrap.mjs: preflight computes the scaffold but leaves source
// untouched so the agent's single edit is the only framework reload.
const deferSourceWrite = args.includes('--defer-source-write');
if (!id) { console.error('Missing --id'); process.exit(1); }
if (!position) { console.error('Missing --position (before | after)'); process.exit(1); }
@@ -244,12 +247,23 @@ Output (JSON):
isJsx,
});
const newLines = [
...lines.slice(0, spliceIndex),
...wrapperLines,
...lines.slice(spliceIndex),
];
fs.writeFileSync(targetFile, newLines.join('\n'), 'utf-8');
let deferredWrapper = null;
if (deferSourceWrite) {
// Insert-as-empty-range: the agent inserts `wrapperBlock` (variants spliced
// at the marker) at spliceIndex without removing any source line.
deferredWrapper = {
block: wrapperLines.join('\n'),
replaceStartLine: spliceIndex + 1,
replaceEndLine: spliceIndex, // empty range (endLine < startLine) => insertion
};
} else {
const newLines = [
...lines.slice(0, spliceIndex),
...wrapperLines,
...lines.slice(spliceIndex),
];
fs.writeFileSync(targetFile, newLines.join('\n'), 'utf-8');
}
const insertLine = spliceIndex + 3;
@@ -257,6 +271,10 @@ Output (JSON):
mode: 'insert',
position,
file: relTargetFile,
sourceWritten: deferredWrapper ? false : undefined,
wrapperBlock: deferredWrapper ? deferredWrapper.block : undefined,
replaceStartLine: deferredWrapper ? deferredWrapper.replaceStartLine : undefined,
replaceEndLine: deferredWrapper ? deferredWrapper.replaceEndLine : undefined,
insertLine: insertLine + 1,
commentSyntax,
styleMode: styleMode.mode,