diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 7855f1fcfab1..a3db0751b45f 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -3380,6 +3380,47 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { return surface } + private func dismissNotificationIfPresent() { + #if DEBUG + dlog("dismissNotificationIfPresent: checking tabId=\(tabId?.uuidString.prefix(8) ?? "nil") surfaceId=\(terminalSurface?.id.uuidString.prefix(8) ?? "nil")") + #endif + + guard let tabId, + let surfaceId = terminalSurface?.id, + let app = AppDelegate.shared, + let notificationStore = app.notificationStore else { + #if DEBUG + dlog("dismissNotificationIfPresent: early return - missing required objects") + #endif + return + } + + let hasUnread = notificationStore.hasUnreadNotification(forTabId: tabId, surfaceId: surfaceId) + #if DEBUG + dlog("dismissNotificationIfPresent: hasUnread=\(hasUnread) for tab=\(tabId.uuidString.prefix(8)) surface=\(surfaceId.uuidString.prefix(8))") + #endif + + guard hasUnread else { + return + } + + #if DEBUG + dlog("dismissNotificationIfPresent: dismissing notification and triggering flash") + #endif + + // Find the owning TabManager for this tab (handles secondary windows) + guard let tabManager = app.tabManagerFor(tabId: tabId), + let workspace = tabManager.tabs.first(where: { $0.id == tabId }) else { + #if DEBUG + dlog("dismissNotificationIfPresent: workspace not found, skipping dismissal") + #endif + return + } + + workspace.triggerNotificationFocusFlash(panelId: surfaceId, requiresSplit: false, shouldFocus: false) + notificationStore.markRead(forTabId: tabId, surfaceId: surfaceId) + } + func performBindingAction(_ action: String) -> Bool { guard let surface = surface else { return false } return action.withCString { cString in @@ -3955,6 +3996,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { } override func keyDown(with event: NSEvent) { + dismissNotificationIfPresent() guard let surface = ensureSurfaceReadyForInput() else { super.keyDown(with: event) return @@ -4401,6 +4443,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { let debugPoint = convert(event.locationInWindow, from: nil) dlog("terminal.mouseDown surface=\(terminalSurface?.id.uuidString.prefix(5) ?? "nil") mods=[\(debugModifierString(event.modifierFlags))] clickCount=\(event.clickCount) point=(\(String(format: "%.0f", debugPoint.x)),\(String(format: "%.0f", debugPoint.y)))") #endif + dismissNotificationIfPresent() window?.makeFirstResponder(self) guard let surface = surface else { return } let point = convert(event.locationInWindow, from: nil) @@ -4417,6 +4460,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { } override func rightMouseDown(with event: NSEvent) { + dismissNotificationIfPresent() guard let surface = surface else { return } if !ghostty_surface_mouse_captured(surface) { super.rightMouseDown(with: event) @@ -4440,6 +4484,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { } override func otherMouseDown(with event: NSEvent) { + dismissNotificationIfPresent() guard event.buttonNumber == 2 else { super.otherMouseDown(with: event) return diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index 5bb768cb3f13..c9ae5787bdfe 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -828,19 +828,8 @@ final class TerminalNotificationStore: ObservableObject { return true } - let isActiveTab = AppDelegate.shared?.tabManager?.selectedTabId == tabId - let focusedSurfaceId = AppDelegate.shared?.tabManager?.focusedSurfaceId(for: tabId) - let isFocusedSurface = surfaceId == nil || focusedSurfaceId == surfaceId - let isFocusedPanel = isActiveTab && isFocusedSurface - let isAppFocused = AppFocusState.isAppFocused() - if isAppFocused && isFocusedPanel { - if !idsToClear.isEmpty { - notifications = updated - center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) - center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) - } - return - } + // Removed auto-dismiss logic - notifications should always show up with blue ring + // and be dismissed via user interaction (click/keystroke) instead if WorkspaceAutoReorderSettings.isEnabled() { AppDelegate.shared?.tabManager?.moveTabToTop(tabId)