@@ -42,56 +42,68 @@ class WindowManager {
4242 this . saverInterval = DEFAULT_SAVE_INTERVAL ;
4343 this . isSaving = false ;
4444 this . isQuitting = false ;
45- this . isClosingLastWindow = false
4645 this . shutdownInProgress = false ;
47- this . saveQueue = Promise . resolve ( ) ;
4846 this . finalSaveCompleted = false ;
49- this . isClearingState = false ;
47+ this . isClearingState = false ;
48+ this . saveQueue = Promise . resolve ( ) ;
5049 this . registerListeners ( ) ;
5150
52- // Add signal handlers for graceful shutdown
53- process . on ( 'SIGINT' , this . handleGracefulShutdown . bind ( this ) ) ;
54- process . on ( 'SIGTERM' , this . handleGracefulShutdown . bind ( this ) ) ;
55-
56- // Enhanced app event handlers for proper UI quit handling
57- app . on ( 'before-quit' , ( event ) => {
58- // If we're clearing state (windows closed), don't save
59- if ( this . isClearingState ) {
60- return ;
61- }
62-
63- if ( ! this . finalSaveCompleted ) {
64- event . preventDefault ( ) ;
65-
66- if ( ! this . isQuitting && ! this . shutdownInProgress ) {
67- this . handleGracefulShutdown ( ) ;
51+ // Treat Ctrl+C / SIGTERM as explicit quits in dev:
52+ if ( ! app . isPackaged ) {
53+ const handleSignal = ( signal ) => {
54+ if ( this . shutdownInProgress ) {
55+ console . log ( `${ signal } received while shutdown already in progress – ignoring.` ) ;
56+ return ;
6857 }
69- }
70- } ) ;
71-
72- // Handle when all windows are closed (UI quit)
73- app . on ( 'window-all-closed' , async ( ) => {
74- if ( ! this . isQuitting && ! this . shutdownInProgress ) {
75- // On macOS, keep app running, on other platforms quit
76- // Clear saved state since user closed all windows (didn't quit)
77- this . isClearingState = true ;
58+
7859 this . isQuitting = true ;
7960 this . shutdownInProgress = true ;
80- this . finalSaveCompleted = true ; // Prevent before-quit from trying to save
8161 this . stopSaver ( ) ;
82- await this . clearSavedState ( ) ;
83-
84- if ( process . platform !== 'darwin' ) {
85- app . quit ( ) ;
86- }
87- }
88- } ) ;
62+
63+ console . log ( `${ signal } received – saving session state WITHOUT clearing it, then exiting.` ) ;
64+
65+ ( async ( ) => {
66+ try {
67+ await this . saveCompleteState ( ) ;
68+ } catch ( error ) {
69+ console . error ( `Error during ${ signal } shutdown save:` , error ) ;
70+ } finally {
71+ this . finalSaveCompleted = true ;
72+ app . exit ( 0 ) ;
73+ }
74+ } ) ( ) ;
75+ } ;
76+
77+ process . on ( 'SIGINT' , ( ) => handleSignal ( 'SIGINT' ) ) ;
78+ process . on ( 'SIGTERM' , ( ) => handleSignal ( 'SIGTERM' ) ) ;
79+ }
8980
90- // Handle app activation (macOS specific)
91- app . on ( 'activate' , ( ) => {
92- if ( this . windows . size === 0 && ! this . isQuitting ) {
93- this . open ( ) ;
81+ app . on ( 'before-quit' , ( event ) => {
82+ // Avoid re-entering if something calls app.quit() again
83+ if ( this . shutdownInProgress ) {
84+ console . log ( 'before-quit: shutdown already in progress, ignoring.' ) ;
85+ return ;
9486 }
87+
88+ console . log ( 'before-quit: performing final session save (without clearing).' ) ;
89+ this . isQuitting = true ;
90+ this . shutdownInProgress = true ;
91+ this . stopSaver ( ) ;
92+
93+ // Prevent the default quit, we'll exit manually after the async save
94+ event . preventDefault ( ) ;
95+
96+ ( async ( ) => {
97+ try {
98+ await this . saveCompleteState ( ) ;
99+ } catch ( error ) {
100+ console . error ( 'Error during final save in before-quit:' , error ) ;
101+ } finally {
102+ this . finalSaveCompleted = true ;
103+ // Important: app.exit() does not re-emit 'before-quit'
104+ app . exit ( 0 ) ;
105+ }
106+ } ) ( ) ;
95107 } ) ;
96108 }
97109
@@ -100,25 +112,22 @@ class WindowManager {
100112 this . open ( ) ;
101113 } ) ;
102114
103- // Add quit handler for UI quit button
115+ // Explicit quit from UI (custom Quit button, etc.).
116+ // We just call app.quit(); app.on('before-quit') will clear session files.
104117 ipcMain . on ( "quit-app" , ( ) => {
105118 console . log ( "Quit app requested from UI" ) ;
106- this . handleGracefulShutdown ( ) ;
119+ this . isQuitting = true ;
120+ app . quit ( ) ;
107121 } ) ;
108122
109- // Add window close handler that checks if it's the last window
123+ // Close the current window and let Electron/main.js decide
124+ // whether the app should quit (non-macOS) or stay alive (macOS).
110125 ipcMain . on ( "close-window" , ( event ) => {
111126 const senderId = event . sender . id ;
112127 const window = this . findWindowBySenderId ( senderId ) ;
113128
114129 if ( window ) {
115- // If this is the last window, trigger graceful shutdown
116- if ( this . windows . size === 1 && ! this . isQuitting ) {
117- this . handleGracefulShutdown ( ) ;
118- } else {
119- // Otherwise just close this window normally
120- window . window . close ( ) ;
121- }
130+ window . window . close ( ) ;
122131 }
123132 } ) ;
124133
@@ -447,7 +456,6 @@ class WindowManager {
447456
448457 // If this is the last window, mark that we're clearing state
449458 if ( this . windows . size === 1 ) {
450- this . isClearingState = true ;
451459 this . stopSaver ( ) ;
452460 }
453461 } ) ;
@@ -498,48 +506,6 @@ class WindowManager {
498506 console . error ( "Error clearing saved session state:" , error ) ;
499507 }
500508 }
501- async handleGracefulShutdown ( ) {
502- if ( this . shutdownInProgress ) {
503- console . log ( 'Shutdown already in progress, ignoring additional signals' ) ;
504- return ;
505- }
506-
507- // Set flags IMMEDIATELY to block window closes and new saves
508- this . shutdownInProgress = true ;
509- this . isQuitting = true ;
510- console . log ( 'Graceful shutdown initiated...' ) ;
511-
512- // Stop the periodic saver immediately
513- this . stopSaver ( ) ;
514-
515- const forceExitTimeout = setTimeout ( ( ) => {
516- console . log ( 'Forced exit after timeout' ) ;
517- process . exit ( 1 ) ;
518- } , 8000 ) ;
519-
520- try {
521- console . log ( 'Saving final state before exit...' ) ;
522- await this . saveCompleteState ( ) ;
523- this . finalSaveCompleted = true ;
524- console . log ( 'State saved successfully. Now safe to close windows.' ) ;
525-
526- // ONLY AFTER successful save, close all windows
527- console . log ( 'Destroying windows...' ) ;
528- const windowsToClose = Array . from ( this . windows ) ;
529- for ( const window of windowsToClose ) {
530- if ( ! window . window . isDestroyed ( ) ) {
531- window . window . destroy ( ) ;
532- }
533- }
534-
535- } catch ( error ) {
536- console . error ( 'Error during shutdown:' , error ) ;
537- } finally {
538- clearTimeout ( forceExitTimeout ) ;
539- console . log ( 'Exiting application...' ) ;
540- app . quit ( ) ;
541- }
542- }
543509
544510 async saveCompleteState ( ) {
545511 // Save both window positions/sizes and tab data
@@ -567,8 +533,26 @@ class WindowManager {
567533
568534 console . log ( `Found ${ validWindows . length } valid windows to save` ) ;
569535
536+ // If there are no live windows…
570537 if ( validWindows . length === 0 ) {
571- console . warn ( 'No valid windows to save!' ) ;
538+ // When the app is quitting (Cmd+Q, menu Quit, SIGINT, etc.), we MUST NOT clear
539+ // the session files. We just leave whatever was last saved on disk.
540+ if ( this . isQuitting || this . shutdownInProgress ) {
541+ console . warn ( 'No valid windows to save during quit – leaving window state file untouched.' ) ;
542+ return ;
543+ }
544+
545+ // But if the app is still running and the user has closed the last window,
546+ // we DO clear the file so the next launch starts fresh
547+ console . warn ( 'No valid windows to save – clearing window state file so session does not restore.' ) ;
548+ try {
549+ const tempPath = PERSIST_FILE + ".tmp" ;
550+ await fs . outputJson ( tempPath , [ ] , { spaces : 2 } ) ;
551+ await fs . move ( tempPath , PERSIST_FILE , { overwrite : true } ) ;
552+ console . log ( `Wrote empty window state to ${ PERSIST_FILE } ` ) ;
553+ } catch ( error ) {
554+ console . error ( "Error clearing window state file:" , error ) ;
555+ }
572556 return ;
573557 }
574558
@@ -605,9 +589,17 @@ class WindowManager {
605589 }
606590 }
607591
592+ // If, for some reason, we ended up with nothing, also clear the file
608593 if ( windowStates . length === 0 ) {
609- console . error ( 'Failed to save any window states!' ) ;
610- // Don't write an empty file - keep the existing one
594+ console . warn ( 'No window states collected during save – clearing window state file.' ) ;
595+ try {
596+ const tempPath = PERSIST_FILE + ".tmp" ;
597+ await fs . outputJson ( tempPath , [ ] , { spaces : 2 } ) ;
598+ await fs . move ( tempPath , PERSIST_FILE , { overwrite : true } ) ;
599+ console . log ( `Wrote empty window state to ${ PERSIST_FILE } ` ) ;
600+ } catch ( error ) {
601+ console . error ( "Error clearing window state file:" , error ) ;
602+ }
611603 return ;
612604 }
613605
@@ -627,13 +619,28 @@ class WindowManager {
627619
628620 try {
629621 const allTabsData = await this . getTabs ( ) ;
622+ const TABS_FILE = path . join ( USER_DATA_PATH , "tabs.json" ) ;
630623
624+ // 🔑 If there are no tabs/windows, clear the tabs file
631625 if ( ! allTabsData || Object . keys ( allTabsData ) . length === 0 ) {
632- console . warn ( "No tabs data to save - keeping existing file" ) ;
626+ // Same logic as window states: if we're quitting, do NOT clear the file.
627+ if ( this . isQuitting || this . shutdownInProgress ) {
628+ console . warn ( "No tabs data to save during quit – leaving tabs file untouched." ) ;
629+ return ;
630+ }
631+
632+ console . warn ( "No tabs data to save – clearing tabs file so session does not restore." ) ;
633+ try {
634+ const tempPath = TABS_FILE + ".tmp" ;
635+ await fs . outputJson ( tempPath , { } , { spaces : 2 } ) ;
636+ await fs . move ( tempPath , TABS_FILE , { overwrite : true } ) ;
637+ console . log ( `Wrote empty tabs data to ${ TABS_FILE } ` ) ;
638+ } catch ( error ) {
639+ console . error ( "Error clearing tabs data file:" , error ) ;
640+ }
633641 return ;
634642 }
635643
636- const TABS_FILE = path . join ( USER_DATA_PATH , "tabs.json" ) ;
637644 const windowCount = Object . keys ( allTabsData ) . length ;
638645
639646 console . log ( `Saving tabs for ${ windowCount } windows...` ) ;
@@ -650,6 +657,19 @@ class WindowManager {
650657 }
651658
652659 async saveOpened ( forceSave = false ) {
660+ // Prevent saving when the last window has no tabs (closing last tab)
661+ if (
662+ this . windows . size === 1 &&
663+ ! this . isQuitting &&
664+ ! this . shutdownInProgress
665+ ) {
666+ const onlyWindow = [ ...this . windows ] [ 0 ] ;
667+ if ( onlyWindow && onlyWindow . savedTabs === null ) {
668+ console . log ( "Preventing save: last window has no tabs (closing last tab)." ) ;
669+ return ;
670+ }
671+ }
672+
653673 //Never save(periodic saves) during shutdown
654674 if ( this . shutdownInProgress ) {
655675 console . log ( "Shutdown in progress - saveOpened blocked" ) ;
0 commit comments