fix structured completion focus scope

This commit is contained in:
Brennan Benson
2026-08-13 14:40:40 -07:00
parent ce1f4a927b
commit 92c2aaeb6a
3 changed files with 79 additions and 2 deletions
@@ -121,6 +121,7 @@ export type IpcEventsHarness = {
useIpcEvents: () => void
createTerminal: (request: CreateTerminalRequest) => void
requestTerminalCreate: (request: RequestTerminalCreateRequest) => void
focusEditorTab: (request: { tabId: string; worktreeId: string }) => void
replyTerminalCreate: ReturnType<typeof vi.fn>
/** Fire a main-process digit chord (zero-based index). */
jumpToWorktreeIndex: (index: number) => void
@@ -136,8 +137,7 @@ export type IpcEventsHarnessOptions = {
}
/**
* Loads useIpcEvents against a stubbed preload API and returns a driver for the
* create-terminal IPC, so reveal/adoption behavior is asserted through the hook.
* Loads useIpcEvents against a stubbed preload API so IPC behavior is asserted through the hook.
*/
export async function loadIpcEventsHarness(
storeState: HarnessStoreState,
@@ -147,6 +147,8 @@ export async function loadIpcEventsHarness(
const activateAndRevealWorkspace = vi.fn()
let createTerminalListener: ((request: CreateTerminalRequest) => void) | null = null
let requestTerminalCreateListener: ((request: RequestTerminalCreateRequest) => void) | null = null
let focusEditorTabListener: ((request: { tabId: string; worktreeId: string }) => void) | null =
null
let navigationUpdateListener:
| ((event: { browserPageId: string; url: string; title: string }) => void)
| null = null
@@ -209,6 +211,12 @@ export async function loadIpcEventsHarness(
requestTerminalCreateListener = listener
return () => {}
},
onFocusEditorTab: (
listener: (request: { tabId: string; worktreeId: string }) => void
) => {
focusEditorTabListener = listener
return () => {}
},
onJumpToWorktreeIndex: (listener: (index: number) => void) => {
indexJumpListeners.set('worktree', listener)
return () => {}
@@ -279,6 +287,12 @@ export async function loadIpcEventsHarness(
}
requestTerminalCreateListener(request)
},
focusEditorTab: (request) => {
if (typeof focusEditorTabListener !== 'function') {
throw new Error('Expected the focus-editor-tab listener to be registered')
}
focusEditorTabListener(request)
},
replyTerminalCreate,
jumpToWorktreeIndex: (index) => fireIndexJump(indexJumpListeners, 'worktree', index),
jumpToTabIndex: (index) => fireIndexJump(indexJumpListeners, 'tab', index),
@@ -0,0 +1,60 @@
import { describe, expect, it, vi } from 'vitest'
import {
createHarnessStoreState,
loadIpcEventsHarness,
type HarnessStoreState
} from './ipc-events-test-harness'
const SESSION_WORKSPACE_ID = 'repo-1::/session-workspace'
const OTHER_WORKSPACE_ID = 'repo-1::/other-workspace'
const TAB_ID = 'structured-agent-session-session-1'
function createStoreState(activeWorktreeId: string): HarnessStoreState {
return createHarnessStoreState({
tabsByWorktree: {},
activeWorktreeId,
unifiedTabsByWorktree: {
[SESSION_WORKSPACE_ID]: [
{
id: TAB_ID,
entityId: 'session-1',
groupId: 'group-1',
worktreeId: SESSION_WORKSPACE_ID,
contentType: 'agent-session'
}
]
},
focusGroup: vi.fn(),
activateTab: vi.fn()
})
}
describe('structured session completion focus', () => {
it('focuses the chat tab when its workspace is active', async () => {
const store = createStoreState(SESSION_WORKSPACE_ID)
const harness = await loadIpcEventsHarness(store)
harness.useIpcEvents()
harness.focusEditorTab({ tabId: TAB_ID, worktreeId: SESSION_WORKSPACE_ID })
expect(store.focusGroup).toHaveBeenCalledWith(SESSION_WORKSPACE_ID, 'group-1')
expect(store.activateTab).toHaveBeenCalledWith(TAB_ID)
expect(store.setActiveTabType).toHaveBeenCalledWith('agent-session')
})
it('does not apply focus after the user moves to another workspace', async () => {
const store = createStoreState(OTHER_WORKSPACE_ID)
const harness = await loadIpcEventsHarness(store)
harness.useIpcEvents()
harness.focusEditorTab({ tabId: TAB_ID, worktreeId: SESSION_WORKSPACE_ID })
expect(store.setActiveWorktree).not.toHaveBeenCalled()
expect(store.markWorktreeVisited).not.toHaveBeenCalled()
expect(store.setActiveView).not.toHaveBeenCalled()
expect(store.focusGroup).not.toHaveBeenCalled()
expect(store.activateTab).not.toHaveBeenCalled()
expect(store.setActiveTabType).not.toHaveBeenCalled()
expect(store.revealWorktreeInSidebar).not.toHaveBeenCalled()
})
})
+3
View File
@@ -1867,6 +1867,9 @@ export function useIpcEvents(): void {
(item) => item.id === tabId
)
const browserTarget = resolveBrowserSessionTabTarget(store, worktreeId, tabId)
if (tab?.contentType === 'agent-session' && store.activeWorktreeId !== worktreeId) {
return
}
if (!tab) {
if (browserTarget) {
// Why: older/mobile fallback snapshots identify browser tabs by workspace id when no unified tab wrapper exists.