Merge pull request #267 from manaflow-ai/hotfix-reopen-browser-stale-focus
Fix intermittent Cmd+Shift+T stale-focus override
This commit is contained in:
commit
89953f387b
2 changed files with 139 additions and 4 deletions
|
|
@ -1613,16 +1613,18 @@ class TabManager: ObservableObject {
|
|||
?? tabs.first else {
|
||||
return false
|
||||
}
|
||||
let preReopenFocusedPanelId = focusedPanelId(for: targetWorkspace.id)
|
||||
|
||||
if selectedTabId != targetWorkspace.id {
|
||||
selectedTabId = targetWorkspace.id
|
||||
}
|
||||
|
||||
if let reopenedPanelId = reopenClosedBrowserPanel(snapshot, in: targetWorkspace) {
|
||||
// Workspace switches defer focus restoration to the next main-queue turn.
|
||||
// Record the reopened browser immediately so deferred restore doesn't snap
|
||||
// back to the previously focused terminal in that workspace.
|
||||
rememberFocusedSurface(tabId: targetWorkspace.id, surfaceId: reopenedPanelId)
|
||||
enforceReopenedBrowserFocus(
|
||||
tabId: targetWorkspace.id,
|
||||
reopenedPanelId: reopenedPanelId,
|
||||
preReopenFocusedPanelId: preReopenFocusedPanelId
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -1630,6 +1632,62 @@ class TabManager: ObservableObject {
|
|||
return false
|
||||
}
|
||||
|
||||
private func enforceReopenedBrowserFocus(
|
||||
tabId: UUID,
|
||||
reopenedPanelId: UUID,
|
||||
preReopenFocusedPanelId: UUID?
|
||||
) {
|
||||
// Keep workspace-switch restoration pinned to the reopened browser panel.
|
||||
rememberFocusedSurface(tabId: tabId, surfaceId: reopenedPanelId)
|
||||
enforceReopenedBrowserFocusIfNeeded(
|
||||
tabId: tabId,
|
||||
reopenedPanelId: reopenedPanelId,
|
||||
preReopenFocusedPanelId: preReopenFocusedPanelId
|
||||
)
|
||||
|
||||
// Some stale focus callbacks can land one runloop turn later. Re-assert focus in two
|
||||
// consecutive turns, but only when focus drifted back to the pre-reopen panel.
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
self.enforceReopenedBrowserFocusIfNeeded(
|
||||
tabId: tabId,
|
||||
reopenedPanelId: reopenedPanelId,
|
||||
preReopenFocusedPanelId: preReopenFocusedPanelId
|
||||
)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.enforceReopenedBrowserFocusIfNeeded(
|
||||
tabId: tabId,
|
||||
reopenedPanelId: reopenedPanelId,
|
||||
preReopenFocusedPanelId: preReopenFocusedPanelId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func enforceReopenedBrowserFocusIfNeeded(
|
||||
tabId: UUID,
|
||||
reopenedPanelId: UUID,
|
||||
preReopenFocusedPanelId: UUID?
|
||||
) {
|
||||
guard selectedTabId == tabId,
|
||||
let tab = tabs.first(where: { $0.id == tabId }),
|
||||
tab.panels[reopenedPanelId] != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
rememberFocusedSurface(tabId: tabId, surfaceId: reopenedPanelId)
|
||||
|
||||
guard tab.focusedPanelId != reopenedPanelId else { return }
|
||||
|
||||
if let focusedPanelId = tab.focusedPanelId,
|
||||
let preReopenFocusedPanelId,
|
||||
focusedPanelId != preReopenFocusedPanelId {
|
||||
return
|
||||
}
|
||||
|
||||
tab.focusPanel(reopenedPanelId)
|
||||
}
|
||||
|
||||
private func reopenClosedBrowserPanel(
|
||||
_ snapshot: ClosedBrowserPanelRestoreSnapshot,
|
||||
in workspace: Workspace
|
||||
|
|
|
|||
|
|
@ -1039,11 +1039,88 @@ final class TabManagerReopenClosedBrowserFocusTests: XCTestCase {
|
|||
XCTAssertTrue(isFocusedPanelBrowser(in: workspace1))
|
||||
}
|
||||
|
||||
func testReopenFromDifferentWorkspaceWinsAgainstSingleDeferredStaleFocus() {
|
||||
let manager = TabManager()
|
||||
guard let workspace1 = manager.selectedWorkspace,
|
||||
let preReopenPanelId = workspace1.focusedPanelId,
|
||||
let closedBrowserId = manager.openBrowser(url: URL(string: "https://example.com/stale-focus-cross-ws")) else {
|
||||
XCTFail("Expected initial workspace state and browser panel")
|
||||
return
|
||||
}
|
||||
|
||||
drainMainQueue()
|
||||
XCTAssertTrue(workspace1.closePanel(closedBrowserId, force: true))
|
||||
drainMainQueue()
|
||||
|
||||
let panelIdsBeforeReopen = Set(workspace1.panels.keys)
|
||||
let workspace2 = manager.addWorkspace()
|
||||
XCTAssertEqual(manager.selectedTabId, workspace2.id)
|
||||
|
||||
XCTAssertTrue(manager.reopenMostRecentlyClosedBrowserPanel())
|
||||
guard let reopenedPanelId = singleNewPanelId(in: workspace1, comparedTo: panelIdsBeforeReopen) else {
|
||||
XCTFail("Expected reopened browser panel ID")
|
||||
return
|
||||
}
|
||||
|
||||
// Simulate one delayed stale focus callback from the panel that was focused before reopen.
|
||||
DispatchQueue.main.async {
|
||||
workspace1.focusPanel(preReopenPanelId)
|
||||
}
|
||||
|
||||
drainMainQueue()
|
||||
drainMainQueue()
|
||||
drainMainQueue()
|
||||
|
||||
XCTAssertEqual(manager.selectedTabId, workspace1.id)
|
||||
XCTAssertEqual(workspace1.focusedPanelId, reopenedPanelId)
|
||||
XCTAssertTrue(workspace1.panels[reopenedPanelId] is BrowserPanel)
|
||||
}
|
||||
|
||||
func testReopenInSameWorkspaceWinsAgainstSingleDeferredStaleFocus() {
|
||||
let manager = TabManager()
|
||||
guard let workspace = manager.selectedWorkspace,
|
||||
let preReopenPanelId = workspace.focusedPanelId,
|
||||
let closedBrowserId = manager.openBrowser(url: URL(string: "https://example.com/stale-focus-same-ws")) else {
|
||||
XCTFail("Expected initial workspace state and browser panel")
|
||||
return
|
||||
}
|
||||
|
||||
drainMainQueue()
|
||||
XCTAssertTrue(workspace.closePanel(closedBrowserId, force: true))
|
||||
drainMainQueue()
|
||||
|
||||
let panelIdsBeforeReopen = Set(workspace.panels.keys)
|
||||
XCTAssertTrue(manager.reopenMostRecentlyClosedBrowserPanel())
|
||||
guard let reopenedPanelId = singleNewPanelId(in: workspace, comparedTo: panelIdsBeforeReopen) else {
|
||||
XCTFail("Expected reopened browser panel ID")
|
||||
return
|
||||
}
|
||||
|
||||
// Simulate one delayed stale focus callback from the panel that was focused before reopen.
|
||||
DispatchQueue.main.async {
|
||||
workspace.focusPanel(preReopenPanelId)
|
||||
}
|
||||
|
||||
drainMainQueue()
|
||||
drainMainQueue()
|
||||
drainMainQueue()
|
||||
|
||||
XCTAssertEqual(manager.selectedTabId, workspace.id)
|
||||
XCTAssertEqual(workspace.focusedPanelId, reopenedPanelId)
|
||||
XCTAssertTrue(workspace.panels[reopenedPanelId] is BrowserPanel)
|
||||
}
|
||||
|
||||
private func isFocusedPanelBrowser(in workspace: Workspace) -> Bool {
|
||||
guard let focusedPanelId = workspace.focusedPanelId else { return false }
|
||||
return workspace.panels[focusedPanelId] is BrowserPanel
|
||||
}
|
||||
|
||||
private func singleNewPanelId(in workspace: Workspace, comparedTo previousPanelIds: Set<UUID>) -> UUID? {
|
||||
let newPanelIds = Set(workspace.panels.keys).subtracting(previousPanelIds)
|
||||
guard newPanelIds.count == 1 else { return nil }
|
||||
return newPanelIds.first
|
||||
}
|
||||
|
||||
private func drainMainQueue() {
|
||||
let expectation = expectation(description: "drain main queue")
|
||||
DispatchQueue.main.async {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue