fix(app): prune stale file-explorer paths during hydration#1146
fix(app): prune stale file-explorer paths during hydration#1146muzhi1991 wants to merge 1 commit into
Conversation
07c9efc to
a26ec99
Compare
|
| Filename | Overview |
|---|---|
| packages/app/src/hooks/use-file-explorer-actions.ts | Adds silent flag threading through all five error/success branches of requestDirectoryListing and exports the pure dropFailedExpandedPaths helper; logic is correct but any transient listing failure (not just ENOENT) is indistinguishable from a missing-on-disk failure and will cause the path to be pruned |
| packages/app/src/components/file-explorer-pane.tsx | Converts requestPersistedExpandedPaths to async/Promise.all with silent flag and post-hydration pruning; type signature for requestDirectoryListing is correctly updated to include silent?; the function is still fire-and-forgotten (void) from initializeExplorer, which is intentional for non-blocking hydration |
| packages/app/src/hooks/use-file-explorer-actions.test.ts | New test file with four focused unit tests for dropFailedExpandedPaths; covers empty failures, single-path pruning, multi-path pruning, and non-member failures — all correct |
Sequence Diagram
sequenceDiagram
participant UE as useEffect
participant IE as initializeExplorer
participant RPE as requestPersistedExpandedPaths
participant RDL as requestDirectoryListing
participant PS as panelStore
UE->>IE: void initializeExplorer(...)
IE->>RDL: "await requestDirectoryListing(".", {recordHistory:false})"
RDL-->>IE: true (root listing succeeds)
IE->>RPE: void requestPersistedExpandedPaths(...) [fire-and-forget]
IE-->>UE: resolves
Note over RPE: runs concurrently
RPE->>PS: read expandedPathsByWorkspace[key]
PS-->>RPE: [".", "src", "vendor/deleted"]
RPE->>RPE: "candidates = ["src", "vendor/deleted"]"
par Promise.all
RPE->>RDL: "requestDirectoryListing("src", {silent:true})"
RDL-->>RPE: true
and
RPE->>RDL: "requestDirectoryListing("vendor/deleted", {silent:true})"
Note over RDL: ENOENT — silent, no lastError written
RDL-->>RPE: false
end
RPE->>RPE: "failed = {"vendor/deleted"}"
RPE->>PS: re-read latest expandedPathsByWorkspace[key]
PS-->>RPE: [".", "src", "vendor/deleted"]
RPE->>RPE: "pruned = dropFailedExpandedPaths(latest, failed)"
RPE->>PS: setExpandedPathsForWorkspace(key, [".", "src"])
Reviews (3): Last reviewed commit: "fix(app): prune stale file-explorer path..." | Re-trigger Greptile
a26ec99 to
f273762
Compare
When a subdirectory in expandedPathsByWorkspace no longer exists on disk (e.g. removed externally while the app was closed), requestPersistedExpandedPaths was fire-and-forgetting a directory listing for each persisted path. A single ENOENT wrote to the shared lastError, so the whole panel rendered as an error banner that only Retry could clear, and the stale path stayed in the persisted state so the error returned on the next open. Make the hydration phase await its listings, pass a new silent flag so failed listings do not overwrite lastError, and prune any failed paths from expandedPathsByWorkspace so subsequent opens stay clean. Interactive paths (toggle, Retry, ancestor expand, navigation) keep their existing error-reporting behaviour. Fixes getpaseo#1145.
f273762 to
91d367c
Compare
Fixes #1145.
Problem
Opening the file-explorer panel for a workspace fires a directory listing for every entry in
expandedPathsByWorkspace. When one of those subdirectories has been removed outside the app, the listing returns an ENOENT, which writes to the sharedlastErrorand renders the whole panel as an error banner with a Retry button. Retry recovers the current session, but the failed path stays inexpandedPathsByWorkspace, so the same error returns on the next open.I hit this on macOS with 0.1.80 after deleting a directory I'd previously expanded. Once I confirmed the stale paths were sitting in
panel-statelocalStorage and that pruning them by hand made the banner go away, the cause was clear.Fix
In the hydration
useEffectofFileExplorerPane:awaitthe persisted listings instead of fire-and-forget.silentflag torequestDirectoryListingso a failed listing does not overwrite the panel'slastError.expandedPathsByWorkspaceafter hydration so the persisted state self-heals.The interactive paths (toggle, Retry, ancestor expand, navigation) don't pass
silent, so their error-reporting behaviour is unchanged.A small pure helper
dropFailedExpandedPathsis exported and unit-tested so the prune logic is covered without rendering the React tree.Verification
Only tested on macOS desktop (relay transport) — other platforms not exercised.
npm run typecheck --workspace=@getpaseo/app✓npm run format:check✓npm run lint --workspace=@getpaseo/app -- src/hooks/use-file-explorer-actions.ts src/hooks/use-file-explorer-actions.test.ts src/components/file-explorer-pane.tsx— no new findings (8 pre-existing warnings infile-explorer-pane.tsxonmainare unrelated)npx vitest run src/hooks/use-file-explorer-actions.test.ts✓ 4/4Bug repro & root-cause confirmation:
panel-statein localStorage; found the stale paths under the workspace key.~/.paseo/daemon.log: every open of the panel emitsFailed to fulfill file explorer request for workspace … path: <stale-path>for every stale entry.expandedPathsByWorkspacemade the banner stop appearing — confirming that the persisted state is the trigger, which is exactly what this patch removes automatically on hydration.I didn't rebuild the Electron desktop app to run the patched code end-to-end, but the patch only changes the front-end hydration flow and is covered by the unit test plus the manual root-cause evidence above.