Guard notification focus failures

This commit is contained in:
Lawrence Chen 2026-03-13 17:21:40 -07:00
parent 02854441e9
commit 09a98c911d
No known key found for this signature in database
4 changed files with 57 additions and 6 deletions

View file

@ -9968,7 +9968,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
context.sidebarSelectionState.selection = .tabs
bringToFront(window)
context.tabManager.focusTabFromNotification(tabId, surfaceId: surfaceId)
guard context.tabManager.focusTabFromNotification(tabId, surfaceId: surfaceId) else {
#if DEBUG
recordMultiWindowNotificationOpenFailureIfNeeded(
tabId: tabId,
surfaceId: surfaceId,
notificationId: notificationId,
reason: "focus_failed"
)
if ProcessInfo.processInfo.environment["CMUX_UI_TEST_JUMP_UNREAD_SETUP"] == "1" {
writeJumpUnreadTestData(["jumpUnreadOpenResult": "0"])
}
#endif
return false
}
#if DEBUG
// UI test support: Jump-to-unread asserts that the correct workspace/panel is focused.
@ -10033,7 +10046,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
sidebarSelectionState?.selection = .tabs
bringToFront(window)
tabManager.focusTabFromNotification(tabId, surfaceId: surfaceId)
guard tabManager.focusTabFromNotification(tabId, surfaceId: surfaceId) else {
#if DEBUG
if ProcessInfo.processInfo.environment["CMUX_UI_TEST_JUMP_UNREAD_SETUP"] == "1" {
writeJumpUnreadTestData([
"jumpUnreadFallbackFail": "focus_failed",
"jumpUnreadOpenResult": "0",
])
}
#endif
return false
}
#if DEBUG
recordJumpUnreadFocusFromModelIfNeeded(

View file

@ -2203,9 +2203,24 @@ class TabManager: ObservableObject {
}
}
func focusTabFromNotification(_ tabId: UUID, surfaceId: UUID? = nil) {
@discardableResult
func focusTabFromNotification(_ tabId: UUID, surfaceId: UUID? = nil) -> Bool {
let wasSelected = selectedTabId == tabId
guard let tab = tabs.first(where: { $0.id == tabId }) else { return }
guard let tab = tabs.first(where: { $0.id == tabId }) else {
#if DEBUG
dlog("notification.focus.fail tab=\(tabId.uuidString.prefix(5)) reason=missingTab")
#endif
return false
}
if let surfaceId, tab.panels[surfaceId] == nil {
#if DEBUG
dlog(
"notification.focus.fail tab=\(tabId.uuidString.prefix(5)) " +
"panel=\(surfaceId.uuidString.prefix(5)) reason=missingPanel"
)
#endif
return false
}
let desiredPanelId = surfaceId ?? tab.focusedPanelId
#if DEBUG
if let desiredPanelId {
@ -2232,6 +2247,7 @@ class TabManager: ObservableObject {
tab.triggerNotificationFocusFlash(panelId: targetPanelId, requiresSplit: false, shouldFocus: true)
notificationStore.markRead(forTabId: tabId, surfaceId: targetPanelId)
}
return true
}
func focusSurface(tabId: UUID, surfaceId: UUID) {

View file

@ -11099,7 +11099,9 @@ class TerminalController {
result = "ERROR: Surface not found"
return
}
tabManager.focusTabFromNotification(tab.id, surfaceId: surfaceId)
if !tabManager.focusTabFromNotification(tab.id, surfaceId: surfaceId) {
result = "ERROR: Focus failed"
}
}
return result
}

View file

@ -5505,7 +5505,7 @@ final class TabManagerNotificationFocusTests: XCTestCase {
XCTAssertTrue(workspace.toggleSplitZoom(panelId: leftPanelId), "Expected split zoom to enable")
XCTAssertTrue(workspace.bonsplitController.isSplitZoomed, "Expected workspace to start zoomed")
manager.focusTabFromNotification(workspace.id, surfaceId: rightPanel.id)
XCTAssertTrue(manager.focusTabFromNotification(workspace.id, surfaceId: rightPanel.id))
drainMainQueue()
drainMainQueue()
@ -5515,6 +5515,16 @@ final class TabManagerNotificationFocusTests: XCTestCase {
)
XCTAssertEqual(workspace.focusedPanelId, rightPanel.id, "Expected notification target panel to be focused")
}
func testFocusTabFromNotificationReturnsFalseForMissingPanel() {
let manager = TabManager()
guard let workspace = manager.selectedWorkspace else {
XCTFail("Expected selected workspace")
return
}
XCTAssertFalse(manager.focusTabFromNotification(workspace.id, surfaceId: UUID()))
}
}
@MainActor