Make resource session rows jump to focused terminals

- Close the resource popover before activating a terminal session so focus can transfer cleanly.
- Preserve worktree reveal behavior and flash the focused pane when a stable pane key matches.
- Cover stale mappings, malformed pane keys, and keyboard activation with tests.
This commit is contained in:
Jinjing
2026-05-17 19:50:09 -07:00
parent 8057cbdf78
commit d9b4f7b2c1
3 changed files with 127 additions and 21 deletions
@@ -40,7 +40,6 @@ import type { AppMemory, UsageValues, Worktree } from '../../../../shared/types'
import { ORPHAN_WORKTREE_ID } from '../../../../shared/constants'
import { isFolderRepo } from '../../../../shared/repo-kind'
import { isWorkspaceOldForCleanup } from '../../../../shared/workspace-cleanup'
import { parsePaneKey } from '../../../../shared/stable-pane-id'
import {
mergeSnapshotAndSessions,
UNATTRIBUTED_REPO_ID,
@@ -52,6 +51,10 @@ import {
} from './mergeSnapshotAndSessions'
import { WorkspaceSpaceCompactPanel } from './WorkspaceSpaceCompactPanel'
import { STATUS_BAR_CONTEXT_MENU_EXEMPT_PROPS } from './status-bar-context-menu-policy'
import {
isResourceSessionActivationKey,
navigateResourceSessionToTab
} from './resource-session-navigation'
const POLL_MS = 2_000
const SESSIONS_POLL_MS = 10_000
@@ -334,7 +337,7 @@ function SessionRow({
onKeyDown={
clickable
? (e) => {
if (e.key === 'Enter' || e.key === ' ') {
if (isResourceSessionActivationKey(e.key)) {
e.preventDefault()
handleClick()
}
@@ -658,7 +661,6 @@ export function ResourceUsageStatusSegment({
const ptyIdsByTabId = useAppStore((s) => s.ptyIdsByTabId)
const tabsByWorktree = useAppStore((s) => s.tabsByWorktree)
const runtimePaneTitlesByTabId = useAppStore((s) => s.runtimePaneTitlesByTabId)
const setActiveView = useAppStore((s) => s.setActiveView)
const openModal = useAppStore((s) => s.openModal)
const openSpacePage = useAppStore((s) => s.openSpacePage)
const activeView = useAppStore((s) => s.activeView)
@@ -949,10 +951,9 @@ export function ResourceUsageStatusSegment({
})
}, [])
// Why: navigation handlers leave the popover open so users can chain
// multiple jumps (e.g. browse worktrees, hop between terminals) without
// having to re-open the popover each time. The popover still closes via
// its own outside-click / Escape handlers.
// Why: worktree navigation leaves the popover open so users can browse the
// tree without reopening it; bound terminal rows close explicitly because
// focus transfer is intentionally suppressed by onFocusOutside below.
const navigateToWorktree = useCallback((worktreeId: string): void => {
if (worktreeId === ORPHAN_WORKTREE_ID || worktreeId.startsWith(`${UNATTRIBUTED_REPO_ID}::`)) {
return
@@ -962,21 +963,14 @@ export function ResourceUsageStatusSegment({
const navigateToTab = useCallback(
(tabId: string, paneKey: string | null) => {
// Resolve the tab → worktree from the store so we can also reveal the
// worktree in the sidebar before flipping the active tab.
for (const [worktreeId, tabs] of Object.entries(tabsByWorktree)) {
if (tabs.some((t) => t.id === tabId)) {
activateAndRevealWorktree(worktreeId)
break
}
}
setActiveView('terminal')
// Why: paneKey suffixes are stable UUID leaf ids after replay/reload.
// Legacy numeric keys degrade to tab-only activation instead of guessing.
const parsed = paneKey ? parsePaneKey(paneKey) : null
activateTabAndFocusPane(tabId, parsed?.tabId === tabId ? parsed.leafId : null)
navigateResourceSessionToTab(tabId, paneKey, {
tabsByWorktree,
setOpen,
activateAndRevealWorktree,
activateTabAndFocusPane
})
},
[tabsByWorktree, setActiveView]
[tabsByWorktree]
)
const deleteWorktree = useCallback((worktreeId: string): void => {
@@ -0,0 +1,72 @@
import { describe, expect, it, vi } from 'vitest'
import {
isResourceSessionActivationKey,
navigateResourceSessionToTab,
type ResourceSessionNavigationDeps
} from './resource-session-navigation'
const TAB_ID = 'tab-1'
const OTHER_TAB_ID = 'tab-2'
const LEAF_ID = '11111111-1111-4111-8111-111111111111'
function makeDeps(events: string[] = []): ResourceSessionNavigationDeps {
return {
tabsByWorktree: {
'wt-1': [{ id: TAB_ID }],
'wt-2': [{ id: OTHER_TAB_ID }]
},
setOpen: vi.fn((open: boolean) => events.push(`open:${open}`)),
activateAndRevealWorktree: vi.fn((worktreeId: string) => {
events.push(`worktree:${worktreeId}`)
}),
activateTabAndFocusPane: vi.fn((tabId: string, leafId: string | null) => {
events.push(`tab:${tabId}:${leafId ?? 'null'}`)
})
}
}
describe('resource session navigation', () => {
it('closes the popover first, reveals the owning worktree, and focuses a matching pane', () => {
const events: string[] = []
const deps = makeDeps(events)
navigateResourceSessionToTab(TAB_ID, `${TAB_ID}:${LEAF_ID}`, deps)
expect(events).toEqual([`open:false`, 'worktree:wt-1', `tab:${TAB_ID}:${LEAF_ID}`])
expect(deps.activateTabAndFocusPane).toHaveBeenCalledWith(TAB_ID, LEAF_ID, {
flashFocusedPane: true
})
})
it('degrades malformed and mismatched pane keys to tab-only activation', () => {
const malformed = makeDeps()
navigateResourceSessionToTab(TAB_ID, `${TAB_ID}:1`, malformed)
expect(malformed.activateTabAndFocusPane).toHaveBeenCalledWith(TAB_ID, null, {
flashFocusedPane: true
})
const mismatched = makeDeps()
navigateResourceSessionToTab(TAB_ID, `${OTHER_TAB_ID}:${LEAF_ID}`, mismatched)
expect(mismatched.activateTabAndFocusPane).toHaveBeenCalledWith(TAB_ID, null, {
flashFocusedPane: true
})
})
it('still activates the tab when the worktree mapping is stale', () => {
const deps = makeDeps()
deps.tabsByWorktree = {}
navigateResourceSessionToTab(TAB_ID, `${TAB_ID}:${LEAF_ID}`, deps)
expect(deps.activateAndRevealWorktree).not.toHaveBeenCalled()
expect(deps.activateTabAndFocusPane).toHaveBeenCalledWith(TAB_ID, LEAF_ID, {
flashFocusedPane: true
})
})
it('keeps keyboard activation equivalent to click for Enter and Space only', () => {
expect(isResourceSessionActivationKey('Enter')).toBe(true)
expect(isResourceSessionActivationKey(' ')).toBe(true)
expect(isResourceSessionActivationKey('Escape')).toBe(false)
})
})
@@ -0,0 +1,40 @@
import { parsePaneKey } from '../../../../shared/stable-pane-id'
type TabLookup = Record<string, { id: string }[]>
export type ResourceSessionNavigationDeps = {
tabsByWorktree: TabLookup
setOpen: (open: boolean) => void
activateAndRevealWorktree: (worktreeId: string) => unknown
activateTabAndFocusPane: (
tabId: string,
leafId: string | null,
opts: { flashFocusedPane: true }
) => void
}
export function isResourceSessionActivationKey(key: string): boolean {
return key === 'Enter' || key === ' '
}
export function navigateResourceSessionToTab(
tabId: string,
paneKey: string | null,
deps: ResourceSessionNavigationDeps
): void {
deps.setOpen(false)
for (const [worktreeId, tabs] of Object.entries(deps.tabsByWorktree)) {
if (tabs.some((tab) => tab.id === tabId)) {
deps.activateAndRevealWorktree(worktreeId)
break
}
}
// Why: paneKey suffixes are stable UUID leaf ids after replay/reload.
// Legacy numeric keys degrade to tab-only activation instead of guessing.
const parsed = paneKey ? parsePaneKey(paneKey) : null
deps.activateTabAndFocusPane(tabId, parsed?.tabId === tabId ? parsed.leafId : null, {
flashFocusedPane: true
})
}