@@ -457,6 +457,36 @@ async function persistStartupLayoutSnapshotRecord(record) {
457457 db . close ( ) ;
458458}
459459
460+ async function deleteStartupLayoutSnapshotRecord ( fingerprint ) {
461+ if ( ! fingerprint ) {
462+ return ;
463+ }
464+
465+ if ( typeof localStorage !== 'undefined' ) {
466+ try {
467+ localStorage . removeItem ( `${ STARTUP_LAYOUT_SNAPSHOT_LS_PREFIX } ${ fingerprint } ` ) ;
468+ } catch ( _err ) {
469+ // Ignore localStorage cleanup failures.
470+ }
471+ }
472+
473+ const db = await openStartupLayoutSnapshotDb ( ) ;
474+ if ( ! db ) {
475+ return ;
476+ }
477+
478+ await new Promise ( ( resolve , reject ) => {
479+ const tx = db . transaction ( STARTUP_LAYOUT_SNAPSHOT_STORE_NAME , 'readwrite' ) ;
480+ const store = tx . objectStore ( STARTUP_LAYOUT_SNAPSHOT_STORE_NAME ) ;
481+ store . delete ( fingerprint ) ;
482+ tx . oncomplete = ( ) => resolve ( ) ;
483+ tx . onerror = ( ) => reject ( tx . error || new Error ( 'indexedDB delete failed' ) ) ;
484+ tx . onabort = ( ) => reject ( tx . error || new Error ( 'indexedDB delete aborted' ) ) ;
485+ } ) ;
486+
487+ db . close ( ) ;
488+ }
489+
460490function collectStartupLayoutSnapshotRecord ( reason = '' ) {
461491 if ( ! startupLayoutSnapshotState . fingerprint || ! Array . isArray ( nodes ) || nodes . length === 0 ) {
462492 return null ;
@@ -515,6 +545,44 @@ function validateStartupLayoutSnapshotRecord(record) {
515545 return { ok : false , reason : 'position-coverage-low' , coverage : Number ( coverage . toFixed ( 4 ) ) } ;
516546 }
517547
548+ const finitePositions = record . positions . filter ( ( item ) => (
549+ item &&
550+ Number . isFinite ( Number ( item . x ) ) &&
551+ Number . isFinite ( Number ( item . y ) )
552+ ) ) ;
553+ if ( expectedNodeCount >= 10 && finitePositions . length > 0 ) {
554+ let minX = Infinity ;
555+ let maxX = - Infinity ;
556+ let minY = Infinity ;
557+ let maxY = - Infinity ;
558+ const uniqueBuckets = new Set ( ) ;
559+
560+ for ( let index = 0 ; index < finitePositions . length ; index += 1 ) {
561+ const item = finitePositions [ index ] ;
562+ const x = Number ( item . x ) ;
563+ const y = Number ( item . y ) ;
564+ if ( x < minX ) minX = x ;
565+ if ( x > maxX ) maxX = x ;
566+ if ( y < minY ) minY = y ;
567+ if ( y > maxY ) maxY = y ;
568+ uniqueBuckets . add ( `${ Math . round ( x ) } :${ Math . round ( y ) } ` ) ;
569+ }
570+
571+ const spanX = maxX - minX ;
572+ const spanY = maxY - minY ;
573+ const uniqueRatio = uniqueBuckets . size / Math . max ( 1 , finitePositions . length ) ;
574+ if ( ( spanX < 48 && spanY < 48 ) || uniqueRatio < 0.12 ) {
575+ return {
576+ ok : false ,
577+ reason : 'degenerate-layout' ,
578+ purge : true ,
579+ spanX : Number ( spanX . toFixed ( 2 ) ) ,
580+ spanY : Number ( spanY . toFixed ( 2 ) ) ,
581+ uniqueRatio : Number ( uniqueRatio . toFixed ( 4 ) ) ,
582+ } ;
583+ }
584+ }
585+
518586 return {
519587 ok : true ,
520588 coverage : Number ( coverage . toFixed ( 4 ) ) ,
@@ -568,8 +636,16 @@ function maybeApplyStartupWarmSnapshot(trigger = '') {
568636 recordFingerprint : record . fingerprint || null ,
569637 recordNodeCount : record . nodeCount || 0 ,
570638 recordEdgeCount : record . edgeCount || 0 ,
571- positionCount : Array . isArray ( record . positions ) ? record . positions . length : 0
639+ positionCount : Array . isArray ( record . positions ) ? record . positions . length : 0 ,
640+ spanX : validation . spanX ,
641+ spanY : validation . spanY ,
642+ uniqueRatio : validation . uniqueRatio ,
572643 } ) ;
644+ if ( validation . purge === true && record . fingerprint ) {
645+ deleteStartupLayoutSnapshotRecord ( record . fingerprint ) . catch ( ( error ) => {
646+ console . warn ( '[Startup Warm Snapshot] Failed to purge invalid snapshot record:' , error && error . message ? error . message : String ( error ) ) ;
647+ } ) ;
648+ }
573649 startupLayoutSnapshotState . pendingRecord = null ;
574650 return false ;
575651 }
0 commit comments