diff --git a/CHANGELOG.md b/CHANGELOG.md index f00305f4..e010ad88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fix potential hang when coordinator deallocates during save +- Fix Cmd+W save not persisting data grid changes (sidebar edits intercepted save path) + ## [0.27.5] - 2026-04-06 ### Added diff --git a/TablePro/Core/Services/Infrastructure/WindowOpener.swift b/TablePro/Core/Services/Infrastructure/WindowOpener.swift index 35fc553f..c75ca71f 100644 --- a/TablePro/Core/Services/Infrastructure/WindowOpener.swift +++ b/TablePro/Core/Services/Infrastructure/WindowOpener.swift @@ -15,15 +15,17 @@ internal final class WindowOpener { internal static let shared = WindowOpener() - private var readyContinuation: CheckedContinuation? + private var readyContinuations: [CheckedContinuation] = [] /// Set on appear by ContentView, WelcomeViewModel, or ConnectionFormView. /// Safe to store — OpenWindowAction is app-scoped, not view-scoped. internal var openWindow: OpenWindowAction? { didSet { if openWindow != nil { - readyContinuation?.resume() - readyContinuation = nil + for continuation in readyContinuations { + continuation.resume() + } + readyContinuations.removeAll() } } } @@ -35,7 +37,7 @@ internal final class WindowOpener { if openWindow != nil { continuation.resume() } else { - readyContinuation = continuation + readyContinuations.append(continuation) } } } diff --git a/TablePro/Resources/Localizable.xcstrings b/TablePro/Resources/Localizable.xcstrings index 82f024f4..1050aeb5 100644 --- a/TablePro/Resources/Localizable.xcstrings +++ b/TablePro/Resources/Localizable.xcstrings @@ -5964,6 +5964,9 @@ } } } + }, + "Change Primary Key" : { + }, "Character Set" : { "localizations" : { @@ -10610,7 +10613,6 @@ } }, "Delete Column" : { - "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -10699,7 +10701,6 @@ } }, "Delete Foreign Key" : { - "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -10744,7 +10745,6 @@ } }, "Delete Index" : { - "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -10815,6 +10815,12 @@ } } } + }, + "Delete Row" : { + + }, + "Delete Rows" : { + }, "Delete Selected" : { "localizations" : { @@ -11689,6 +11695,12 @@ } } } + }, + "Edit Cell" : { + + }, + "Edit Column" : { + }, "Edit Connection" : { "localizations" : { @@ -11734,6 +11746,12 @@ } } } + }, + "Edit Foreign Key" : { + + }, + "Edit Index" : { + }, "Edit Profile..." : { "localizations" : { @@ -17115,6 +17133,12 @@ } } } + }, + "Insert Row" : { + + }, + "Insert Rows" : { + }, "INSERT Statement(s)" : { "localizations" : { @@ -28525,6 +28549,7 @@ } }, "Search for field..." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -28636,6 +28661,7 @@ } }, "Search shortcuts..." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -28658,6 +28684,7 @@ } }, "Search tables, views, databases..." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { diff --git a/TablePro/Views/Main/MainContentCommandActions.swift b/TablePro/Views/Main/MainContentCommandActions.swift index bc00dd86..82c6561b 100644 --- a/TablePro/Views/Main/MainContentCommandActions.swift +++ b/TablePro/Views/Main/MainContentCommandActions.swift @@ -358,22 +358,36 @@ final class MainContentCommandActions { return } - // Sidebar edits + // Data grid changes or pending table operations take priority + let hasDataChanges = coordinator.changeManager.hasChanges + || !pendingTruncates.wrappedValue.isEmpty + || !pendingDeletes.wrappedValue.isEmpty + if hasDataChanges { + let saved = await withCheckedContinuation { continuation in + coordinator.saveCompletionContinuation = continuation + saveChanges() + } + if saved { + performClose() + } + return + } + + // Sidebar-only edits (made directly in the inspector panel) if rightPanelState.editState.hasEdits { rightPanelState.onSave?() performClose() return } - // Data grid changes: await the async save via continuation - let saved = await withCheckedContinuation { continuation in - coordinator.saveCompletionContinuation = continuation - saveChanges() - } - - if saved { + // File save (query editor with source file) + if coordinator.tabManager.selectedTab?.isFileDirty == true { + saveFileToSourceURL() performClose() + return } + + performClose() } private func saveFileToSourceURL() { diff --git a/TablePro/Views/Main/MainContentCoordinator.swift b/TablePro/Views/Main/MainContentCoordinator.swift index 903af2bc..70aa8178 100644 --- a/TablePro/Views/Main/MainContentCoordinator.swift +++ b/TablePro/Views/Main/MainContentCoordinator.swift @@ -410,6 +410,9 @@ final class MainContentCoordinator { } deinit { + saveCompletionContinuation?.resume(returning: false) + saveCompletionContinuation = nil + let connectionId = connection.id let alreadyHandled = _didTeardown.withLock { $0 } || _teardownScheduled.withLock { $0 }