perf(checks-panel): gate the terminal-cwd poll on window visibility

The Checks panel polls window.api.pty.getCwd every 4s to follow the active
terminal's cwd. It was gated only on panel visibility (isPanelVisible), not
window visibility — so a hidden or minimized window kept spawning lsof on macOS
every 4s (the 4s cadence exceeds getCwd's 1.5s per-pid cache TTL, so every tick
is a guaranteed miss and a fresh subprocess).

Swap the raw window.setInterval for installWindowVisibilityInterval — the shared
helper every sibling timer already uses — which pauses while the window is hidden
and runs an immediate refresh on becoming visible so a cd made while hidden is
picked up on return. A visible-but-unfocused window (second monitor) keeps
polling, matching prior behavior.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil
2026-07-20 22:13:57 -07:00
co-authored by Orca
parent 05c32c4757
commit 92d2090593
2 changed files with 43 additions and 4 deletions
@@ -129,6 +129,36 @@ describe('useChecksPanelTerminalWorktree', () => {
expect(latest?.worktree).toBe(parentWorktree)
})
it('does not spawn getCwd while the window is hidden, then refreshes on becoming visible', async () => {
getCwdMock.mockResolvedValue(`${CHILD_PATH}/src`)
const setVisibility = (state: 'visible' | 'hidden'): void => {
Object.defineProperty(document, 'visibilityState', {
configurable: true,
get: () => state
})
}
setVisibility('hidden')
try {
await renderHook(parentWorktree)
await flushMicrotasks()
// Hidden window: no lsof-backed cwd probe at all.
expect(getCwdMock).not.toHaveBeenCalled()
expect(latest?.worktree).toBe(parentWorktree)
setVisibility('visible')
await act(async () => {
document.dispatchEvent(new Event('visibilitychange'))
})
await flushMicrotasks()
// Becoming visible runs an immediate refresh (a `cd` made while hidden
// is picked up promptly on return).
expect(getCwdMock).toHaveBeenCalledWith('pty-1')
expect(latest?.worktree).toBe(childWorktree)
} finally {
setVisibility('visible')
}
})
it('keeps the default worktree when the cwd is outside every worktree', async () => {
getCwdMock.mockResolvedValue('/tmp/scratch')
@@ -1,6 +1,7 @@
import { useEffect, useMemo, useState } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { useAppStore } from '@/store'
import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval'
import { useAllWorktrees, useRepoMap } from '@/store/selectors'
import { isRemoteRuntimePtyId } from '@/runtime/runtime-terminal-inspection'
import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id'
@@ -31,7 +32,8 @@ type ChecksPanelTerminalWorktree = {
* the *remote* shell's OSC 7 path, which must not resolve a local worktree.
* getCwd reads the local shell pid, so it is correct for that case. Remote
* runtime PTYs are skipped (their cwd lives on the relay host). Polling is
* gated on panel visibility, so a hidden panel does no background work and the
* gated on both panel visibility and window visibility, so a hidden panel or a
* hidden/minimized window does no background work (no `lsof` spawns) and the
* caller's fallback worktree is used.
*
* Resolution is scoped to locally-executing worktrees: the cwd comes from a
@@ -123,11 +125,18 @@ export function useChecksPanelTerminalWorktree(args: {
}
}
void refresh()
const interval = window.setInterval(refresh, TERMINAL_CWD_POLL_MS)
// Why: getCwd shells out to `lsof` on macOS every tick (poll cadence >
// the 1.5s per-pid cache TTL, so each tick is a guaranteed miss). Gate on
// window visibility so a hidden/minimized window spawns no subprocesses;
// the helper runs an immediate refresh on becoming visible so a `cd` made
// while hidden is picked up promptly on return.
const stopInterval = installWindowVisibilityInterval({
run: () => void refresh(),
intervalMs: TERMINAL_CWD_POLL_MS
})
return () => {
disposed = true
window.clearInterval(interval)
stopInterval()
}
}, [activeTerminalPtyId, shouldPollCwd])