fix(desktop): scope branch-opens-primary to the currently selected session

forkBranch was unconditionally routing every branched session into the
main pane via resumeSession, including sidebar/background branches of
a session the user isn't currently viewing. That reintroduces the
#69750 focus-stealing bug for that path: branching a different session
from the sidebar yanked the active view away from whatever was open.
Only take over the main pane when the branch's parent is the session
already selected; otherwise keep opening it as its own tile.
This commit is contained in:
chelsealong
2026-08-24 02:43:40 +00:00
committed by Teknium
parent a4092cd2d2
commit 9fbe3cc040
2 changed files with 74 additions and 6 deletions
@@ -1437,6 +1437,7 @@ describe('branchStoredSession desktop source tagging', () => {
navigate={navigate}
onReady={branch => (branchStoredSession = branch)}
requestGateway={requestGateway}
selectedStoredSessionId="stored-parent"
/>
)
await waitFor(() => expect(branchStoredSession).not.toBeNull())
@@ -1451,6 +1452,56 @@ describe('branchStoredSession desktop source tagging', () => {
expect($sessionTiles.get().some(tile => tile.storedSessionId === 'branch-stored')).toBe(false)
})
it('keeps the current view when branching a different session from the sidebar (does not reintroduce #69750)', async () => {
const requestGateway = vi.fn(async (method: string) => {
if (method === 'session.create') {
return { session_id: 'branch-runtime', stored_session_id: 'branch-stored' } as never
}
if (method === 'session.resume') {
return {
info: {},
message_count: 0,
messages: [],
resumed: 'branch-stored',
session_id: 'branch-runtime',
session_key: 'branch-stored'
} as never
}
return {} as never
})
// The user is looking at "stored-other" and right-clicks a DIFFERENT,
// unrelated session ("stored-parent") in the sidebar to branch it.
setSessions([storedSession({ id: 'stored-parent', message_count: 1 })])
setSelectedStoredSessionId('stored-other')
vi.mocked(getAllSessionMessages).mockResolvedValue({
messages: [{ content: 'branch me', role: 'user', timestamp: 1 }],
session_id: 'stored-parent'
} as never)
const navigate = vi.fn()
let branchStoredSession: ((storedSessionId: string) => Promise<boolean>) | null = null
render(
<BranchHarness
navigate={navigate}
onReady={branch => (branchStoredSession = branch)}
requestGateway={requestGateway}
selectedStoredSessionId="stored-other"
/>
)
await waitFor(() => expect(branchStoredSession).not.toBeNull())
await expect(branchStoredSession!('stored-parent')).resolves.toBe(true)
// Branching a session that is not the one currently open must not steal
// the user's active view — "stored-other" stays selected.
expect($selectedStoredSessionId.get()).toBe('stored-other')
// The branch instead opens as its own tile.
expect($sessionTiles.get().some(tile => tile.storedSessionId === 'branch-stored')).toBe(true)
})
it('tags desktop branch sessions as desktop sessions', async () => {
let createParams: Record<string, unknown> | undefined
@@ -1656,11 +1656,20 @@ export function useSessionActions({
updateSessionState(branched.session_id, state => ({ ...state, ...runtimeInfo }), routedSessionId)
}
// Load the branch as the primary session so it opens in the main
// workspace, not just a sidebar row. resumeSession reuses the runtime
// warm-cached above (ensureSessionState/updateSessionState) instead of
// an extra resume RPC.
await resumeSession(routedSessionId)
// Only take over the main pane when the chat being branched is the one
// already open there — branching a background/sidebar session must
// not yank the user's current view away from what they're looking at
// (the #69750 focus-stealing bug, reintroduced if this fires
// unconditionally). resumeSession reuses the runtime warm-cached above
// (ensureSessionState/updateSessionState) instead of an extra resume RPC.
if (parentStoredId !== null && selectedStoredSessionIdRef.current === parentStoredId) {
await resumeSession(routedSessionId)
} else {
openSessionTile(routedSessionId, 'center')
patchSessionTile(routedSessionId, { runtimeId: branched.session_id })
revealTreePane(`session-tile:${routedSessionId}`)
}
broadcastSessionsChanged()
return true
@@ -1674,7 +1683,15 @@ export function useSessionActions({
}, 0)
}
},
[copy, creatingSessionRef, ensureSessionState, requestGateway, resumeSession, updateSessionState]
[
copy,
creatingSessionRef,
ensureSessionState,
requestGateway,
resumeSession,
selectedStoredSessionIdRef,
updateSessionState
]
)
// Branch the open chat — optionally from a specific message — off its live transcript.