Merge pull request #257 from manaflow-ai/feat-reopen-browser-focus-fix
Fix reopened browser focus after workspace switch
This commit is contained in:
commit
7dbc80d48e
3 changed files with 114 additions and 8 deletions
|
|
@ -1618,7 +1618,11 @@ class TabManager: ObservableObject {
|
|||
selectedTabId = targetWorkspace.id
|
||||
}
|
||||
|
||||
if reopenClosedBrowserPanel(snapshot, in: targetWorkspace) {
|
||||
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)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -1629,14 +1633,14 @@ class TabManager: ObservableObject {
|
|||
private func reopenClosedBrowserPanel(
|
||||
_ snapshot: ClosedBrowserPanelRestoreSnapshot,
|
||||
in workspace: Workspace
|
||||
) -> Bool {
|
||||
) -> UUID? {
|
||||
if let originalPane = workspace.bonsplitController.allPaneIds.first(where: { $0.id == snapshot.originalPaneId }),
|
||||
let browserPanel = workspace.newBrowserSurface(inPane: originalPane, url: snapshot.url, focus: true) {
|
||||
let tabCount = workspace.bonsplitController.tabs(inPane: originalPane).count
|
||||
let maxIndex = max(0, tabCount - 1)
|
||||
let targetIndex = min(max(snapshot.originalTabIndex, 0), maxIndex)
|
||||
_ = workspace.reorderSurface(panelId: browserPanel.id, toIndex: targetIndex)
|
||||
return true
|
||||
return browserPanel.id
|
||||
}
|
||||
|
||||
if let orientation = snapshot.fallbackSplitOrientation,
|
||||
|
|
@ -1644,19 +1648,19 @@ class TabManager: ObservableObject {
|
|||
let anchorPane = workspace.bonsplitController.allPaneIds.first(where: { $0.id == fallbackAnchorPaneId }),
|
||||
let anchorTab = workspace.bonsplitController.selectedTab(inPane: anchorPane) ?? workspace.bonsplitController.tabs(inPane: anchorPane).first,
|
||||
let anchorPanelId = workspace.panelIdFromSurfaceId(anchorTab.id),
|
||||
workspace.newBrowserSplit(
|
||||
let browserPanelId = workspace.newBrowserSplit(
|
||||
from: anchorPanelId,
|
||||
orientation: orientation,
|
||||
insertFirst: snapshot.fallbackSplitInsertFirst,
|
||||
url: snapshot.url
|
||||
) != nil {
|
||||
return true
|
||||
)?.id {
|
||||
return browserPanelId
|
||||
}
|
||||
|
||||
guard let focusedPane = workspace.bonsplitController.focusedPaneId ?? workspace.bonsplitController.allPaneIds.first else {
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
return workspace.newBrowserSurface(inPane: focusedPane, url: snapshot.url, focus: true) != nil
|
||||
return workspace.newBrowserSurface(inPane: focusedPane, url: snapshot.url, focus: true)?.id
|
||||
}
|
||||
|
||||
/// Flash the currently focused panel so the user can visually confirm focus.
|
||||
|
|
|
|||
|
|
@ -2461,6 +2461,9 @@ extension Workspace: BonsplitDelegate {
|
|||
case .markAsUnread:
|
||||
guard let panelId = panelIdFromSurfaceId(tab.id) else { return }
|
||||
markPanelUnread(panelId)
|
||||
case .markAsRead:
|
||||
guard let panelId = panelIdFromSurfaceId(tab.id) else { return }
|
||||
markPanelRead(panelId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -818,6 +818,13 @@ final class UpdateChannelSettingsTests: XCTestCase {
|
|||
XCTAssertTrue(resolved.usedFallback)
|
||||
}
|
||||
|
||||
func testResolvedFeedFallsBackWhenInfoFeedEmpty() {
|
||||
let resolved = UpdateFeedResolver.resolvedFeedURLString(infoFeedURL: "")
|
||||
XCTAssertEqual(resolved.url, UpdateFeedResolver.fallbackFeedURL)
|
||||
XCTAssertFalse(resolved.isNightly)
|
||||
XCTAssertTrue(resolved.usedFallback)
|
||||
}
|
||||
|
||||
func testResolvedFeedUsesInfoFeedForStableChannel() {
|
||||
let infoFeed = "https://example.com/custom/appcast.xml"
|
||||
let resolved = UpdateFeedResolver.resolvedFeedURLString(infoFeedURL: infoFeed)
|
||||
|
|
@ -954,6 +961,98 @@ final class TabManagerSurfaceCreationTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class TabManagerReopenClosedBrowserFocusTests: XCTestCase {
|
||||
func testReopenFromDifferentWorkspaceFocusesReopenedBrowser() {
|
||||
let manager = TabManager()
|
||||
guard let workspace1 = manager.selectedWorkspace,
|
||||
let closedBrowserId = manager.openBrowser(url: URL(string: "https://example.com/ws-switch")) else {
|
||||
XCTFail("Expected initial workspace and browser panel")
|
||||
return
|
||||
}
|
||||
|
||||
drainMainQueue()
|
||||
XCTAssertTrue(workspace1.closePanel(closedBrowserId, force: true))
|
||||
drainMainQueue()
|
||||
|
||||
let workspace2 = manager.addWorkspace()
|
||||
XCTAssertEqual(manager.selectedTabId, workspace2.id)
|
||||
|
||||
XCTAssertTrue(manager.reopenMostRecentlyClosedBrowserPanel())
|
||||
drainMainQueue()
|
||||
|
||||
XCTAssertEqual(manager.selectedTabId, workspace1.id)
|
||||
XCTAssertTrue(isFocusedPanelBrowser(in: workspace1))
|
||||
}
|
||||
|
||||
func testReopenFallsBackToCurrentWorkspaceAndFocusesBrowserWhenOriginalWorkspaceDeleted() {
|
||||
let manager = TabManager()
|
||||
guard let originalWorkspace = manager.selectedWorkspace,
|
||||
let closedBrowserId = manager.openBrowser(url: URL(string: "https://example.com/deleted-ws")) else {
|
||||
XCTFail("Expected initial workspace and browser panel")
|
||||
return
|
||||
}
|
||||
|
||||
drainMainQueue()
|
||||
XCTAssertTrue(originalWorkspace.closePanel(closedBrowserId, force: true))
|
||||
drainMainQueue()
|
||||
|
||||
let currentWorkspace = manager.addWorkspace()
|
||||
manager.closeWorkspace(originalWorkspace)
|
||||
|
||||
XCTAssertEqual(manager.selectedTabId, currentWorkspace.id)
|
||||
XCTAssertFalse(manager.tabs.contains(where: { $0.id == originalWorkspace.id }))
|
||||
|
||||
XCTAssertTrue(manager.reopenMostRecentlyClosedBrowserPanel())
|
||||
drainMainQueue()
|
||||
|
||||
XCTAssertEqual(manager.selectedTabId, currentWorkspace.id)
|
||||
XCTAssertTrue(isFocusedPanelBrowser(in: currentWorkspace))
|
||||
}
|
||||
|
||||
func testReopenCollapsedSplitFromDifferentWorkspaceFocusesBrowser() {
|
||||
let manager = TabManager()
|
||||
guard let workspace1 = manager.selectedWorkspace,
|
||||
let sourcePanelId = workspace1.focusedPanelId,
|
||||
let splitBrowserId = manager.newBrowserSplit(
|
||||
tabId: workspace1.id,
|
||||
fromPanelId: sourcePanelId,
|
||||
orientation: .horizontal,
|
||||
insertFirst: false,
|
||||
url: URL(string: "https://example.com/collapsed-split")
|
||||
) else {
|
||||
XCTFail("Expected to create browser split")
|
||||
return
|
||||
}
|
||||
|
||||
drainMainQueue()
|
||||
XCTAssertTrue(workspace1.closePanel(splitBrowserId, force: true))
|
||||
drainMainQueue()
|
||||
|
||||
let workspace2 = manager.addWorkspace()
|
||||
XCTAssertEqual(manager.selectedTabId, workspace2.id)
|
||||
|
||||
XCTAssertTrue(manager.reopenMostRecentlyClosedBrowserPanel())
|
||||
drainMainQueue()
|
||||
|
||||
XCTAssertEqual(manager.selectedTabId, workspace1.id)
|
||||
XCTAssertTrue(isFocusedPanelBrowser(in: workspace1))
|
||||
}
|
||||
|
||||
private func isFocusedPanelBrowser(in workspace: Workspace) -> Bool {
|
||||
guard let focusedPanelId = workspace.focusedPanelId else { return false }
|
||||
return workspace.panels[focusedPanelId] is BrowserPanel
|
||||
}
|
||||
|
||||
private func drainMainQueue() {
|
||||
let expectation = expectation(description: "drain main queue")
|
||||
DispatchQueue.main.async {
|
||||
expectation.fulfill()
|
||||
}
|
||||
wait(for: [expectation], timeout: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class WorkspacePanelGitBranchTests: XCTestCase {
|
||||
func testClosingFocusedSplitRestoresBranchForRemainingFocusedPanel() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue