perf: clean tab bar focus timers from root ref (#3627)

* perf: clean tab bar focus timers from root ref

* test: cover tab bar focus cleanup on unmount

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil 2026-05-30 13:06:59 -07:00 committed by GitHub
parent 6e2f20abe2
commit cbca6b2456
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 52 additions and 5 deletions

View File

@ -1,3 +1,5 @@
/* oxlint-disable max-lines -- Why: keeping these mocked TabBar wiring cases
* together avoids duplicating the lightweight renderer harness. */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const appStoreSnapshot: {
@ -155,6 +157,7 @@ vi.mock('@/components/ui/dropdown-menu', () => ({
type ReactElementLike = {
type: unknown
props: Record<string, unknown>
ref?: unknown
}
function findChildrenByType(node: unknown, typeName: string): ReactElementLike[] {
@ -241,6 +244,7 @@ describe('TabBar context menu wiring', () => {
callback(0)
return 1
})
vi.stubGlobal('cancelAnimationFrame', vi.fn())
vi.stubGlobal('window', {
setTimeout,
clearTimeout,
@ -324,4 +328,35 @@ describe('TabBar context menu wiring', () => {
expect(focusTerminalTabSurface).toHaveBeenCalledWith('new-terminal')
expect(focusTerminalTabSurface).not.toHaveBeenCalledWith('old-terminal')
})
it('cancels delayed menu focus when the tab bar root unmounts', async () => {
vi.useFakeTimers()
Object.assign(window, { setTimeout, clearTimeout })
const { focusTerminalTabSurface } = await import('@/lib/focus-terminal-tab-surface')
const element = await renderTabBar({
tabs: [TERMINAL_TAB],
activeTabId: 'old-terminal',
activeTabType: 'terminal',
onNewTerminalTab: () => {
window.setTimeout(() => {
appStoreSnapshot.activeTabId = 'new-terminal'
appStoreSnapshot.activeTabType = 'terminal'
}, 100)
}
})
const newTerminalItem = findChildrenByType(element, 'DropdownMenuItem')[0]
const menuContent = findChildrenByType(element, 'DropdownMenuContent')[0]
;(newTerminalItem.props.onSelect as () => void)()
;(menuContent.props.onCloseAutoFocus as (event: { preventDefault: () => void }) => void)({
preventDefault: vi.fn()
})
const root = findChildrenByType(element, 'div')[0]
const rootRef = (root.props.ref ?? root.ref) as (node: HTMLDivElement | null) => void
rootRef(null)
await vi.advanceTimersByTimeAsync(5000)
expect(focusTerminalTabSurface).not.toHaveBeenCalled()
})
})

View File

@ -338,13 +338,24 @@ function TabBarInner({
})
}
}
useEffect(
() => () => {
const clearPendingNewTabMenuFocusOnUnmountRef = useRef<
((node: HTMLDivElement | null) => void) | null
>(null)
if (clearPendingNewTabMenuFocusOnUnmountRef.current === null) {
clearPendingNewTabMenuFocusOnUnmountRef.current = (node: HTMLDivElement | null): void => {
if (node !== null) {
return
}
// Why: the delayed focus handoff is scoped to this tab bar instance.
// A root ref cleanup cancels it at the DOM owner boundary without an
// otherwise cleanup-only React Effect.
clearPendingNewTabMenuFocusAnimation()
clearPendingNewTabMenuFocusRetry()
},
[]
)
}
}
const clearPendingNewTabMenuFocusOnUnmount = clearPendingNewTabMenuFocusOnUnmountRef.current
useEffect(() => {
if (!newTabMenuOpen) {
return
@ -517,6 +528,7 @@ function TabBarInner({
return (
<div
ref={clearPendingNewTabMenuFocusOnUnmount}
className="flex items-stretch h-full overflow-hidden flex-1 min-w-0"
// Why: only drops aimed at the top tab/session strip should open files in
// Orca's editor. Terminal-pane drops need to keep inserting file paths