Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions Sources/GhosttyTerminalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Comment on lines +3388 to +3390

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Dismiss tab-scoped notifications on terminal input

dismissNotificationIfPresent() only checks unread state for the current concrete surfaceId, because it guards on terminalSurface?.id and then calls hasUnreadNotification(forTabId:surfaceId:) with that value. After this commit removed the focused auto-dismiss branch in addNotification (which previously treated surfaceId == nil as focused), notifications created at tab scope (surfaceId == nil) can remain unread even after clicks/keystrokes in the focused terminal, so the unread badge can stick until a manual mark-read action.

Useful? React with 👍 / 👎.

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
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// 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)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

func performBindingAction(_ action: String) -> Bool {
guard let surface = surface else { return false }
return action.withCString { cString in
Expand Down Expand Up @@ -3955,6 +3996,7 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
}

override func keyDown(with event: NSEvent) {
dismissNotificationIfPresent()
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
guard let surface = ensureSurfaceReadyForInput() else {
super.keyDown(with: event)
return
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand Down
15 changes: 2 additions & 13 deletions Sources/TerminalNotificationStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down