@@ -291,7 +291,32 @@ function attachYjsProvider() {
291291 if ( constants . debugging ) console . log ( 'Collab: Initial sync data already exists, applying it now.' ) ;
292292 const syncData = constants . mutableRefs . yProjectDataSync . get ( 'sync' ) ;
293293 if ( syncData && syncData . data ) {
294+ let isCorrupt = false ;
295+ for ( const targetData of syncData . data ) {
296+ const blocksObject = JSON . parse ( targetData . blockData ) ;
297+ if ( helper . hasCircularDependency ( blocksObject ) ) {
298+ isCorrupt = true ;
299+ break ;
300+ }
301+ }
294302
303+ if ( isCorrupt ) {
304+ console . error ( "Collab FATAL: Received corrupt master copy with circular dependency. Aborting project load." ) ;
305+ collabUI . hideSyncingPopup ( ) ; // Hide the "syncing" message
306+
307+ const popup = document . createElement ( 'div' ) ;
308+ popup . className = 'collab-popup' ;
309+ popup . innerHTML = `
310+ <div class="collab-popup-content">
311+ <h2>Collaboration Error</h2>
312+ <p>The project data from the session is corrupt and cannot be loaded. This session is in an unrecoverable state.</p>
313+ <p>Please report this to @CodeTorch.</p>
314+ </div>
315+ ` ;
316+ document . body . appendChild ( popup ) ;
317+
318+ return ; // Abort applying the sync data.
319+ }
295320 // IMPORTANT: Clear all existing blocks from all targets in the VM first.
296321 // This prevents duplication and ensures a clean slate before applying remote blocks.
297322 constants . mutableRefs . vm . runtime . targets . forEach ( t => {
@@ -597,6 +622,96 @@ function attachYjsProvider() {
597622 }
598623 // --- 'savedProject' Trigger (for project save operations) ---
599624 else if ( detail . triggerId === 'savedProject' ) {
625+ let isCorrupt = false ;
626+ // Loop through every target (Sprite, Stage) in the project.
627+ for ( const target of constants . mutableRefs . vm . runtime . targets ) {
628+ const blocksToValidate = target . blocks . _blocks ;
629+ if ( helper . hasCircularDependency ( blocksToValidate ) ) {
630+ console . error ( `Collab FATAL: Circular dependency detected in target "${ target . getName ( ) } ". Aborting sync.` ) ;
631+ isCorrupt = true ;
632+ break ; // Stop checking as soon as one corrupt target is found.
633+ }
634+ }
635+
636+ if ( isCorrupt ) {
637+ // Create the main popup container.
638+ const popup = document . createElement ( 'div' ) ;
639+ popup . className = 'collab-popup' ;
640+ popup . innerHTML = `
641+ <div class="collab-popup-content">
642+ <h2>Project Sync Error</h2>
643+ <p>An invalid block connection (a loop) was detected in your project. This can sometimes happen after complex block movements.</p>
644+ <p>To fix this, the page needs to be reloaded. Your work should be saved up to this point.</p>
645+ <p>Please send the debug log to @CodeTorch.</p>
646+ <button id="collab-reload-button">Reload Project</button>
647+ </div>
648+ ` ;
649+
650+ // Get a reference to the content area of the popup to append buttons.
651+ const popupContent = popup . querySelector ( '.collab-popup-content' ) ;
652+
653+ // --- Button 1: Download Project (Unchanged) ---
654+ const downloadProjectButton = document . createElement ( 'button' ) ;
655+ downloadProjectButton . innerText = 'Download Project' ;
656+ downloadProjectButton . addEventListener ( 'click' , ( ) => {
657+ document . querySelectorAll ( '[class*="menu-bar_menu-bar-item_"]' ) [ 1 ] . click ( ) ;
658+ setTimeout ( ( ) => {
659+ document . querySelectorAll ( 'li[class*="menu_menu-item_"]' ) [ 3 ] . click ( ) ;
660+ } , 500 ) ;
661+ } ) ;
662+ popupContent . appendChild ( downloadProjectButton ) ;
663+
664+ // --- Button 2 (NEW): Download Debug Log ---
665+ const downloadLogButton = document . createElement ( 'button' ) ;
666+ downloadLogButton . innerText = 'Download Debug Log' ;
667+ downloadLogButton . addEventListener ( 'click' , ( ) => {
668+ try {
669+ // Get the current state of the Yjs event arrays. .toArray() converts them to standard JS arrays.
670+ const yEventsData = constants . mutableRefs . yEvents . toArray ( ) ;
671+ const yProjectEventsData = constants . mutableRefs . yProjectEvents . toArray ( ) ;
672+
673+ // Format the data into a readable string with headers.
674+ const logContent = `Collaboration Addon Debug Log\n` +
675+ `Timestamp: ${ new Date ( ) . toISOString ( ) } \n\n` +
676+ `--- YEvents (Block Actions) ---\n\n` +
677+ `${ JSON . stringify ( yEventsData , null , 2 ) } \n\n` +
678+ `--- YProjectEvents (Asset & Project Actions) ---\n\n` +
679+ `${ JSON . stringify ( yProjectEventsData , null , 2 ) } ` ;
680+
681+ // Create a Blob from the string content.
682+ const blob = new Blob ( [ logContent ] , { type : 'text/plain;charset=utf-8' } ) ;
683+
684+ // Create a temporary link element to trigger the download.
685+ const url = URL . createObjectURL ( blob ) ;
686+ const a = document . createElement ( 'a' ) ;
687+ a . style . display = 'none' ;
688+ a . href = url ;
689+ a . download = 'collaboration_debug_log.txt' ;
690+
691+ document . body . appendChild ( a ) ;
692+ a . click ( ) ;
693+
694+ // Clean up by revoking the URL and removing the link.
695+ window . URL . revokeObjectURL ( url ) ;
696+ document . body . removeChild ( a ) ;
697+ } catch ( e ) {
698+ console . error ( "Collab: Failed to generate or download debug log." , e ) ;
699+ alert ( "Sorry, the debug log could not be created." ) ;
700+ }
701+ } ) ;
702+ popupContent . appendChild ( downloadLogButton ) ;
703+
704+ // Display the popup.
705+ document . body . appendChild ( popup ) ;
706+
707+ // Add the listener for the "Reload" button (which was created via innerHTML).
708+ document . getElementById ( 'collab-reload-button' ) . addEventListener ( 'click' , ( ) => {
709+ window . location . reload ( ) ;
710+ } ) ;
711+
712+ return ; // Abort the save/sync process.
713+ }
714+
600715 // Get the current project data for a full sync snapshot.
601716 const projectDataSync = {
602717 type : 'projectDataSync' ,
0 commit comments