mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
refactor(file-explorer): use useMemo for paths; remove relay reference
Replace manual ref-based caching with proper React hooks for content-stable path memoization. Remove outdated relay grace-time reference documentation from code review cycle.
This commit is contained in:
@@ -7,18 +7,19 @@ for finding E of the P1 review; line numbers are as of that change.
|
||||
## What decides the grace window
|
||||
|
||||
`startGrace` (`src/relay/relay.ts:1140`) samples
|
||||
`ptyHandler.configuredGraceTimeMs` and picks one branch, always passing an
|
||||
explicit `timeoutMs`:
|
||||
`ptyHandler.configuredGraceTimeMs` and takes the **first** branch below that
|
||||
matches (`decideRelayGrace`, `src/relay/relay-grace-branch.ts`), always passing
|
||||
an explicit `timeoutMs`. Listed in that precedence order:
|
||||
|
||||
- `shutdown-deferred` — a refused kill left the PTY pooled, so the idle branch is
|
||||
unreachable; this branch supplies its own bound or a `grace=0` default would
|
||||
arm no retry at all.
|
||||
- `startup-empty-detached` — a detached relay that never accepted a client, so
|
||||
it has no PTY state to preserve. Capped at `EMPTY_DETACHED_STARTUP_GRACE_MS`.
|
||||
- `idle-no-ptys` — zero PTYs *and* zero pending creations
|
||||
(`isRelayIdle`, `src/relay/relay.ts:1175`) under the unlimited default only.
|
||||
Capped at `IDLE_RELAY_GRACE_MS` (`src/relay/relay.ts:74`, 15 min, overridable
|
||||
via `ORCA_RELAY_IDLE_GRACE_MS`).
|
||||
- `shutdown-deferred` — a refused kill left the PTY pooled, so the idle branch is
|
||||
unreachable; this branch supplies its own bound or a `grace=0` default would
|
||||
arm no retry at all.
|
||||
- `configured` — the configured value verbatim, never clamped.
|
||||
|
||||
The zero-only gate is load-bearing: the idle cap bounds *only* the unlimited
|
||||
|
||||
@@ -51,8 +51,6 @@ export function useFileExplorerTree(
|
||||
const staleDirsRef = useRef(new Set<string>())
|
||||
// Why: separates a failed root read from a superseded one — loadDir returns false for both.
|
||||
const rootReadFailedRef = useRef(false)
|
||||
const expandedRef = useRef(expanded)
|
||||
expandedRef.current = expanded
|
||||
|
||||
const loadDir = useCallback(
|
||||
async (
|
||||
@@ -179,11 +177,8 @@ export function useFileExplorerTree(
|
||||
staleDirsRef.current.delete(dirPath)
|
||||
}
|
||||
}
|
||||
for (const dirPath of collectStaleDirCachePaths(
|
||||
dirCacheRef.current,
|
||||
worktreePath,
|
||||
expandedRef.current
|
||||
)) {
|
||||
// Why: callers use the latest refreshTree identity, so this closure has the live expanded set.
|
||||
for (const dirPath of collectStaleDirCachePaths(dirCacheRef.current, worktreePath, expanded)) {
|
||||
staleDirsRef.current.add(dirPath)
|
||||
}
|
||||
const refreshSession = dirLoadTrackerRef.current.getSession()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { isDotfileRelativePath } from './file-explorer-entries'
|
||||
import type { DirCache, TreeNode } from './file-explorer-types'
|
||||
@@ -118,25 +118,17 @@ export function createVisibleFileExplorerRowProjection(
|
||||
* exists to bound.
|
||||
*/
|
||||
function useContentStableRelativePaths(relativePaths: string[], enabled: boolean): string[] {
|
||||
const cache = useRef<{ source: string[]; signature: string; value: string[] } | null>(null)
|
||||
// Why: a name-filter list is a different population, so signing it would evict the tree
|
||||
// signature — clearing the filter would then mint a fresh identity and re-issue the very
|
||||
// whole-tree check-ignore this hook exists to prevent.
|
||||
if (!enabled) {
|
||||
return relativePaths
|
||||
}
|
||||
// Why: the caller memoizes, so an unchanged tree hands back the same array — skip rebuilding a
|
||||
// signature over every visible path on re-renders that never touched the tree.
|
||||
if (cache.current?.source === relativePaths) {
|
||||
return cache.current.value
|
||||
}
|
||||
// NUL separator: it is the one byte no path segment can contain.
|
||||
const signature = relativePaths.join('\u0000')
|
||||
cache.current =
|
||||
cache.current?.signature === signature
|
||||
? { ...cache.current, source: relativePaths }
|
||||
: { source: relativePaths, signature, value: relativePaths }
|
||||
return cache.current.value
|
||||
// Why: filters need fresh identities per keystroke and must not evict the tree signature.
|
||||
// Why: NUL cannot occur in paths, so the signature can reconstruct the list losslessly.
|
||||
const signature = useMemo(
|
||||
() => (enabled ? relativePaths.join('\u0000') : null),
|
||||
[enabled, relativePaths]
|
||||
)
|
||||
const stableTreePaths = useMemo(
|
||||
() => (signature ? signature.split('\u0000') : EMPTY_RELATIVE_PATHS),
|
||||
[signature]
|
||||
)
|
||||
return enabled ? stableTreePaths : relativePaths
|
||||
}
|
||||
|
||||
export function useFileExplorerVisibleRowProjection(
|
||||
|
||||
@@ -143,6 +143,10 @@ export function useFileExplorerWatch({
|
||||
const scheduler = createFileExplorerWatchRefreshScheduler({
|
||||
refreshTree: () => refreshTreeRef.current(),
|
||||
refreshDir: (dirPath) => refreshDirRef.current(dirPath),
|
||||
// Why exact-match on `expanded`, not a normalized compare: dirPath already arrives as a
|
||||
// resolved dirCache key (resolveCachedDirPath), and refreshTree re-reads dirCache under the
|
||||
// verbatim expanded strings — a differently-spelled key it never wrote would be reported
|
||||
// covered and left stale. Only the root is normalized: it is keyed by worktreePath.
|
||||
isCoveredByFullRefresh: (dirPath) =>
|
||||
normalizeRuntimePathForComparison(dirPath) ===
|
||||
normalizeRuntimePathForComparison(currentWorktreePath) ||
|
||||
|
||||
Reference in New Issue
Block a user