fix(remote): guard browser focus through reconcile

This commit is contained in:
Jinwoo-H
2026-08-12 04:38:02 -04:00
parent 2f1638f205
commit f6d28dae70
2 changed files with 55 additions and 2 deletions
@@ -471,6 +471,57 @@ describe('createWebRuntimeSessionBrowserTab', () => {
expect(peekWebSessionFocusIntent({ environmentId: ENVIRONMENT_ID }, WORKTREE_ID)).toBeNull()
})
it('does not focus a browser after an editor A-B-A selection during reconciliation', async () => {
let resolveList!: (response: unknown) => void
const listResponse = new Promise((resolve) => {
resolveList = resolve
})
let state = mocks.getState()
let listener: ((next: typeof state, previous: typeof state) => void) | null = null
mocks.getState.mockImplementation(() => state)
mocks.subscribe.mockImplementation((nextListener) => {
listener = nextListener
return vi.fn()
})
const runtimeCall = vi
.fn()
.mockResolvedValueOnce({
id: 'create',
ok: true,
result: { browserPageId: 'created-host-browser' }
})
.mockReturnValueOnce(listResponse)
vi.stubGlobal('window', { api: { runtimeEnvironments: { call: runtimeCall } } })
const pendingCreate = createWebRuntimeSessionBrowserTab({ worktreeId: WORKTREE_ID })
await vi.waitFor(() => expect(runtimeCall).toHaveBeenCalledTimes(2))
const editorBState = {
...state,
activeFileIdByWorktree: { [WORKTREE_ID]: '/worktree/other.html' },
unifiedTabsByWorktree: {
[WORKTREE_ID]: [
...state.unifiedTabsByWorktree[WORKTREE_ID],
{
id: 'other-editor-tab',
entityId: '/worktree/other.html',
contentType: 'editor'
}
]
}
}
listener!(editorBState, state)
const editorAState = {
...editorBState,
activeFileIdByWorktree: { [WORKTREE_ID]: '/worktree/index.html' }
}
listener!(editorAState, editorBState)
state = editorAState
resolveList({ id: 'list', ok: true, result: makeSnapshot() })
await expect(pendingCreate).resolves.toBe(true)
expect(peekWebSessionFocusIntent({ environmentId: ENVIRONMENT_ID }, WORKTREE_ID)).toBeNull()
})
it('does not retry a failed browser create', async () => {
const runtimeCall = vi.fn().mockResolvedValue({
id: 'create-lost',
@@ -468,6 +468,7 @@ export async function createWebRuntimeSessionBrowserTab(args: {
selectWebRuntimeSessionBrowserWorktree(args.worktreeId, environmentId)
}
let unsubscribeFocusGuard = (): void => {}
let guardedPageId = provisionalPageId
try {
if (matchesWebSessionIntentOwner(intentOwner)) {
recordWebSessionFocusIntent(
@@ -492,7 +493,7 @@ export async function createWebRuntimeSessionBrowserTab(args: {
if (resolveWebSessionVisibleTabId(state, args.worktreeId) === expectedCurrentLocalTabId) {
return
}
clearWebSessionFocusIntentIfMatches(intentOwner, args.worktreeId, provisionalPageId)
clearWebSessionFocusIntentIfMatches(intentOwner, args.worktreeId, guardedPageId)
unsubscribeFocusGuard()
})
}
@@ -513,7 +514,6 @@ export async function createWebRuntimeSessionBrowserTab(args: {
timeoutMs: 15_000
})) as RuntimeRpcResponse<BrowserTabCreateResult>
)
unsubscribeFocusGuard()
if (created.browserPageId !== provisionalPageId) {
moveWebSessionBrowserPlacement({
environmentId,
@@ -531,6 +531,7 @@ export async function createWebRuntimeSessionBrowserTab(args: {
focusIntent.expectedCurrentLocalTabId
)
}
guardedPageId = created.browserPageId
}
try {
await refreshWebRuntimeSessionTabsSnapshot(environmentId, args.worktreeId, {
@@ -543,6 +544,7 @@ export async function createWebRuntimeSessionBrowserTab(args: {
error instanceof Error ? error.message : String(error)
)
}
unsubscribeFocusGuard()
return true
} catch (error) {
unsubscribeFocusGuard()