Skip to content

Commit 967db03

Browse files
committed
fix(desktop): dedupe duplicate snapshots + silence chromium debug noise
- store.ts: per-designId serialized queue in persistArtifactSnapshot collapses the race between applyGenerateResult and agent_end paths; content-based dedupe drops the second write when parent html matches. - scripts/dev.cjs: line-filter child stderr to drop Chromium 'Hit debug scenario: 4' noise from iframe srcdoc navigations (electron/electron#44368, upstream not-planned).
1 parent fd27c4e commit 967db03

2 files changed

Lines changed: 64 additions & 14 deletions

File tree

apps/desktop/scripts/dev.cjs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,38 @@ env.ELECTRON_RUN_AS_NODE = undefined;
1212

1313
const child = spawn(process.execPath, [electronViteBin, 'dev', ...process.argv.slice(2)], {
1414
env,
15-
stdio: 'inherit',
15+
stdio: ['inherit', 'inherit', 'pipe'],
1616
windowsHide: false,
1717
});
1818

19+
// Chromium emits `ERROR:debug_utils.cc(14)] Hit debug scenario: 4` from iframe
20+
// srcdoc navigations — a known regression (electron/electron#44368) closed by
21+
// upstream as "not planned". Harmless but floods the terminal on every preview
22+
// render, so drop just that line. Everything else passes through untouched.
23+
const NOISY_STDERR = /Hit debug scenario:\s*4/;
24+
let stderrTail = '';
25+
child.stderr.on('data', (chunk) => {
26+
const combined = stderrTail + chunk.toString('utf8');
27+
const lastNewline = combined.lastIndexOf('\n');
28+
const complete = lastNewline >= 0 ? combined.slice(0, lastNewline + 1) : '';
29+
stderrTail = lastNewline >= 0 ? combined.slice(lastNewline + 1) : combined;
30+
if (complete.length === 0) return;
31+
const filtered = complete
32+
.split('\n')
33+
.filter((line, index, arr) => {
34+
if (index === arr.length - 1 && line === '') return true;
35+
return !NOISY_STDERR.test(line);
36+
})
37+
.join('\n');
38+
if (filtered.length > 0) process.stderr.write(filtered);
39+
});
40+
child.stderr.on('end', () => {
41+
if (stderrTail.length > 0 && !NOISY_STDERR.test(stderrTail)) {
42+
process.stderr.write(stderrTail);
43+
}
44+
stderrTail = '';
45+
});
46+
1947
child.on('exit', (code, signal) => {
2048
if (signal) {
2149
process.kill(process.pid, signal);

apps/desktop/src/renderer/src/store.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -528,25 +528,47 @@ function artifactFromResult(
528528
return { type: source.type, content: source.content, prompt, message };
529529
}
530530

531+
// Per-designId serialization queue. A single generate run reaches this
532+
// function twice — once from applyGenerateResult → persistDesignState and once
533+
// from the agent_end handler → persistAgentRunSnapshot. Without serialization
534+
// both callers race on `snapshots.list`, see zero rows, and both write a fresh
535+
// parent-less 'initial' snapshot. Chaining per design collapses the race and
536+
// lets the content-based dedupe below drop the second write cleanly.
537+
const snapshotPersistLocks = new Map<string, Promise<unknown>>();
538+
531539
async function persistArtifactSnapshot(
532540
designId: string,
533541
artifact: PersistArtifact,
534542
): Promise<string | null> {
535543
if (!window.codesign) return null;
536-
// Look up the latest snapshot to chain parentId; the first generation in a
537-
// design has no parent and uses type='initial', subsequent ones use 'edit'.
538-
const existing = await window.codesign.snapshots.list(designId);
539-
const parent = existing[0] ?? null;
540-
const created = await window.codesign.snapshots.create({
541-
designId,
542-
parentId: parent?.id ?? null,
543-
type: parent ? 'edit' : 'initial',
544-
prompt: artifact.prompt,
545-
artifactType: toSnapshotArtifactType(artifact.type),
546-
artifactSource: artifact.content,
547-
...(artifact.message ? { message: artifact.message } : {}),
544+
const prior = snapshotPersistLocks.get(designId) ?? Promise.resolve();
545+
const run = prior.then(async () => {
546+
if (!window.codesign) return null;
547+
const existing = await window.codesign.snapshots.list(designId);
548+
const parent = existing[0] ?? null;
549+
// Dedupe by content: the agent_end path and the generate-result path both
550+
// fire at the tail of a run and often hold identical html. Returning the
551+
// existing id avoids duplicate rows without making either caller aware of
552+
// the other.
553+
if (parent !== null && parent.artifactSource === artifact.content) {
554+
return parent.id;
555+
}
556+
const created = await window.codesign.snapshots.create({
557+
designId,
558+
parentId: parent?.id ?? null,
559+
type: parent ? 'edit' : 'initial',
560+
prompt: artifact.prompt,
561+
artifactType: toSnapshotArtifactType(artifact.type),
562+
artifactSource: artifact.content,
563+
...(artifact.message ? { message: artifact.message } : {}),
564+
});
565+
return created?.id ?? null;
548566
});
549-
return created?.id ?? null;
567+
snapshotPersistLocks.set(
568+
designId,
569+
run.catch(() => {}),
570+
);
571+
return run;
550572
}
551573

552574
/**

0 commit comments

Comments
 (0)