From 19f34f9979c0c622962047968c130a3cc2a7865f Mon Sep 17 00:00:00 2001 From: Dustin Hilgaertner Date: Fri, 1 May 2026 23:13:52 -0500 Subject: [PATCH] Fix Settings workspace add/edit sheets silently no-op-ing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 8827cf8 made the Settings window app-modal via NSApp.runModal, which takes over event dispatch and prevents SwiftUI .sheet(...) from presenting on the modal-hosted NSWindow. Result: clicking the pencil or Add buttons in Settings → Workspaces did nothing. Drop the modal session and present Settings as a regular key window, mirroring showAbout(). Re-add the existing-window short-circuit so repeated Cmd+, brings the open Settings window forward, move settingsWindow cleanup into the willClose observer, and store the observer token on AppDelegate so the closure doesn't capture a mutable local var (which Swift 6 flags as a sendability warning). Closes #236 Co-Authored-By: Claude Opus 4.7 (1M context) --- Sources/Crow/App/AppDelegate.swift | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/Sources/Crow/App/AppDelegate.swift b/Sources/Crow/App/AppDelegate.swift index dd82936..1988fa2 100644 --- a/Sources/Crow/App/AppDelegate.swift +++ b/Sources/Crow/App/AppDelegate.swift @@ -11,6 +11,7 @@ import CrowTelemetry final class AppDelegate: NSObject, NSApplicationDelegate { private var window: NSWindow? private var settingsWindow: NSWindow? + private var settingsWindowCloseObserver: NSObjectProtocol? private var aboutWindow: NSWindow? private let appState = AppState() private var store: JSONStore? @@ -589,9 +590,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate { @objc private func showSettings() { guard let devRoot, let appConfig else { return } - // Settings is app-modal — the user must dismiss it before returning - // to the main app. (`NSApp.runModal(for:)` blocks until stopModal - // is called; we trigger that on the window's willClose notification.) + if let existing = settingsWindow { + existing.makeKeyAndOrderFront(nil) + return + } + let settingsView = SettingsView( appState: appState, devRoot: devRoot, @@ -617,21 +620,26 @@ final class AppDelegate: NSObject, NSApplicationDelegate { win.isReleasedWhenClosed = false win.contentView = hostingView win.center() + win.makeKeyAndOrderFront(nil) self.settingsWindow = win - let token = NotificationCenter.default.addObserver( + settingsWindowCloseObserver = NotificationCenter.default.addObserver( forName: NSWindow.willCloseNotification, object: win, queue: .main - ) { _ in - // .main queue dispatches to the main thread, but Swift 6 doesn't - // statically know that's the MainActor's executor. NSApp is + ) { [weak self] _ in + // .main queue dispatches on the main thread, but Swift 6 doesn't + // statically know that's the MainActor's executor. AppDelegate is // MainActor-isolated; assume isolation explicitly. - MainActor.assumeIsolated { NSApp.stopModal() } + MainActor.assumeIsolated { + guard let self else { return } + self.settingsWindow = nil + if let token = self.settingsWindowCloseObserver { + NotificationCenter.default.removeObserver(token) + self.settingsWindowCloseObserver = nil + } + } } - NSApp.runModal(for: win) - NotificationCenter.default.removeObserver(token) - self.settingsWindow = nil } private func saveSettings(devRoot: String, config: AppConfig) {