From f6d28dae708b723ebff19a2661774d042e227919 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Wed, 12 Aug 2026 03:57:23 -0400 Subject: [PATCH] fix(remote): guard browser focus through reconcile --- .../src/runtime/web-runtime-session.test.ts | 51 +++++++++++++++++++ .../src/runtime/web-runtime-session.ts | 6 ++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/runtime/web-runtime-session.test.ts b/src/renderer/src/runtime/web-runtime-session.test.ts index 576515e74ed..40da9f22b77 100644 --- a/src/renderer/src/runtime/web-runtime-session.test.ts +++ b/src/renderer/src/runtime/web-runtime-session.test.ts @@ -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', diff --git a/src/renderer/src/runtime/web-runtime-session.ts b/src/renderer/src/runtime/web-runtime-session.ts index 12f5e062c9c..5ecff61c2e5 100644 --- a/src/renderer/src/runtime/web-runtime-session.ts +++ b/src/renderer/src/runtime/web-runtime-session.ts @@ -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 ) - 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()