perf: keep git status polling visible-only (#2604)

This commit is contained in:
Neil 2026-05-21 22:55:16 -07:00 committed by GitHub
parent fb863590cd
commit 2a3175b2ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 24 additions and 31 deletions

View File

@ -90,7 +90,12 @@ async function usePollingOnce(
removeEventListener: vi.fn()
})
vi.stubGlobal('document', { hasFocus: () => true })
vi.stubGlobal('document', {
visibilityState: 'visible',
hasFocus: () => true,
addEventListener: vi.fn(),
removeEventListener: vi.fn()
})
vi.stubGlobal('setInterval', vi.fn())
vi.stubGlobal('clearInterval', vi.fn())
@ -161,7 +166,7 @@ describe('useGitStatusPolling', () => {
expect(state.setGitStatus).not.toHaveBeenCalled()
})
it('does not overlap slow git status polls and runs one trailing refresh', async () => {
it('does not overlap slow visible git status polls and runs one trailing refresh', async () => {
vi.resetModules()
let intervalCallback: (() => void) | null = null
let resolveFirst!: (value: GitStatusResult) => void
@ -219,7 +224,12 @@ describe('useGitStatusPolling', () => {
addEventListener: vi.fn(),
removeEventListener: vi.fn()
})
vi.stubGlobal('document', { hasFocus: () => true })
vi.stubGlobal('document', {
visibilityState: 'visible',
hasFocus: () => false,
addEventListener: vi.fn(),
removeEventListener: vi.fn()
})
vi.stubGlobal(
'setInterval',
vi.fn((callback: () => void) => {

View File

@ -7,6 +7,7 @@ import { getConnectionId } from '@/lib/connection-context'
import { getRuntimeGitConflictOperation } from '@/runtime/runtime-git-client'
import { refreshGitStatusForWorktree } from './git-status-refresh'
import { createCoalescedPollRunner } from './coalesced-poll-runner'
import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval'
const POLL_INTERVAL_MS = 3000
@ -115,24 +116,9 @@ export function useGitStatusPolling(): void {
fetchStatusRef.current = fetchStatus
useEffect(() => {
void fetchStatus()
// Why: skip IPC-heavy git status calls when the window is not focused.
// These intervals run at the App root level regardless of which sidebar tab
// is open, so gating on document.hasFocus() prevents wasted CPU and IPC
// traffic while the user is working in another application.
const intervalId = setInterval(() => {
if (document.hasFocus()) {
void fetchStatus()
}
}, POLL_INTERVAL_MS)
// Why: when the user returns to the window, poll immediately so the sidebar
// shows up-to-date status without waiting up to POLL_INTERVAL_MS.
const onFocus = (): void => void fetchStatus()
window.addEventListener('focus', onFocus)
return () => {
clearInterval(intervalId)
window.removeEventListener('focus', onFocus)
}
// Why: this root-level poll should pause while hidden, but visible
// unfocused windows still need fresh status for second-display workflows.
return installWindowVisibilityInterval({ run: fetchStatus, intervalMs: POLL_INTERVAL_MS })
}, [fetchStatus])
// Why: poll conflict operation for non-active worktrees that have a stale
@ -169,18 +155,15 @@ export function useGitStatusPolling(): void {
// flight and coalesce skipped ticks into one trailing pass so stale badges
// catch up without stacking SSH/RPC work.
const pollRunner = createCoalescedPollRunner(pollStale)
pollRunner.run()
const intervalId = setInterval(() => {
if (document.hasFocus()) {
pollRunner.run()
}
}, POLL_INTERVAL_MS)
const onFocus = (): void => pollRunner.run()
window.addEventListener('focus', onFocus)
// Why: conflict badges are visible sidebar state; keep them fresh in
// visible unfocused windows, but do not poll disconnected hidden windows.
const stopVisiblePoll = installWindowVisibilityInterval({
run: () => pollRunner.run(),
intervalMs: POLL_INTERVAL_MS
})
return () => {
pollRunner.dispose()
clearInterval(intervalId)
window.removeEventListener('focus', onFocus)
stopVisiblePoll()
}
}, [staleConflictWorktrees, setConflictOperation, isConnectionReady])
}