diff --git a/src/main/runtime/orca-runtime-session-tabs-path-alias.test.ts b/src/main/runtime/orca-runtime-session-tabs-path-alias.test.ts index 62966b013da..2b90d53e021 100644 --- a/src/main/runtime/orca-runtime-session-tabs-path-alias.test.ts +++ b/src/main/runtime/orca-runtime-session-tabs-path-alias.test.ts @@ -299,7 +299,7 @@ describe('session tab workspace path aliases', () => { expect.objectContaining({ result: expect.objectContaining({ type: 'updated', - worktree: WORKTREE, + worktree: ALIAS, tabs: expect.arrayContaining([expect.objectContaining({ id: TAB })]) }) }) @@ -313,6 +313,37 @@ describe('session tab workspace path aliases', () => { runtime.cleanupSubscriptionsForConnection('alias-connection') }) + // Why: clients file frames by their `worktree`, so one stream switching spellings splits a + // workspace's tabs across two keys. + it('names an early alias subscriber stream by one spelling for its whole life', async () => { + const runtime = new OrcaRuntimeService() + runtime.attachWindow(1) + const dispatcher = new RpcDispatcher({ runtime, methods: SESSION_TAB_METHODS }) + const frames: { type?: string; worktree?: string; tabs?: unknown[] }[] = [] + await dispatcher.dispatchStreaming( + { + id: 'sub-early', + authToken: 'token', + method: 'session.tabs.subscribe', + params: { worktree: `id:${ALIAS}` } + }, + (message) => { + const result = JSON.parse(message).result + if (result?.type === 'snapshot' || result?.type === 'updated') { + frames.push(result) + } + }, + { connectionId: 'alias-connection' } + ) + + publishGraph(runtime) + + await vi.waitFor(() => expect(frames.some((frame) => frame.type === 'updated')).toBe(true)) + expect(frames.map((frame) => frame.worktree)).toEqual(frames.map(() => ALIAS)) + expect(frames.at(-1)?.tabs).toContainEqual(expect.objectContaining({ id: TAB })) + runtime.cleanupSubscriptionsForConnection('alias-connection') + }) + it.each([ [WORKTREE, ALIAS], [ALIAS, WORKTREE], diff --git a/src/main/runtime/rpc/methods/session-tabs.ts b/src/main/runtime/rpc/methods/session-tabs.ts index 2e0d1ff08b3..2b2e0e7e0da 100644 --- a/src/main/runtime/rpc/methods/session-tabs.ts +++ b/src/main/runtime/rpc/methods/session-tabs.ts @@ -193,11 +193,16 @@ export const SESSION_TAB_METHODS = [ } } if (snapshot.worktree === subscribedWorktree) { + // Why: clients file frames by `worktree`; a spelling switch mid-stream splits the tabs. + const streamSnapshot = + snapshot.worktree === initial.worktree + ? snapshot + : { ...snapshot, worktree: initial.worktree } emit({ type: 'updated', ...tabDelta.update( withProofDelta( - projectSessionTabsForClient(snapshot, clientKind, clientCapabilities) + projectSessionTabsForClient(streamSnapshot, clientKind, clientCapabilities) ) ) })