fix(mobile): open a worktree on the tab the desktop is showing (#31215)

This commit is contained in:
Kelvin Amoaba
2026-10-20 23:12:13 +00:00
committed by GitHub
parent 026c45e9e3
commit be29644ec9
3 changed files with 27 additions and 12 deletions
@@ -130,15 +130,27 @@ describe('client session-tab selection', () => {
expect(store.project(initial, 'device-a').activeTabId).toBe('terminal-a::leaf-a')
})
it('does not expose host focus when initializing a new paired device', () => {
it('lands a device that has not picked a tab on the host focus', () => {
const store = new ClientSessionTabSelectionStore()
const hostFocusedBrowser = snapshot('browser-unified')
const projected = store.project(hostFocusedBrowser, 'new-device')
const projected = store.project(snapshot('browser-unified'), 'new-device')
expect(projected.activeTabId).toBe('terminal-a::leaf-a')
expect(projected.activeGroupId).toBe('group-left')
expect(projected.tabs.find((tab) => tab.isActive)?.id).toBe('terminal-a::leaf-a')
expect(projected.activeTabId).toBe('browser-unified')
expect(projected.activeGroupId).toBe('group-right')
expect(projected.tabs.find((tab) => tab.isActive)?.id).toBe('browser-unified')
// A later open follows where the host moved, not the first projection.
expect(store.project(snapshot('terminal-a::leaf-b'), 'new-device').activeTabId).toBe(
'terminal-a::leaf-b'
)
})
it('keeps a device on its own pick when the host moves', () => {
const store = new ClientSessionTabSelectionStore()
store.activate(snapshot(), 'device-a', 'browser-unified')
expect(store.project(snapshot('terminal-a::leaf-b'), 'device-a').activeTabId).toBe(
'browser-unified'
)
})
it('persists activations and restores them across a store rebuild (host restart)', () => {
@@ -193,7 +193,6 @@ export class ClientSessionTabSelectionStore {
const statesByWorktree = this.getStatesByWorktree(clientNavigationId)
const existingState = statesByWorktree.get(snapshot.worktree)
const state = existingState ?? {
// Why: host focus is private navigation; a new paired device starts from deterministic topology instead of inheriting it.
selection: emptyClientSessionTabSelection(),
revision: 0,
shouldPersist: false,
@@ -214,14 +213,18 @@ export class ClientSessionTabSelectionStore {
snapshotVersion: snapshot.snapshotVersion + state.revision
}
}
const projected = projectClientSessionTabSelection(closed.snapshot, state.selection)
// Why: until the device picks a tab here, opening the worktree lands on what the host shows;
// clients keep their own tab while open, so this never moves a device mid-visit.
const selection = state.shouldPersist
? state.selection
: deriveClientSessionTabSelection(closed.snapshot)
const projected = projectClientSessionTabSelection(closed.snapshot, selection)
// Why: a browser guest process swap drops its tab for one snapshot; the topology fallback
// must not overwrite (and persist) the device's explicit pick, or focus never returns.
const selectionSurvived =
!state.selection.activeTabId ||
closed.snapshot.tabs.some((tab) => tab.id === state.selection.activeTabId)
!selection.activeTabId || closed.snapshot.tabs.some((tab) => tab.id === selection.activeTabId)
statesByWorktree.set(snapshot.worktree, {
selection: selectionSurvived ? projected.selection : state.selection,
selection: selectionSurvived ? projected.selection : selection,
revision: state.revision,
shouldPersist: state.shouldPersist,
closedTabIds: closed.retainedClosedTabIds
@@ -807,7 +807,7 @@ describe('paired runtime navigation isolation', () => {
)
expect(remembered.activeTabId).toBe('client-a-tab')
expect(remembered.tabs.find((tab) => tab.isActive)?.id).toBe('client-a-tab')
// Why: an unknown device must still start from deterministic topology, not inherit another device's restored state.
// Why: an unknown device starts from the host's view, not another device's restored state.
const freshDevice = await restarted.listMobileSessionTabs(
`id:${SESSION_WORKTREE_ID}`,
'device-b'