Merge pull request #1401 from manaflow-ai/task-cmd-shift-u-exits-cmd-shift-enter-mode

Exit split zoom when jumping to unread
This commit is contained in:
Lawrence Chen 2026-03-13 17:28:34 -07:00 committed by GitHub
commit 5dd93a8ab1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 87 additions and 5 deletions

View file

@ -9980,7 +9980,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.
@ -10045,7 +10058,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

@ -2214,14 +2214,33 @@ class TabManager: ObservableObject {
}
}
func focusTabFromNotification(_ tabId: UUID, surfaceId: UUID? = nil) {
@discardableResult
func focusTabFromNotification(_ tabId: UUID, surfaceId: UUID? = nil) -> Bool {
let wasSelected = selectedTabId == tabId
let desiredPanelId = surfaceId ?? tabs.first(where: { $0.id == tabId })?.focusedPanelId
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 {
AppDelegate.shared?.armJumpUnreadFocusRecord(tabId: tabId, surfaceId: desiredPanelId)
}
#endif
// Jump-to-unread should reveal the destination pane instead of keeping an old split-zoom
// state active around it.
tab.clearSplitZoom()
suppressFocusFlash = true
focusTab(tabId, surfaceId: desiredPanelId, suppressFlash: true)
if wasSelected {
@ -2239,6 +2258,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

@ -11142,7 +11142,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

@ -5589,6 +5589,43 @@ final class TabManagerCloseCurrentPanelTests: XCTestCase {
}
}
@MainActor
final class TabManagerNotificationFocusTests: XCTestCase {
func testFocusTabFromNotificationClearsSplitZoomBeforeFocusingTargetPanel() {
let manager = TabManager()
guard let workspace = manager.selectedWorkspace,
let leftPanelId = workspace.focusedPanelId,
let rightPanel = workspace.newTerminalSplit(from: leftPanelId, orientation: .horizontal) else {
XCTFail("Expected split setup to succeed")
return
}
workspace.focusPanel(leftPanelId)
XCTAssertTrue(workspace.toggleSplitZoom(panelId: leftPanelId), "Expected split zoom to enable")
XCTAssertTrue(workspace.bonsplitController.isSplitZoomed, "Expected workspace to start zoomed")
XCTAssertTrue(manager.focusTabFromNotification(workspace.id, surfaceId: rightPanel.id))
drainMainQueue()
drainMainQueue()
XCTAssertFalse(
workspace.bonsplitController.isSplitZoomed,
"Expected notification focus to exit split zoom so the target pane becomes visible"
)
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
final class TabManagerPendingUnfocusPolicyTests: XCTestCase {
func testDoesNotUnfocusWhenPendingTabIsCurrentlySelected() {