@@ -30,6 +30,8 @@ internal final class TabPersistenceCoordinator {
3030 private static let logger = Logger ( subsystem: " com.TablePro " , category: " NativeTabLifecycle " )
3131 let connectionId : UUID
3232
33+ @ObservationIgnored private var saveTask : Task < Void , Never > ?
34+
3335 init ( connectionId: UUID ) {
3436 self . connectionId = connectionId
3537 }
@@ -45,43 +47,25 @@ internal final class TabPersistenceCoordinator {
4547 return
4648 }
4749 let persisted = nonPreviewTabs. map { convertToPersistedTab ( $0) }
48- let connId = connectionId
4950 let normalizedSelectedId = nonPreviewTabs. contains ( where: { $0. id == selectedTabId } )
5051 ? selectedTabId : nonPreviewTabs. first? . id
51- Self . logger. debug ( " [persist] saveNow queued tabCount= \( nonPreviewTabs. count) connId= \( connId, privacy: . public) " )
52-
53- Task {
54- let t0 = Date ( )
55- do {
56- try await TabDiskActor . shared. save ( connectionId: connId, tabs: persisted, selectedTabId: normalizedSelectedId)
57- Self . logger. debug ( " [persist] saveNow written tabCount= \( persisted. count) connId= \( connId, privacy: . public) ms= \( Int ( Date ( ) . timeIntervalSince ( t0) * 1_000 ) ) " )
58- } catch {
59- TabDiskActor . logSaveError ( connectionId: connId, error: error)
60- }
61- }
52+ scheduleSave ( tabs: persisted, selectedTabId: normalizedSelectedId)
6253 }
6354
6455 /// Save pre-aggregated tabs for the quit path, where the caller has already
6556 /// collected and converted tabs from all windows for this connection.
6657 internal func saveNow( persistedTabs: [ PersistedTab ] , selectedTabId: UUID ? ) {
67- let connId = connectionId
68- let selectedId = selectedTabId
69-
70- Task {
71- do {
72- try await TabDiskActor . shared. save ( connectionId: connId, tabs: persistedTabs, selectedTabId: selectedId)
73- } catch {
74- TabDiskActor . logSaveError ( connectionId: connId, error: error)
75- }
76- }
58+ scheduleSave ( tabs: persistedTabs, selectedTabId: selectedTabId)
7759 }
7860
7961 /// Synchronous save for `applicationWillTerminate` where no run loop
8062 /// remains to service async Tasks. Bypasses the actor and writes directly.
8163 internal func saveNowSync( tabs: [ QueryTab ] , selectedTabId: UUID ? ) {
8264 let nonPreviewTabs = tabs. filter { !$0. isPreview }
8365 guard !nonPreviewTabs. isEmpty else {
84- TabDiskActor . saveSync ( connectionId: connectionId, tabs: [ ] , selectedTabId: nil )
66+ saveTask? . cancel ( )
67+ saveTask = nil
68+ TabDiskActor . clearSync ( connectionId: connectionId)
8569 return
8670 }
8771 let persisted = nonPreviewTabs. map { convertToPersistedTab ( $0) }
@@ -94,12 +78,37 @@ internal final class TabPersistenceCoordinator {
9478
9579 /// Clear all saved state for this connection (user closed all tabs).
9680 internal func clearSavedState( ) {
81+ saveTask? . cancel ( )
82+ saveTask = nil
9783 let connId = connectionId
9884 Task {
9985 await TabDiskActor . shared. clear ( connectionId: connId)
10086 }
10187 }
10288
89+ // MARK: - Private save scheduling
90+
91+ private func scheduleSave( tabs: [ PersistedTab ] , selectedTabId: UUID ? ) {
92+ saveTask? . cancel ( )
93+ let connId = connectionId
94+ let tabsCopy = tabs
95+ let selectedId = selectedTabId
96+ Self . logger. debug ( " [persist] saveNow queued tabCount= \( tabsCopy. count) connId= \( connId, privacy: . public) " )
97+
98+ saveTask = Task {
99+ guard !Task. isCancelled else { return }
100+ let t0 = Date ( )
101+ do {
102+ try await TabDiskActor . shared. save ( connectionId: connId, tabs: tabsCopy, selectedTabId: selectedId)
103+ Self . logger. debug ( " [persist] saveNow written tabCount= \( tabsCopy. count) connId= \( connId, privacy: . public) ms= \( Int ( Date ( ) . timeIntervalSince ( t0) * 1_000 ) ) " )
104+ } catch is CancellationError {
105+ return
106+ } catch {
107+ Self . logger. fault ( " Failed to save tab state for connection \( connId, privacy: . public) : \( error. localizedDescription, privacy: . public) " )
108+ }
109+ }
110+ }
111+
103112 // MARK: - Restore
104113
105114 /// Restore tabs from disk. Called once at window creation.
0 commit comments