fix(file-tree): invalidate both cached target and ancestor, skip collapsed refetches
When a deep directory was renamed or deleted externally and both the directory itself and a parent directory were cached, only the self entry was cleared — leaving the parent's children list holding a ghost reference to the old child. Walk up to the nearest cached ancestor in addition to the direct hit so both stale entries are dropped together. Also gate the follow-up getFileTree refetch on the directory still being expanded. Collapsed branches only need their cache cleared; they will re-hydrate naturally on the next expansion, which avoids unnecessary IPC traffic during FS event bursts.
This commit is contained in:
@@ -1107,19 +1107,26 @@ export function FileTreeTab() {
|
|||||||
for (const changed of paths) {
|
for (const changed of paths) {
|
||||||
const normalized = normalizeComparePath(changed)
|
const normalized = normalizeComparePath(changed)
|
||||||
if (!normalized) continue
|
if (!normalized) continue
|
||||||
// Walk from the changed path up to the root, invalidating the
|
// When the changed path is itself a cached directory (FS events
|
||||||
// nearest cached ancestor (or the path itself if a directory of
|
// that report the directory directly, e.g. a rename or a dir-level
|
||||||
// that name is in the cache). Checking the path itself covers
|
// notification), its own entry is stale — invalidate it.
|
||||||
// FS events that report the directory path directly.
|
if (cache.has(normalized)) {
|
||||||
|
invalidated.add(normalized)
|
||||||
|
}
|
||||||
|
// Independently of the above, walk up to the nearest cached
|
||||||
|
// ancestor: the ancestor's children listing may also be stale
|
||||||
|
// (a child was added, removed, or renamed). Without this, cases
|
||||||
|
// where both a parent and child are cached leave the parent
|
||||||
|
// holding a ghost reference to the old child.
|
||||||
let cursor = normalized
|
let cursor = normalized
|
||||||
while (cursor.length > 0) {
|
while (cursor.length > 0) {
|
||||||
if (cache.has(cursor)) {
|
|
||||||
invalidated.add(cursor)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
const slash = cursor.lastIndexOf("/")
|
const slash = cursor.lastIndexOf("/")
|
||||||
const parent = slash === -1 ? "" : cursor.slice(0, slash)
|
const parent = slash === -1 ? "" : cursor.slice(0, slash)
|
||||||
if (parent.length === 0) break
|
if (parent.length === 0) break
|
||||||
|
if (cache.has(parent)) {
|
||||||
|
invalidated.add(parent)
|
||||||
|
break
|
||||||
|
}
|
||||||
cursor = parent
|
cursor = parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1129,7 +1136,13 @@ export function FileTreeTab() {
|
|||||||
cache.delete(path)
|
cache.delete(path)
|
||||||
}
|
}
|
||||||
if (!loader) return
|
if (!loader) return
|
||||||
|
// Skip refetching directories that are no longer expanded — their
|
||||||
|
// cleared cache will be re-hydrated on the next expansion via the
|
||||||
|
// expandedPaths effect. This avoids spurious getFileTree traffic
|
||||||
|
// for collapsed branches under bursty FS activity.
|
||||||
|
const expanded = expandedPathsRef.current
|
||||||
for (const path of invalidated) {
|
for (const path of invalidated) {
|
||||||
|
if (!expanded.has(path)) continue
|
||||||
void loader(path)
|
void loader(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user