diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 287c9829..469ec5fa 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -1602,6 +1602,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser func persistSessionForUpdateRelaunch() { isTerminatingApp = true + // The user already consented to this termination by choosing to install + // the update — without this, applicationShouldTerminate shows the modal + // "Quit Programa?" warning in the middle of the update relaunch for + // default-config users, and if Sparkle force-kills past its timeout the + // stale cleanShutdown=false snapshot fires the crash-recovery notice as + // a false positive on the next launch (audit 2026-08-20, H4). + isQuitWarningConfirmed = true _ = saveSessionSnapshot(includeScrollback: true, removeWhenEmpty: false) } @@ -8683,6 +8690,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser private func handleNotificationResponse(_ response: UNNotificationResponse) { guard let tabIdString = response.notification.request.content.userInfo["tabId"] as? String, let tabId = UUID(uuidString: tabIdString) else { + // App-level notification (crash-recovery notice, no tab routing): + // bring the app forward explicitly. macOS activates on a default + // click, but a bare return here left action-button clicks and + // already-active-but-windowless states doing nothing (audit + // 2026-08-20, M1). + NSApp.activate(ignoringOtherApps: true) return } let surfaceId: UUID? = { diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index d626475a..0ccdecdb 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -584,8 +584,8 @@ final class TerminalNotificationStore: ObservableObject { /// App-level (not tab-scoped) system notification — crash-recovery notice and /// similar app-lifecycle events. Uses the same authorization flow as terminal - /// notifications but carries no tab routing: the response handler's tabId guard - /// makes activation a plain app-activate, never a tab jump. + /// notifications but carries no tab routing: the response handler's no-tabId + /// branch activates the app explicitly and never jumps to a tab. func postAppNotification(title: String, body: String) { ensureAuthorization(origin: .notificationDelivery) { [weak self] authorized in guard let self, authorized else { return }