diff --git a/CHANGELOG.md b/CHANGELOG.md index f11f305fd..a2de1ee36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Toolbar customizations now persist after closing and reopening a session window. (#1455) - Pasting copied rows no longer misplaces values when a cell contains a comma (such as a user agent string); each value stays in its own column, and a real NULL is kept distinct from the literal text "NULL". - BigQuery: switching to another table now loads its data right away, instead of leaving the grid empty until you close and reopen the tab. - Custom and OpenAI-compatible AI providers now work when the base URL already ends in `/v1`, instead of building a doubled `/v1/v1/` path that failed. (#1400) diff --git a/TablePro/Core/Services/Infrastructure/MainWindowToolbar.swift b/TablePro/Core/Services/Infrastructure/MainWindowToolbar.swift index 0749c9bc9..1ab57a5f1 100644 --- a/TablePro/Core/Services/Infrastructure/MainWindowToolbar.swift +++ b/TablePro/Core/Services/Infrastructure/MainWindowToolbar.swift @@ -13,6 +13,8 @@ import TableProPluginKit internal final class MainWindowToolbar: NSObject, NSToolbarDelegate { private static let lifecycleLogger = Logger(subsystem: "com.TablePro", category: "NativeTabLifecycle") + internal static let toolbarIdentifier = NSToolbar.Identifier("com.TablePro.main.toolbar") + weak var coordinator: MainContentCoordinator? internal let managedToolbar: NSToolbar @@ -26,14 +28,12 @@ internal final class MainWindowToolbar: NSObject, NSToolbarDelegate { internal init(coordinator: MainContentCoordinator) { self.coordinator = coordinator - // Unique identifier per toolbar instance prevents tab-group merging that would collapse - // all tabs into one toolbar and leave subsequent windows blank. - self.managedToolbar = NSToolbar(identifier: "com.TablePro.main.toolbar.\(UUID().uuidString)") + self.managedToolbar = NSToolbar(identifier: Self.toolbarIdentifier) super.init() self.managedToolbar.delegate = self self.managedToolbar.displayMode = .iconOnly self.managedToolbar.allowsUserCustomization = true - self.managedToolbar.autosavesConfiguration = false + self.managedToolbar.autosavesConfiguration = true } func invalidate() { diff --git a/TableProTests/Services/MainWindowToolbarValidationTests.swift b/TableProTests/Services/MainWindowToolbarValidationTests.swift index 6605be4f9..a8c408e74 100644 --- a/TableProTests/Services/MainWindowToolbarValidationTests.swift +++ b/TableProTests/Services/MainWindowToolbarValidationTests.swift @@ -142,4 +142,39 @@ struct MainWindowToolbarValidationTests { let unknown = NSToolbarItem.Identifier("com.test.unknown") #expect(MainWindowToolbar.isEnabled(itemIdentifier: unknown, context: context) == true) } + + @Test("Toolbar identifier is stable across instances so AppKit autosave can persist customizations") + func toolbarIdentifierIsStable() { + #expect(MainWindowToolbar.toolbarIdentifier.rawValue == "com.TablePro.main.toolbar") + } + + @Test("Toolbar is configured for user customization and autosave") + func toolbarConfigurationEnablesAutosave() { + let coordinator = makeCoordinator() + defer { coordinator.teardown() } + let owner = MainWindowToolbar(coordinator: coordinator) + #expect(owner.managedToolbar.identifier == MainWindowToolbar.toolbarIdentifier) + #expect(owner.managedToolbar.allowsUserCustomization == true) + #expect(owner.managedToolbar.autosavesConfiguration == true) + } + + @Test("Allowed item identifiers are a superset of defaults so restored items survive autosave") + func allowedItemIdentifiersAreSupersetOfDefaults() { + let coordinator = makeCoordinator() + defer { coordinator.teardown() } + let owner = MainWindowToolbar(coordinator: coordinator) + let toolbar = owner.managedToolbar + let defaults = Set(owner.toolbarDefaultItemIdentifiers(toolbar)) + let allowed = Set(owner.toolbarAllowedItemIdentifiers(toolbar)) + #expect(defaults.isSubset(of: allowed)) + } + + private func makeCoordinator() -> MainContentCoordinator { + MainContentCoordinator( + connection: TestFixtures.makeConnection(database: "db_a"), + tabManager: QueryTabManager(), + changeManager: DataChangeManager(), + toolbarState: ConnectionToolbarState() + ) + } }