From 4913d171be11ee4133ce7ddfb6a6e5a806a48d8f Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sat, 25 Apr 2026 20:46:07 -0700 Subject: [PATCH 1/9] Guard against duplicate restored main windows --- Sources/AppDelegate.swift | 16 ++++++++++++++++ Sources/ContentView.swift | 1 + 2 files changed, 17 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 803f2434a9f4..4959d05a63b5 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -3299,6 +3299,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent existing.cmuxConfigStore = cmuxConfigStore } } else if let existing = mainWindowContexts.values.first(where: { $0.windowId == windowId }) { + if let existingWindow = existing.window, + existingWindow !== window, + existingWindow.isVisible { +#if DEBUG + cmuxDebugLog( + "mainWindow.register.duplicateIgnored windowId=\(String(windowId.uuidString.prefix(8))) " + + "existing={\(debugWindowToken(existingWindow))} duplicate={\(debugWindowToken(window))}" + ) +#endif + window.orderOut(nil) + window.close() + return + } existing.window = window if let cmuxConfigStore { existing.cmuxConfigStore = cmuxConfigStore @@ -5794,6 +5807,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent window.title = "" window.titleVisibility = .hidden window.titlebarAppearsTransparent = true + // cmux persists and restores main windows itself. Disable AppKit window + // restoration so the OS cannot resurrect stale duplicate main windows. + window.isRestorable = false window.isMovableByWindowBackground = false window.isMovable = false let restoredFrame = resolvedWindowFrame(from: sessionWindowSnapshot) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 418a0a21fb65..0db0db8c9da3 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -3442,6 +3442,7 @@ struct ContentView: View { view = AnyView(view.background(WindowAccessor { [sidebarBlendMode, bgGlassEnabled, bgGlassTintHex, bgGlassTintOpacity] window in window.identifier = NSUserInterfaceItemIdentifier(windowIdentifier) + window.isRestorable = false window.titlebarAppearsTransparent = true // Keep window immovable; the sidebar's WindowDragHandleView handles // drag-to-move via performDrag with temporary movable override. From 9e805f9eac5286bfe1c7c8bad7de0dfa6871ed61 Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sat, 25 Apr 2026 21:44:01 -0700 Subject: [PATCH 2/9] Make AppDelegate own main window bootstrap --- Sources/AppDelegate.swift | 34 ++++++++++++++++++++++++ Sources/ContentView.swift | 1 + Sources/cmuxApp.swift | 54 ++++++++++++++++++++++----------------- 3 files changed, 65 insertions(+), 24 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 4959d05a63b5..ccc664bd41eb 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -564,6 +564,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let tabManager: TabManager let sidebarState: SidebarState let sidebarSelectionState: SidebarSelectionState + var fileExplorerState: FileExplorerState? var cmuxConfigStore: CmuxConfigStore? weak var window: NSWindow? @@ -572,6 +573,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tabManager: TabManager, sidebarState: SidebarState, sidebarSelectionState: SidebarSelectionState, + fileExplorerState: FileExplorerState?, cmuxConfigStore: CmuxConfigStore?, window: NSWindow? ) { @@ -579,6 +581,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent self.tabManager = tabManager self.sidebarState = sidebarState self.sidebarSelectionState = sidebarSelectionState + self.fileExplorerState = fileExplorerState self.cmuxConfigStore = cmuxConfigStore self.window = window } @@ -898,6 +901,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } } + func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { + ensureInitialMainWindowIfNeeded() + return true + } + func applicationDidFinishLaunching(_ notification: Notification) { let env = ProcessInfo.processInfo.environment let isRunningUnderXCTest = isRunningUnderXCTest(env) @@ -3285,6 +3293,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tabManager: TabManager, sidebarState: SidebarState, sidebarSelectionState: SidebarSelectionState, + fileExplorerState: FileExplorerState? = nil, cmuxConfigStore: CmuxConfigStore? = nil ) { tabManager.window = window @@ -3295,6 +3304,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent #endif if let existing = mainWindowContexts[key] { existing.window = window + if let fileExplorerState { + existing.fileExplorerState = fileExplorerState + } if let cmuxConfigStore { existing.cmuxConfigStore = cmuxConfigStore } @@ -3313,6 +3325,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return } existing.window = window + if let fileExplorerState { + existing.fileExplorerState = fileExplorerState + } if let cmuxConfigStore { existing.cmuxConfigStore = cmuxConfigStore } @@ -3323,6 +3338,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tabManager: tabManager, sidebarState: sidebarState, sidebarSelectionState: sidebarSelectionState, + fileExplorerState: fileExplorerState, cmuxConfigStore: cmuxConfigStore, window: window ) @@ -4923,6 +4939,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tabManager = context.tabManager sidebarState = context.sidebarState sidebarSelectionState = context.sidebarSelectionState + fileExplorerState = context.fileExplorerState TerminalController.shared.setActiveTabManager(context.tabManager) } #if DEBUG @@ -5063,6 +5080,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent _ = createMainWindow() } + @discardableResult + func ensureInitialMainWindowIfNeeded(shouldActivate: Bool = true) -> UUID { + for context in sortedMainWindowContextsForSessionSnapshot() { + guard let window = resolvedWindow(for: context) else { continue } + if shouldActivate { + window.makeKeyAndOrderFront(nil) + setActiveMainWindow(window) + } + return context.windowId + } + + return createMainWindow(shouldActivate: shouldActivate) + } + @discardableResult func performNewWorkspaceAction( tabManager preferredTabManager: TabManager? = nil, @@ -5702,6 +5733,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tabManager = context.tabManager sidebarState = context.sidebarState sidebarSelectionState = context.sidebarSelectionState + fileExplorerState = context.fileExplorerState TerminalController.shared.setActiveTabManager(context.tabManager) } @@ -5847,6 +5879,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tabManager: tabManager, sidebarState: sidebarState, sidebarSelectionState: sidebarSelectionState, + fileExplorerState: fileExplorerState, cmuxConfigStore: cmuxConfigStore ) installFileDropOverlay(on: window, tabManager: tabManager) @@ -11683,6 +11716,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tabManager = context.tabManager sidebarState = context.sidebarState sidebarSelectionState = context.sidebarSelectionState + fileExplorerState = context.fileExplorerState TerminalController.shared.setActiveTabManager(context.tabManager) #if DEBUG cmuxDebugLog( diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 0db0db8c9da3..a365b3ca4b78 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -3527,6 +3527,7 @@ struct ContentView: View { tabManager: tabManager, sidebarState: sidebarState, sidebarSelectionState: sidebarSelectionState, + fileExplorerState: fileExplorerState, cmuxConfigStore: cmuxConfigStore ) installFileDropOverlayWhenReady(on: window, tabManager: tabManager) diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index c5a78b61256d..645b9b9c6609 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -9,11 +9,7 @@ struct cmuxApp: App { @StateObject private var tabManager: TabManager @StateObject private var notificationStore = TerminalNotificationStore.shared @StateObject private var sidebarState = SidebarState() - @StateObject private var sidebarSelectionState = SidebarSelectionState() - @StateObject private var fileExplorerState = FileExplorerState() - @StateObject private var cmuxConfigStore = CmuxConfigStore() @StateObject private var keyboardShortcutSettingsObserver = KeyboardShortcutSettingsObserver.shared - private let primaryWindowId = UUID() @AppStorage(AppearanceSettings.appearanceModeKey) private var appearanceMode = AppearanceSettings.defaultMode.rawValue @AppStorage("titlebarControlsStyle") private var titlebarControlsStyle = TitlebarControlsStyle.classic.rawValue @AppStorage(ShortcutHintDebugSettings.alwaysShowHintsKey) private var alwaysShowShortcutHints = ShortcutHintDebugSettings.defaultAlwaysShowHints @@ -190,31 +186,14 @@ struct cmuxApp: App { var body: some Scene { WindowGroup { - ContentView(updateViewModel: appDelegate.updateViewModel, windowId: primaryWindowId) - .environmentObject(tabManager) - .environmentObject(notificationStore) - .environmentObject(sidebarState) - .environmentObject(sidebarSelectionState) - .environmentObject(fileExplorerState) - .environmentObject(cmuxConfigStore) + MainWindowBootstrapView() .onAppear { #if DEBUG if ProcessInfo.processInfo.environment["CMUX_UI_TEST_MODE"] == "1" { UpdateLogStore.shared.append("ui test: cmuxApp onAppear") } #endif - // Start the Unix socket controller for programmatic access - updateSocketController() - appDelegate.configure(tabManager: tabManager, notificationStore: notificationStore, sidebarState: sidebarState) - appDelegate.fileExplorerState = fileExplorerState - cmuxConfigStore.wireDirectoryTracking(tabManager: tabManager) - cmuxConfigStore.loadAll() - applyAppearance() - if ProcessInfo.processInfo.environment["CMUX_UI_TEST_SHOW_SETTINGS"] == "1" { - DispatchQueue.main.async { - appDelegate.openPreferencesWindow(debugSource: "uiTestShowSettings") - } - } + bootstrapMainWindowScene() } .onChange(of: appearanceMode) { _ in applyAppearance() @@ -737,7 +716,7 @@ struct cmuxApp: App { let mode = SocketControlSettings.effectiveMode(userMode: currentSocketMode) if mode != .off { TerminalController.shared.start( - tabManager: tabManager, + tabManager: activeTabManager, socketPath: SocketControlSettings.socketPath(), accessMode: mode ) @@ -746,6 +725,17 @@ struct cmuxApp: App { } } + private func bootstrapMainWindowScene() { + appDelegate.ensureInitialMainWindowIfNeeded() + updateSocketController() + applyAppearance() + if ProcessInfo.processInfo.environment["CMUX_UI_TEST_SHOW_SETTINGS"] == "1" { + Task { @MainActor in + appDelegate.openPreferencesWindow(debugSource: "uiTestShowSettings") + } + } + } + private var currentSocketMode: SocketControlMode { SocketControlSettings.migrateMode(socketControlMode) } @@ -1028,6 +1018,22 @@ struct cmuxApp: App { } } +private struct MainWindowBootstrapView: View { + var body: some View { + Color.clear + .frame(width: 1, height: 1) + .background(WindowAccessor { window in + window.identifier = NSUserInterfaceItemIdentifier("cmux.main.bootstrap") + window.isRestorable = false + window.orderOut(nil) + Task { @MainActor [weak window] in + window?.orderOut(nil) + window?.close() + } + }) + } +} + private let cmuxAuxiliaryWindowIdentifiers: Set = [ "cmux.settings", "cmux.about", From ae9bdb1d881262019e8217ef85e47131db090707 Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sat, 25 Apr 2026 21:48:20 -0700 Subject: [PATCH 3/9] Fix duplicate window registration ordering --- Sources/AppDelegate.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index ccc664bd41eb..907fdfe730bf 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -3296,13 +3296,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent fileExplorerState: FileExplorerState? = nil, cmuxConfigStore: CmuxConfigStore? = nil ) { - tabManager.window = window - let key = ObjectIdentifier(window) #if DEBUG let priorManagerToken = debugManagerToken(self.tabManager) #endif if let existing = mainWindowContexts[key] { + tabManager.window = window existing.window = window if let fileExplorerState { existing.fileExplorerState = fileExplorerState @@ -3320,10 +3319,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent "existing={\(debugWindowToken(existingWindow))} duplicate={\(debugWindowToken(window))}" ) #endif + existing.tabManager.window = existingWindow window.orderOut(nil) window.close() return } + tabManager.window = window existing.window = window if let fileExplorerState { existing.fileExplorerState = fileExplorerState @@ -3333,6 +3334,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } reindexMainWindowContextIfNeeded(existing, for: window) } else { + tabManager.window = window mainWindowContexts[key] = MainWindowContext( windowId: windowId, tabManager: tabManager, From b10ef23226626783252a61339c9823b931e7818f Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sat, 25 Apr 2026 21:51:50 -0700 Subject: [PATCH 4/9] Keep window context state synchronized --- Sources/AppDelegate.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 907fdfe730bf..2f41930cc0a4 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -4699,11 +4699,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tabManager = nextContext.tabManager sidebarState = nextContext.sidebarState sidebarSelectionState = nextContext.sidebarSelectionState + fileExplorerState = nextContext.fileExplorerState TerminalController.shared.setActiveTabManager(nextContext.tabManager) } else { tabManager = nil sidebarState = nil sidebarSelectionState = nil + fileExplorerState = nil TerminalController.shared.setActiveTabManager(nil) } } @@ -5087,6 +5089,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent for context in sortedMainWindowContextsForSessionSnapshot() { guard let window = resolvedWindow(for: context) else { continue } if shouldActivate { + if window.isMiniaturized { + window.deminiaturize(nil) + } window.makeKeyAndOrderFront(nil) setActiveMainWindow(window) } @@ -11766,11 +11771,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent tabManager = nextContext.tabManager sidebarState = nextContext.sidebarState sidebarSelectionState = nextContext.sidebarSelectionState + fileExplorerState = nextContext.fileExplorerState TerminalController.shared.setActiveTabManager(nextContext.tabManager) } else { tabManager = nil sidebarState = nil sidebarSelectionState = nil + fileExplorerState = nil TerminalController.shared.setActiveTabManager(nil) } } From 9d9a25014015c866e29a16c3ebee4423590a182b Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sat, 25 Apr 2026 22:01:38 -0700 Subject: [PATCH 5/9] Bootstrap main window from app launch --- Sources/AppDelegate.swift | 15 +++++++++++++++ Sources/cmuxApp.swift | 7 +------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 2f41930cc0a4..4477378633a0 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -804,6 +804,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private var lastSessionAutosavePersistedAt: Date = .distantPast private var lastTypingActivityAt: TimeInterval = 0 private var didHandleExplicitOpenIntentAtStartup = false + private var didBootstrapInitialMainWindow = false private var isTerminatingApp = false // Set to true when the user has already confirmed quit via the warning dialog, // so applicationShouldTerminate does not show a second alert. @@ -1024,6 +1025,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent installShortcutDefaultsObserver() SystemWideHotkeyController.shared.start() NSApp.servicesProvider = self + + bootstrapInitialMainWindowIfNeeded(debugSource: "didFinishLaunching") #if DEBUG UpdateTestSupport.applyIfNeeded(to: updateController.viewModel) if env["CMUX_UI_TEST_MODE"] == "1" { @@ -5084,6 +5087,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent _ = createMainWindow() } + @discardableResult + func bootstrapInitialMainWindowIfNeeded(debugSource: String, shouldActivate: Bool = true) -> UUID { + let windowId = ensureInitialMainWindowIfNeeded(shouldActivate: shouldActivate) + guard !didBootstrapInitialMainWindow else { return windowId } + + didBootstrapInitialMainWindow = true + if ProcessInfo.processInfo.environment["CMUX_UI_TEST_SHOW_SETTINGS"] == "1" { + openPreferencesWindow(debugSource: "uiTestShowSettings.\(debugSource)") + } + return windowId + } + @discardableResult func ensureInitialMainWindowIfNeeded(shouldActivate: Bool = true) -> UUID { for context in sortedMainWindowContextsForSessionSnapshot() { diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 645b9b9c6609..718f14ffd1a1 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -726,14 +726,9 @@ struct cmuxApp: App { } private func bootstrapMainWindowScene() { - appDelegate.ensureInitialMainWindowIfNeeded() + appDelegate.bootstrapInitialMainWindowIfNeeded(debugSource: "swiftUIBootstrap") updateSocketController() applyAppearance() - if ProcessInfo.processInfo.environment["CMUX_UI_TEST_SHOW_SETTINGS"] == "1" { - Task { @MainActor in - appDelegate.openPreferencesWindow(debugSource: "uiTestShowSettings") - } - } } private var currentSocketMode: SocketControlMode { From 79ca94980f837dd747cf305b1d784125019f082a Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sat, 25 Apr 2026 22:59:48 -0700 Subject: [PATCH 6/9] Start socket after main window bootstrap --- Sources/AppDelegate.swift | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 4477378633a0..dcb146c4adfa 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1417,23 +1417,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent setupMultiWindowNotificationsUITestIfNeeded() setupDisplayResolutionUITestDiagnosticsIfNeeded() - // UI tests sometimes don't run SwiftUI `.onAppear` soon enough (or at all) on the VM. - // The automation socket is a core testing primitive, so ensure it's started here when - // we detect XCTest, even if the main view lifecycle is flaky. let env = ProcessInfo.processInfo.environment - if isRunningUnderXCTest(env) { - let raw = UserDefaults.standard.string(forKey: SocketControlSettings.appStorageKey) - ?? SocketControlSettings.defaultMode.rawValue - let userMode = SocketControlSettings.migrateMode(raw) - let mode = SocketControlSettings.effectiveMode(userMode: userMode) - if mode != .off { - TerminalController.shared.start( - tabManager: tabManager, - socketPath: SocketControlSettings.socketPath(), - accessMode: mode - ) - scheduleUITestSocketSanityCheckIfNeeded() - } + if isRunningUnderXCTest(env) || env["CMUX_UI_TEST_MODE"] == "1" { + scheduleUITestSocketSanityCheckIfNeeded() } #endif } @@ -2845,6 +2831,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return (mode: mode, path: SocketControlSettings.socketPath()) } + private func startSocketListenerIfEnabled(tabManager: TabManager, source: String) { + guard let config = socketListenerConfigurationIfEnabled() else { + TerminalController.shared.stop() + return + } + let path = TerminalController.shared.activeSocketPath(preferredPath: config.path) + sentryBreadcrumb("socket.listener.start", category: "socket", data: [ + "mode": config.mode.rawValue, + "path": path, + "source": source + ]) + TerminalController.shared.start(tabManager: tabManager, socketPath: path, accessMode: config.mode) + } + private func restartSocketListenerIfEnabled(source: String) { guard let tabManager, let config = socketListenerConfigurationIfEnabled() else { return } @@ -5090,6 +5090,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent @discardableResult func bootstrapInitialMainWindowIfNeeded(debugSource: String, shouldActivate: Bool = true) -> UUID { let windowId = ensureInitialMainWindowIfNeeded(shouldActivate: shouldActivate) + if let manager = tabManagerFor(windowId: windowId) { + startSocketListenerIfEnabled( + tabManager: manager, + source: "bootstrapInitialMainWindow.\(debugSource)" + ) + } guard !didBootstrapInitialMainWindow else { return windowId } didBootstrapInitialMainWindow = true From 557b105b451572bd620061525794b2722a97e9cd Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sat, 25 Apr 2026 23:01:49 -0700 Subject: [PATCH 7/9] Harden hidden bootstrap window handling --- Sources/AppDelegate.swift | 2 +- Sources/cmuxApp.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index dcb146c4adfa..7501cc5d8298 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -3315,7 +3315,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } else if let existing = mainWindowContexts.values.first(where: { $0.windowId == windowId }) { if let existingWindow = existing.window, existingWindow !== window, - existingWindow.isVisible { + existingWindow.isVisible || existingWindow.isMiniaturized { #if DEBUG cmuxDebugLog( "mainWindow.register.duplicateIgnored windowId=\(String(windowId.uuidString.prefix(8))) " + diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 718f14ffd1a1..94d81cf0a6d1 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -1018,7 +1018,7 @@ private struct MainWindowBootstrapView: View { Color.clear .frame(width: 1, height: 1) .background(WindowAccessor { window in - window.identifier = NSUserInterfaceItemIdentifier("cmux.main.bootstrap") + window.identifier = NSUserInterfaceItemIdentifier("cmux.bootstrap") window.isRestorable = false window.orderOut(nil) Task { @MainActor [weak window] in From 39dac431fc4fd460ea19f56e5dd873d33b361436 Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sat, 25 Apr 2026 23:15:26 -0700 Subject: [PATCH 8/9] Preserve active window on app reopen --- Sources/AppDelegate.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 7501cc5d8298..63a8069fcba5 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -903,6 +903,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { + if hasVisibleMainTerminalWindow() { + _ = synchronizeActiveMainWindowContext(preferredWindow: NSApp.keyWindow ?? NSApp.mainWindow) + return true + } ensureInitialMainWindowIfNeeded() return true } @@ -5122,6 +5126,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent return createMainWindow(shouldActivate: shouldActivate) } + private func hasVisibleMainTerminalWindow() -> Bool { + mainWindowContexts.values.contains { context in + guard let window = resolvedWindow(for: context) else { return false } + return window.isVisible && !window.isMiniaturized + } + } + @discardableResult func performNewWorkspaceAction( tabManager preferredTabManager: TabManager? = nil, From 57220bd0c0ffc5041019135a84f817314a4a3667 Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Sat, 25 Apr 2026 23:33:36 -0700 Subject: [PATCH 9/9] Delay initial bootstrap until launch opens settle --- Sources/AppDelegate.swift | 11 ++++++++++- Sources/cmuxApp.swift | 3 +-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 63a8069fcba5..66c867b5a862 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -804,6 +804,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private var lastSessionAutosavePersistedAt: Date = .distantPast private var lastTypingActivityAt: TimeInterval = 0 private var didHandleExplicitOpenIntentAtStartup = false + private var didScheduleInitialMainWindowBootstrap = false private var didBootstrapInitialMainWindow = false private var isTerminatingApp = false // Set to true when the user has already confirmed quit via the warning dialog, @@ -1030,7 +1031,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent SystemWideHotkeyController.shared.start() NSApp.servicesProvider = self - bootstrapInitialMainWindowIfNeeded(debugSource: "didFinishLaunching") + scheduleInitialMainWindowBootstrap(debugSource: "didFinishLaunching") #if DEBUG UpdateTestSupport.applyIfNeeded(to: updateController.viewModel) if env["CMUX_UI_TEST_MODE"] == "1" { @@ -5091,6 +5092,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent _ = createMainWindow() } + func scheduleInitialMainWindowBootstrap(debugSource: String) { + guard !didScheduleInitialMainWindowBootstrap else { return } + didScheduleInitialMainWindowBootstrap = true + DispatchQueue.main.async { [weak self] in + self?.bootstrapInitialMainWindowIfNeeded(debugSource: debugSource) + } + } + @discardableResult func bootstrapInitialMainWindowIfNeeded(debugSource: String, shouldActivate: Bool = true) -> UUID { let windowId = ensureInitialMainWindowIfNeeded(shouldActivate: shouldActivate) diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 94d81cf0a6d1..f1dbea2157b1 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -726,8 +726,7 @@ struct cmuxApp: App { } private func bootstrapMainWindowScene() { - appDelegate.bootstrapInitialMainWindowIfNeeded(debugSource: "swiftUIBootstrap") - updateSocketController() + appDelegate.scheduleInitialMainWindowBootstrap(debugSource: "swiftUIBootstrap") applyAppearance() }