perf(quick-open): preserve scans across worktree metadata updates (#34838)

* perf(quick-open): preserve scans across worktree metadata updates

* test(quick-open): collapse duplicate metadata-update case

* fix(quick-open): key exclude request by state instead of render-time ref

React Doctor rejects mutating a ref during render. Hold the nested-worktree
exclude request in state and replace it only when its key changes, so
metadata-only worktree updates keep the same object identity.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-28 13:06:19 -07:00
committed by GitHub
co-authored by OrcaWin
parent 09bd3b96d4
commit be7a6eecc7
3 changed files with 93 additions and 2 deletions
@@ -0,0 +1,79 @@
// @vitest-environment happy-dom
import { act } from 'react'
import { expect, it, vi } from 'vitest'
import { useAppStore } from '@/store'
import type { Worktree } from '../../../shared/worktree/types'
import type { RuntimeFileListState } from './quick-open-file-list'
import {
cancelRuntimeFileListMock,
flushEffects,
listRuntimeFilesMock,
makeRemoteWorktree,
renderProbe,
searchRuntimeFilePathsMock,
seedRemoteWorktree
} from './quick-open-file-list-test-harness'
vi.mock('@/runtime/runtime-file-search-client', async () => {
const mocks = await import('./__mocks__/quick-open-runtime-file-client')
return {
listRuntimeFiles: mocks.listRuntimeFilesMock,
cancelRuntimeFileList: mocks.cancelRuntimeFileListMock,
searchRuntimeFilePaths: mocks.searchRuntimeFilePathsMock
}
})
async function replaceWorktrees(worktrees: Worktree[]): Promise<void> {
await act(async () => {
useAppStore.setState({ worktreesByRepo: { 'repo-remote': worktrees } })
})
await flushEffects()
}
it('keeps a settled inventory through metadata updates', async () => {
seedRemoteWorktree()
const states: RuntimeFileListState[] = []
await renderProbe({ enabled: true, states, worktreeId: 'wt-remote' })
const settledFiles = states.at(-1)?.files
const initialRequest = listRuntimeFilesMock.mock.calls[0]?.[1]
const rendersBeforeUpdates = states.length
await replaceWorktrees([{ ...makeRemoteWorktree(), head: 'commit-1' }])
await replaceWorktrees([{ ...makeRemoteWorktree(), head: 'commit-2' }])
expect(listRuntimeFilesMock).toHaveBeenCalledOnce()
expect(cancelRuntimeFileListMock).not.toHaveBeenCalled()
expect(initialRequest?.signal?.aborted).toBe(false)
expect(states.slice(rendersBeforeUpdates).every((state) => !state.loading)).toBe(true)
expect(states.at(-1)?.files).toBe(settledFiles)
})
it('keeps an in-flight query through metadata updates but replaces it for changed exclusions', async () => {
vi.useFakeTimers()
seedRemoteWorktree()
searchRuntimeFilePathsMock.mockReturnValue(new Promise(() => {}))
try {
await renderProbe({ enabled: true, states: [], query: 'target', worktreeId: 'wt-remote' })
await act(async () => vi.advanceTimersByTimeAsync(120))
const initialRequest = searchRuntimeFilePathsMock.mock.calls[0]?.[1]
await replaceWorktrees([{ ...makeRemoteWorktree(), comment: 'Updated comment' }])
await act(async () => vi.advanceTimersByTimeAsync(120))
expect(initialRequest?.signal?.aborted).toBe(false)
expect(searchRuntimeFilePathsMock).toHaveBeenCalledOnce()
const nested = { ...makeRemoteWorktree(), id: 'nested', path: '/srv/remote/nested' }
await replaceWorktrees([makeRemoteWorktree(), nested])
expect(initialRequest?.signal?.aborted).toBe(true)
await act(async () => vi.advanceTimersByTimeAsync(120))
expect(searchRuntimeFilePathsMock).toHaveBeenCalledTimes(2)
expect(searchRuntimeFilePathsMock.mock.calls[1]?.[1].excludePaths).toEqual([nested.path])
await replaceWorktrees([makeRemoteWorktree()])
await act(async () => vi.advanceTimersByTimeAsync(120))
expect(searchRuntimeFilePathsMock).toHaveBeenCalledTimes(3)
expect(searchRuntimeFilePathsMock.mock.calls[2]?.[1].excludePaths).toBeUndefined()
} finally {
vi.useRealTimers()
}
})
@@ -1,3 +1,4 @@
import { useState } from 'react'
import type { Worktree } from '../../../shared/worktree/types'
import { getNestedWorktreeExcludePaths } from './quick-open-nested-worktrees'
@@ -44,3 +45,14 @@ export function getNestedWorktreeExcludeRequest(
// JSON preserves newline-containing paths in stable dependency keys; pass the original path array to IPC.
return { paths, key: JSON.stringify(paths) }
}
// Why: worktree metadata updates rebuild the request object; only a key change should restart scans.
export function useKeyedExcludeRequest(
request: NestedWorktreeExcludeRequest
): NestedWorktreeExcludeRequest {
const [keyed, setKeyed] = useState(request)
if (keyed.key !== request.key) {
setKeyed(request)
}
return keyed.key === request.key ? keyed : request
}
@@ -1,4 +1,4 @@
import { getRuntimeFileListTarget } from './quick-open-file-list-target'
import { getRuntimeFileListTarget, useKeyedExcludeRequest } from './quick-open-file-list-target'
export {
getRuntimeFileListTarget,
getNestedWorktreeExcludeRequest
@@ -93,7 +93,7 @@ export function useRuntimeFileListForWorktree({
() => getRuntimeFileListTarget(worktreeId, worktreePath, repoWorktrees),
[repoWorktrees, worktreeId, worktreePath]
)
const { excludeRequest } = target
const excludeRequest = useKeyedExcludeRequest(target.excludeRequest)
const operationOwnerState = useAppStore(
useShallow((state) => ({