From b00da7b702b295c574e15635b8894f15af36dcb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Mon, 4 May 2026 15:49:48 +0700 Subject: [PATCH] fix(windows): persist welcome, connection form, and feedback window positions across launches --- CHANGELOG.md | 1 + .../ConnectionFormWindowFactory.swift | 3 ++- .../Infrastructure/TabWindowController.swift | 2 +- .../Infrastructure/WelcomeWindowFactory.swift | 3 ++- TablePro/Extensions/NSWindow+FrameAutosave.swift | 15 +++++++++++++++ .../Views/Feedback/FeedbackWindowController.swift | 3 ++- 6 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 TablePro/Extensions/NSWindow+FrameAutosave.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f27bc305..75d5e771f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Welcome window, connection form, and feedback panel now remember their position and size across launches. Previously these always reopened centered on screen because `setFrameAutosaveName` was never set on the underlying `NSWindow`/`NSPanel`. They now use the same native AppKit frame autosave mechanism the main editor and Settings windows already used. - MCP: GET `/mcp` now opens a real SSE notification stream. Previously the GET path was routed through the request dispatcher, which had no handler for it, so the connection was closed immediately and `notifications/progress` events were dropped. - MCP: concurrent tool calls no longer serialize at the dispatcher loop. Each exchange is dispatched in its own child task while session-state guards still serialize per-session work. - MCP: server validates the `protocolVersion` requested in `initialize` against a supported set and rejects unknown versions with `-32600 invalid_request` instead of silently echoing back whatever the client sent. diff --git a/TablePro/Core/Services/Infrastructure/ConnectionFormWindowFactory.swift b/TablePro/Core/Services/Infrastructure/ConnectionFormWindowFactory.swift index 4c55f775e..ab58e8179 100644 --- a/TablePro/Core/Services/Infrastructure/ConnectionFormWindowFactory.swift +++ b/TablePro/Core/Services/Infrastructure/ConnectionFormWindowFactory.swift @@ -9,6 +9,7 @@ import SwiftUI @MainActor internal enum ConnectionFormWindowFactory { private static let baseIdentifier = "connection-form" + private static let autosaveName: NSWindow.FrameAutosaveName = "ConnectionFormWindow" internal static func openOrFront(connectionId: UUID? = nil) { if let existing = existingWindow(for: connectionId) { @@ -53,8 +54,8 @@ internal enum ConnectionFormWindowFactory { window.standardWindowButton(.zoomButton)?.isEnabled = false window.styleMask.remove(.miniaturizable) window.collectionBehavior.insert(.fullScreenNone) - window.center() window.isReleasedWhenClosed = false + window.applyAutosaveName(autosaveName) return window } } diff --git a/TablePro/Core/Services/Infrastructure/TabWindowController.swift b/TablePro/Core/Services/Infrastructure/TabWindowController.swift index 7048dbe73..aac5df115 100644 --- a/TablePro/Core/Services/Infrastructure/TabWindowController.swift +++ b/TablePro/Core/Services/Infrastructure/TabWindowController.swift @@ -74,7 +74,7 @@ internal final class TabWindowController: NSWindowController, NSWindowDelegate { window.identifier = NSUserInterfaceItemIdentifier("main") window.minSize = NSSize(width: 720, height: 480) window.isRestorable = false - window.setFrameAutosaveName("MainEditorWindow") + window.applyAutosaveName("MainEditorWindow") window.toolbarStyle = .unified // Hide the window title ("Query 1 / TablePro") embedded in the unified // toolbar — otherwise it claims leading space and pushes our navigation diff --git a/TablePro/Core/Services/Infrastructure/WelcomeWindowFactory.swift b/TablePro/Core/Services/Infrastructure/WelcomeWindowFactory.swift index e64d00315..7ef0e3866 100644 --- a/TablePro/Core/Services/Infrastructure/WelcomeWindowFactory.swift +++ b/TablePro/Core/Services/Infrastructure/WelcomeWindowFactory.swift @@ -9,6 +9,7 @@ import SwiftUI @MainActor internal enum WelcomeWindowFactory { private static let identifier = NSUserInterfaceItemIdentifier("welcome") + private static let autosaveName: NSWindow.FrameAutosaveName = "WelcomeWindow" private static let contentSize = NSSize(width: 700, height: 450) internal static func openOrFront() { @@ -48,8 +49,8 @@ internal enum WelcomeWindowFactory { window.standardWindowButton(.zoomButton)?.isHidden = true window.collectionBehavior.insert(.fullScreenNone) window.setContentSize(contentSize) - window.center() window.isReleasedWhenClosed = false + window.applyAutosaveName(autosaveName) return window } } diff --git a/TablePro/Extensions/NSWindow+FrameAutosave.swift b/TablePro/Extensions/NSWindow+FrameAutosave.swift new file mode 100644 index 000000000..da9eb8ad2 --- /dev/null +++ b/TablePro/Extensions/NSWindow+FrameAutosave.swift @@ -0,0 +1,15 @@ +// +// NSWindow+FrameAutosave.swift +// TablePro +// + +import AppKit + +extension NSWindow { + func applyAutosaveName(_ name: NSWindow.FrameAutosaveName) { + setFrameAutosaveName(name) + if !setFrameUsingName(name) { + center() + } + } +} diff --git a/TablePro/Views/Feedback/FeedbackWindowController.swift b/TablePro/Views/Feedback/FeedbackWindowController.swift index ba41b4789..0b1e569e9 100644 --- a/TablePro/Views/Feedback/FeedbackWindowController.swift +++ b/TablePro/Views/Feedback/FeedbackWindowController.swift @@ -9,6 +9,7 @@ import SwiftUI @MainActor final class FeedbackWindowController { static let shared = FeedbackWindowController() + private static let autosaveName: NSWindow.FrameAutosaveName = "FeedbackWindow" private var panel: NSPanel? private var closeObserver: NSObjectProtocol? private let viewModel = FeedbackViewModel() @@ -42,7 +43,7 @@ final class FeedbackWindowController { panel.standardWindowButton(.miniaturizeButton)?.isHidden = true panel.standardWindowButton(.zoomButton)?.isHidden = true panel.contentView = hostingView - panel.center() + panel.applyAutosaveName(Self.autosaveName) panel.makeKeyAndOrderFront(nil) self.panel = panel