@@ -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+
531539async 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