Repository navigation
feat(explorer): add optional host filesystem navigation - #23934
Chihen-Tai wants to merge 22 commits into
Conversation
Adds a Host mode to the right-sidebar File Explorer that browses the workspace host's filesystem like an SFTP browser: parent directory, breadcrumbs, sibling folders, and a one-click return to the workspace root. Project mode is unchanged. Host mode is a read-only flat list layered over the tree, so the tree's caches, watchers, git decorations and search stay bound to the workspace. Listing reuses the existing any-directory browse paths (ssh:browseDir, files.browseServerDir) plus a local fs:browseHostDir that returns names only and never touches the external-path allowlist. Opening a file resolves it in main: only a regular file outside the workspace gets a single-file grant, and it opens read-only and is never persisted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bservable Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hydration keeps editor tab chrome even without a backing file, so a Host-mode tab excluded from openFilesByWorktree still came back as an empty tab after restart. Filter those tabs where unified tabs are persisted, and cover the full persist/restore round trip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Host-mode state lived in FileExplorerFiles, so every toggle re-rendered the whole tree (~110 ms long task in dev on a large folder) and the Host bar was inserted into the flow, shifting the query strip twice. Move the state into a provider whose children bail out, render the Host bar inside the tree overlay, cover the query row in place, delay visible spinners by 200 ms, and dim the current folder during slow navigation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…for workspace searches The Host overlay covered the Project Names/Contents inputs without making them inert, so Tab or a search focus request could land in them and run a workspace search behind the Host view. Make them inert, and return to Project mode whenever a workspace Contents search is requested (shortcut, command, Contents tab) so the search is always visible. Split the context value into its own module so Fast Refresh keeps working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 Walkthrough
Priority: ➖ Normal Merge Risk: 🔵 Low · up to
|
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 29.51% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 61 functions across 39 files. (6 skipped:… | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (4 passed)
-
Check name Status Explanation Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request. Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request. Title check ✅ Passed The title clearly and concisely describes the primary change: optional host filesystem navigation in the Explorer. Description check ✅ Passed The description is detailed and covers the user impact, implementation, rationale, security model, testing, visual proof, limitations, and checklist. The required Linked Issue section is incomplete be…
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 29.51% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 61 functions across 39 files. (6 skipped: 6 unsupported.)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.
🟡 Minor · Store captured provenance when a Host tab becomes writable. · open-file-apply.ts:161-163
src/renderer/src/store/slices/editor/actions/open-file-apply.ts:161-163
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winStore captured provenance when a Host tab becomes writable.
For a local Host tab, the writable reopen captures
operationProvenance, butrefreshExternalSshProvenanceis false becauseexternalSshTargetIdis absent. The existing-tab update therefore keeps the old undefined value and drops the capture. Later save and rename paths recapture ownership instead of using the snapshot taken when the tab became writable.Preserve existing provenance, but store the captured value when the existing tab has none.
🐛 Suggested fix
operationProvenance: refreshExternalSshProvenance ? operationProvenance - : f.operationProvenance, + : (f.operationProvenance ?? operationProvenance),
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 3e7fe2da-e6fe-4f1e-9b74-29bc663a3c69
📒 Files selected for processing (43)
src/main/ipc/filesystem.tssrc/main/ipc/filesystem/filesystem-host-browse-handlers.test.tssrc/main/ipc/filesystem/filesystem-host-browse-handlers.tssrc/main/runtime/runtime-server-environment-commands.tssrc/preload/api/filesystem-api.tssrc/preload/api/fs-bridge.tssrc/renderer/src/components/right-sidebar/FileExplorer.host-mode.test.tsxsrc/renderer/src/components/right-sidebar/FileExplorer.tsxsrc/renderer/src/components/right-sidebar/FileExplorerHostBar.test.tsxsrc/renderer/src/components/right-sidebar/FileExplorerHostBar.tsxsrc/renderer/src/components/right-sidebar/FileExplorerHostList.test.tsxsrc/renderer/src/components/right-sidebar/FileExplorerHostList.tsxsrc/renderer/src/components/right-sidebar/FileExplorerHostQueryRow.tsxsrc/renderer/src/components/right-sidebar/FileExplorerToolbar.test.tsxsrc/renderer/src/components/right-sidebar/FileExplorerToolbar.tsxsrc/renderer/src/components/right-sidebar/file-explorer-host-mode-context-value.tssrc/renderer/src/components/right-sidebar/file-explorer-host-mode-context.tsxsrc/renderer/src/components/right-sidebar/file-explorer-host-mode.test.tssrc/renderer/src/components/right-sidebar/file-explorer-host-mode.tssrc/renderer/src/components/right-sidebar/file-explorer-host-open.test.tssrc/renderer/src/components/right-sidebar/file-explorer-host-open.tssrc/renderer/src/components/right-sidebar/use-file-explorer-host-browser.test.tsxsrc/renderer/src/components/right-sidebar/use-file-explorer-host-browser.tssrc/renderer/src/components/right-sidebar/use-file-explorer-host-mode.test.tsxsrc/renderer/src/components/right-sidebar/use-file-explorer-host-mode.tssrc/renderer/src/components/sidebar/RemoteFileBrowserBreadcrumbs.tsxsrc/renderer/src/components/sidebar/remote-file-browser-drive-paths.test.tssrc/renderer/src/components/sidebar/remote-file-browser-drive-paths.tssrc/renderer/src/components/sidebar/remote-file-browser-helpers.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/fr.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/renderer/src/lib/workspace-session-editor-drafts.test.tssrc/renderer/src/lib/workspace-session-unified-tabs.tssrc/renderer/src/lib/workspace-session.tssrc/renderer/src/store/slices/editor-read-only-tabs.test.tssrc/renderer/src/store/slices/editor-session-host-browse-restore.test.tssrc/renderer/src/store/slices/editor/actions/open-file-apply.tssrc/renderer/src/store/slices/editor/types/open-file.tssrc/shared/filesystem-entry-types.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.
…on desktop APIs - A workspace symlink that points outside now opens read-only: local/SSH ownership follows main's canonical verdict; only paired servers fall back to the literal path. - The web client stubs ssh.browseDir, so SSH Host mode now requires the desktop browse and resolve APIs, like local. - Up from a UNC share root without a trailing separator is a no-op. - A read-only tab reopened writable keeps the provenance captured at that moment; restored tabs still resolve their owner lazily. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Important
Host mode hides the Project tree but leaves its keyboard shortcuts live. The Host toggle keeps focus inside the Explorer shell (data-orca-explorer-shell), and useFileExplorerKeys listens on window in the capture phase while only ignoring targets marked data-ignore-file-explorer-keys — which the Host list/bar/filter have, but the toolbar does not. So immediately after entering Host mode, Delete/Enter/arrow keys act on the hidden tree's selection (local Delete goes to Trash with no confirmation, Enter starts a rename). The inert boundary blocks focus, not these ref-driven shortcuts. Details inline.
Reviewed changes
- Host pane and state: new read-only overlay (
FileExplorerHostOverlay/HostBar/HostList/HostQueryRow), a Host-mode context provider, a toolbar hard-drive toggle, andinertboundaries that leave the Project tree mounted but covered. - Main-process browse/resolve: new
fs:browseHostDir(names/types only) andfs:resolveHostBrowseEntry(realpath + stat; grants exactly the clicked regular file locally, SSH only classifies);browseServerDirectoryextracted fromRuntimeServerEnvironmentCommandsand reused. - Open/planning logic:
file-explorer-host-mode.ts,file-explorer-host-open.ts,use-file-explorer-host-browser.ts,use-file-explorer-host-mode.tsroute local/SSH/runtime sources; workspace files open writable, outside files open read-only withhostBrowse: true. - Session-only tabs:
hostBrowseexcluded frombuildEditorSessionDataandbuildPersistedUnifiedTabSessionData;applyOpenFileToStatenever promotes it onto an existing tab and clears it on a normal writable open. - UNC/WSL/long-path paths:
uncRootOf, theuncbranch insplitBrowsePath,parentOfUncPath,parentPath,joinPath, plus reworkedRemoteFileBrowserBreadcrumbs. - i18n:
fileExplorer.host.*across en/es/fr/ja/ko/zh.
ℹ️ Host-tab exclusion from the incremental session patch is untested
The exclusion itself holds — every Host open goes through openFile → openWorkspaceEditorItem, which always reallocates groupsByWorktree/unifiedTabsByWorktree, so buildWorkspaceSessionPatch always rebuilds the unified tabs and runs the sessionOnlyEditorIds filter. But no test exercises that path with a live Host tab: vanished full-payload test is the only one pinning both filters, the "rewrites the tab chrome" test only checks the reopen-writable direction, and workspace-session-editor-drafts.test.ts never populates unified tabs. The production open also uses preview: true while the restore fixture uses preview: false, so preview replacement of a Host file is untested.
Technical details
# Host-tab session exclusion relies on an untested coupling
## Affected sites
- `src/renderer/src/lib/workspace-session-unified-tabs.ts` — filter is correct but only reached via the `groupsByWorktree`/`unifiedTabsByWorktree` reference change, not `openFiles`.
- `src/renderer/src/store/slices/editor-session-host-browse-restore.test.ts` — builds full payloads, never `buildWorkspaceSessionPatch`, and opens the Host file with `preview: false`.
- `src/renderer/src/components/right-sidebar/../lib/workspace-session-editor-drafts.test.ts` — asserts only `openFilesByWorktree`/`activeFileIdByWorktree` with `unifiedTabsByWorktree: {}`.
## Required outcome
- A test that opens a Host file, builds `buildWorkspaceSessionPatch(state, changedFields)` from the live delta, and asserts `patch.unifiedTabs` and `patch.openFilesByWorktree` exclude the Host entity/file.
## Suggested approach (optional)
Mirror the host file's production open options (`preview: true`) so preview replacement is covered too.ℹ️ Nitpicks
src/preload/api/filesystem-api.tssaysresolveHostBrowseEntry"grants a local read only when the target resolves to a regular file". The grant isauthorizeExternalPath, i.e. full path authorization (the file is also writable/deletable viafs:writeFile/fs:deletePath); only the editor tab is read-only. Consider tightening the wording.- The
fileExplorer.host.contentsUnavailablecopy ("Return to the workspace root to use it") is slightly off: clicking the Contents tab actually leaves Host mode and runs the workspace search, so "return to the workspace root" could read as a manual step.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ The follow-up commit is a strict improvement and introduces no new issues. The two inline findings from the prior review (toolbar keyboard fallback into the hidden tree; Host re-activation silently re-listing the root) are not touched by this commit and remain open.
Reviewed changes (delta since d1cad27)
- Canonical ownership now decides Host opens for local/SSH:
planHostFileOpenuses main'sworkspaceRelativePathfor non-runtime sources and the literal containment check only for paired runtimes. A workspace symlink that leaves the workspace (canonical verdictnull) now stays read-only instead of opening through the writable workspace path — this closes the inconsistency called out in the prior review. - SSH Host mode requires the desktop APIs:
getHostBrowseAvailabilitynow gates thesshowner on bothbrowseHostDirandresolveHostBrowseEntry(the web client stubsssh.browseDir), while runtime browsing stays available without them.use-file-explorer-host-mode.tspasses the combined check. - UNC root Up fix:
parentOfUncPathreturns its input when the share root has no segments, soparentPath('\\server\share')/parentPath('\\?\UNC\server\share')now equal the input and Up is correctly disabled at the root instead of re-listing the same folder. - Provenance on Host-tab promotion:
applyOpenFileToStatenow captures and stores owner provenance when a read-only tab turns writable, without disturbing hydrated writable tabs that intentionally resolve their owner lazily.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…ost, revalidate on return - Render Host folders through the shared VirtualizedList and drop the 2,000-row cap: 50k-entry folders enter and filter without long tasks. - Explorer tree shortcuts stand down while the Host overlay is shown, so Delete/F2 with focus on the toolbar or body cannot act on the hidden tree's selection. - Reactivating Host mode without re-entering keeps the visited folder and revalidates it (reissuing an interrupted navigation) instead of snapping back to the workspace root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@coderabbitai Re the outside-diff provenance note: fixed in b90c711, narrowed to the read-only → writable transition. Restored tabs without provenance keep resolving their owner lazily (a blanket Docstring coverage: this repo's AGENTS.md asks for "Concise/Brief Non-obvious Comments ONLY", so functions carry short "Why" comments instead of docstrings. |
|
@greptileai review |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since b90c711)
- Host listings are virtualized:
FileExplorerHostListnow renders throughVirtualizedList(mounting only the viewport plus overscan) and drops theHOST_LIST_RENDER_LIMITcap and thefileExplorer.host.truncatedstring from all six locales; filtering runs throughuseDeferredValue. - Tree shortcuts stand down under Host mode: a new
isExplorerTreeCoveredByHostModegate at the top of theuseFileExplorerKeyshandler returns early whenever the Host overlay is in the explorer shell, so Delete/rename/arrow shortcuts can no longer act on the hidden tree selection regardless of where focus sits. This closes the prior review's important toolbar finding; the overlay div now carriesdata-file-explorer-host-overlay. - Reactivation revalidates instead of resetting:
useFileExplorerHostBrowser's activation effect keeps the last visited folder (or an interruptedpendingPath) and silently re-lists it in the background; only an explicitenter()resets to the workspace root. \\?\C:root is terminal:parentPath('\\?\C:', 'win32')now equals its input, so Up is disabled at an extended-length drive root.
All three prior Pullfrog threads are addressed by these commits.
ℹ️ Stale known-limitation in the PR description
The description still lists "The list renders at most 2,000 entries per folder; the Names filter narrows it" under Known limitations, but this commit removes that cap by virtualizing the list (and deletes the fileExplorer.host.truncated copy). Worth dropping or rewording so reviewers don't look for behavior that no longer exists.
Technical details
# Stale PR-description limitation
## Required outcome
- Remove or rewrite the "renders at most 2,000 entries per folder" Known-limitations bullet to reflect viewport-only windowing.
## Suggested approach (optional)
- Mention that very large folders now mount only the visible window.DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
The virtualized Host list reuses one scroller, so a deep scroll carried over into the next folder or filter result. Reset it when the listed path or filter changes; revalidating the same folder keeps the user's place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline src/renderer/src/store/slices/editor/actions/open-file-apply.ts
ast-grep outline src/renderer/src/store/slices/editor-read-only-tabs.test.ts
rg -n -C 12 'operationProvenance|readOnly|hostBrowse' src/renderer/src/store/slices/editor/actions/open-file-apply.ts
rg -n -A 65 -B 5 'captures owner provenance when a Host tab turns writable|keeps restored writable tabs resolving their owner lazily when reopened' src/renderer/src/store/slices/editor-read-only-tabs.test.ts
fd -H '^AGENTS\.md$' --exec rg -n -C 5 'Concise|Brief|Non-obvious|docstring|Why' {}Length of output: 14235 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '115,170p' src/renderer/src/store/slices/editor/actions/open-file-apply.ts
sed -n '263,308p' src/renderer/src/store/slices/editor-read-only-tabs.test.ts
rg -n -A 4 'Concise/Brief Non-obvious Comments ONLY' AGENTS.mdLength of output: 5034
Both named tests contain the expected assertions. I inspected the tests but did not run them. Thanks for the guidance on comments. ✏️ Learnings added
If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! You are interacting with an AI system. |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since the prior pullfrog review at 1f63af93)
- Host list scroll reset:
FileExplorerHostListgained auseLayoutEffectkeyed on[scrollElement, resolvedPath, deferredQuery]that sets the scroller'sscrollTopto 0, so entering a different folder or changing the Names filter starts at the top while a same-folder revalidation keeps the user's place. - Regression test: a new
FileExplorerHostListcase covers a new folder, a changed filter, and a same-folder revalidation. I confirmed it fails when the effect is removed (scroll stays at 500), so the assertion is not theatre.
The effect's deps are the right ones: resolvedPath is a value string, so an entry-list refresh or a silent reactivation re-list of the same folder leaves scroll untouched, and scrollElement is a stable setState from useState, so no spurious reset on ordinary renders.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing the workspace - Drop paired-server Host browsing: it could not open files outside the worktree, had no realpath, and was disabled over SSH. Host mode now reports one generic 'not available for this workspace' state. - Leaving the workspace ends Host mode; each entry starts a fresh session at the workspace root, replacing the retain-and-revalidate machinery. - Simplify the host label to local and SSH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n leave Also drop the unused source-null clear: active already requires a source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A briefly unresolved owner (reconnect, hydration) used to resume the same visit and flash its stale folder; ending the visit keeps it simple. Tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sits on host change - Register fs:browseHostDir directly on browseServerDirectory (drop a pass-through wrapper); the no-grant test now goes through the registered IPC handler. - Correct two comments that described the file grant as read-only, update a stale scroll comment, merge a doubled doc comment, drop an alias, share the desktop-API check, and make an internal constant module-private. - Scope a Host visit to its host as well as its workspace, so a workspace repointed to another SSH target never shows or opens the old host's listing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since the prior pullfrog review at 27454a3)
- Host mode trimmed to local + SSH: dropped paired-server (runtime) browsing — it could not open files outside the worktree and had no
realpath, and was already disabled over SSH.getHostBrowseSourcenow returnsHostBrowseSource | null(null for runtime/unresolved/non-desktop clients),planHostFileOpenlost itssourceargument andunsupportedvariant, and one genericfileExplorer.host.unavailablecopy replaced the three reason strings across all six locales. - Fresh session per visit:
useFileExplorerHostBrowseris now keyed by avisitKey(an entry counter) instead of source + worktree, and the retain-and-revalidate machinery plusbrowser.reset()are gone.useFileExplorerHostModeholdsvisit = { worktreeId, entry }; leaving the workspace ends Host mode and each entry starts a fresh browse at the workspace root. - End on host loss:
95990d2restores!sourceto the render-phase visit clear, so a briefly unresolved owner (reconnect/hydration) ends Host mode instead of resuming a stale folder when the host returns. A test locks both the end and the non-resume. - Tests:
e72c331locks fresh-per-entry sessions and the filter reset on leave;9d48c37types the recorded visit keys without an assertion.
I ran the four touched host-mode suites (use-file-explorer-host-mode.test.tsx, file-explorer-host-mode.test.ts, use-file-explorer-host-browser.test.tsx, FileExplorerHostList.test.tsx) — 33/33 pass. The three prior Pullfrog threads are addressed and resolved; the reactivation thread is superseded by 62c980d (author replied there).
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 27f8d2d0-d88c-4cec-9b46-5b6dc5aebe9e
📒 Files selected for processing (15)
src/renderer/src/components/right-sidebar/FileExplorerHostList.test.tsxsrc/renderer/src/components/right-sidebar/file-explorer-host-mode.test.tssrc/renderer/src/components/right-sidebar/file-explorer-host-mode.tssrc/renderer/src/components/right-sidebar/file-explorer-host-open.test.tssrc/renderer/src/components/right-sidebar/file-explorer-host-open.tssrc/renderer/src/components/right-sidebar/use-file-explorer-host-browser.test.tsxsrc/renderer/src/components/right-sidebar/use-file-explorer-host-browser.tssrc/renderer/src/components/right-sidebar/use-file-explorer-host-mode.test.tsxsrc/renderer/src/components/right-sidebar/use-file-explorer-host-mode.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/fr.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (6)
- src/renderer/src/i18n/locales/en.json
- src/renderer/src/i18n/locales/ja.json
- src/renderer/src/i18n/locales/es.json
- src/renderer/src/i18n/locales/ko.json
- src/renderer/src/i18n/locales/fr.json
- src/renderer/src/i18n/locales/zh.json
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.
- Toolbar takes a single Host availability flag instead of a mirrored state object. - Drop redundant filter reset on entry and the duplicate active-visit comparison. - Merge overlapping Host tests, keeping every assertion (with messages). - Cover a slow click resolving after the Host visit ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since the prior pullfrog review at 95990d2)
- Host mode ends when the workspace's host changes:
useFileExplorerHostModerecords ahostKey(localorssh:<connectionId>) per visit and clears the visit during render when it no longer matches the current source, so repointing a workspace at another host never leaves a listing from the old machine clickable.activeis now simplyvisit !== null; the render-phase clear owns the mismatch check, and a non-nullhostKeyalready implies a non-null source. A new test locks both the end and the non-resume when the host returns. - Browse handler simplified: the exported
browseHostDirectorywrapper is gone;registerFilesystemHostBrowseHandlerscallsbrowseServerDirectorydirectly. Its test now drives the realfs:browseHostDirIPC handler through a mockedipcMain, and additionally asserts a null-byte SSH path never reaches the relay. - "Unavailable" reshaped:
FileExplorerToolbar'sunavailableLabel: string | nullbecameavailable: booleanwith the label chosen inside the toolbar;useFileExplorerHostModeexposesavailableinstead of a pre-builttoolbarobject. - Redundant filter reset dropped:
enterno longer clears the Names filter —exitand the render-phase clear already do, and the filter can only be non-empty while Host mode is active. - Test consolidation: the read-only-tab and session-restore suites merge overlapping cases behind an
openWritablehelper, and the host-browser slow-click case becomes one table-driven test covering both "navigated away" and "visit ended". Coverage is not weakened — the assertions are exact, not truthiness checks.
I re-read the authoritative full diff and the incremental range-diff, traced the hostKey/render-clear interaction (a stale visit cannot paint because the same-component setState-during-render is re-rendered before commit), and confirmed no dangling references to the removed browseHostDirectory or the old unavailableLabel/toolbar shapes. Ran the six touched suites (use-file-explorer-host-mode, use-file-explorer-host-browser, FileExplorer.host-mode, filesystem-host-browse-handlers, editor-read-only-tabs, editor-session-host-browse-restore): 44/44 pass. Security posture is unchanged from prior reviews — browsing never widens grants, and each outside click grants at most the one regular file.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Resolve FileDocument rename conflict and replace the removed useDelayedStatus hook with a local loading delay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion inline.
Reviewed changes (delta since the prior pullfrog review at 976e0e2)
- Merged
origin/mainand adapted to the removed status hook: the branch'suse-file-explorer-host-browserno longer importsuseDelayedStatus/@/lib/delayed-status(main deleted both in5c62cb5320); the show delay is now an inlineuseState+useEffecttimer keyed on[loading, sessionKey]. - Adopted main's
MarkdownDocument→FileDocumentrename insrc/shared/filesystem-entry-types.ts(with aMarkdownDocument = FileDocumentcompat alias); no branch-authored change beyond the merge resolution. - No other branch-authored delta: the range-diff
976e0e2..0230ee9is exactly these two hunks, and nouseDelayedStatus/@/lib/delayed-statusreferences remain insrc. Ranuse-file-explorer-host-browser.test.tsx— 12/12 pass, including the fake-timer spinner case.
Technical details
# Inline spinner timer drops the minimum-visible hold
## Affected sites
- `src/renderer/src/components/right-sidebar/use-file-explorer-host-browser.ts:135` — `showLoading = loading && delayedLoadingKey === sessionKey`; the timer at :127-134 fills `delayedLoadingKey` after `HOST_LOADING_SHOW_DELAY_MS` (200ms) and clears it whenever `loading`/`sessionKey` change.
- Removed `src/renderer/src/hooks/use-delayed-status.ts` / `src/renderer/src/lib/delayed-status.ts` — the old hook also enforced `STATUS_MIN_VISIBLE_MS = 400`, holding a shown status up so it could not flash right at the show delay.
## Required outcome
- Decide whether the Host spinner should keep the 400ms minimum-visible hold: if yes, the inline timer must not hide before that floor; if the immediate hide is intended, no change is needed.
## Suggested approach (optional)
- Track a `shownAt` timestamp alongside `delayedLoadingKey` and gate the reset on `Date.now() - shownAt >= 400`, mirroring the old `show()`/`minVisibleMs` behavior, or keep a trimmed `createDelayedStatus` helper for this consumer.deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Restores the 400ms minimum-visible hold the removed useDelayedStatus hook provided, so a listing that lands just after the show delay no longer flashes the spinner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since the prior pullfrog review at 0230ee9)
- Host-mode spinner regained its minimum-visible hold:
use-file-explorer-host-browser.tsnow tracks the shown key plus a shown-at timestamp; after the 200ms show delay a spinner stays on screen for at least 400ms, so an SSH listing that lands just after the delay no longer flashes. A new fake-timer test locks the hold and fails without it — this closes the prior review's inline finding.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — minor suggestions below.
Reviewed changes (delta since the prior pullfrog review at b2bfb20; the net branch-authored change is the merge's adaptation to upstream 51fe6f3fba / #24489)
- Dropped the
authorizeExternalPathgrant from Host entry resolution: main deleted that API, soresolveHostBrowseEntryno longer calls it andfs:resolveHostBrowseEntrynow only classifies (realpath+stat). - External local Host files moved onto main's user-named access:
openHostFilestill storesrelativePath === filePath, soeditorTabFileAccessresolves{kind:'user-file'}(read in place, no root, nothing remembered) while the tab staysreadOnly+hostBrowse; SSH external files keepexternalSshTargetId. - Tests retargeted: the handler test now asserts the clicked file, its parent, and nested children are never allowlisted and
resolveAuthorizedPathrejects; the host-mode test dropped theauthorizeExternalPathmock. - Docs retargeted:
HostBrowseEntryResolutionand the preloadresolveHostBrowseEntrysignature now say "classification only; no grant". - Ratchet updated:
file-explorer-host-open.tsadded toUSER_NAMED_TAB_OPENERS(correct, since it can open a tab read as user-named). - Adopted main's
csvPreviewOnlyfield onOpenFile(CSV editor, not branch behavior).
The read path holds: useEditorPanelFileContentLoader passes editorTabFileAccess(...) whenever the tab is present in openFiles, so a freshly-opened external Host file reads as user-named even with no grant; readOnly keeps it non-editable and editorTabDocumentFolderAccess still returns undefined for read-only tabs. Net posture is narrower than the old single-file subtree grant. Ran the touched suites — 41/41 and 34/34 pass across the handler, host-open, local-file-access, read-only-tabs, host-mode, host-browser, FileExplorer overlay, session-restore, and drafts tests.
ℹ️ Nitpicks
src/renderer/src/store/slices/editor/types/open-file.ts:149— thehostBrowsecomment still explains non-persistence asrestore cannot replay its path grant, but external Host files no longer receive any path grant (they read through user-named access). Reword to the read-only session semantics.- PR description — the Mechanism / Trust model / Worst case sections still describe the removed grant ("gets the same session read/write path grant that terminal links and View Log already use"); worth updating to the classification-only model.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

ELI5
The file explorer can only show the project folder. This adds a "Host" button that lets you walk up out of the project and browse the rest of the machine, like an SFTP browser. It works for local and SSH projects. One click brings you straight back to the project.
What Changed
Before: the right-sidebar Explorer is rooted at the workspace. From
/home/allen/codesyou cannot reach/home/allenor its siblings (Documents, other projects). This hurts most on SSH hosts, where the Explorer is the easiest way to look around.After: a hard-drive toggle in the Explorer toolbar enters Host mode.
~, each path segment)./or from a UNC share root does nothing. Up from a Windows drive root (C:\) shows the drive list, as the remote folder picker already does.Project mode is unchanged.
Mechanism
inert), not an extension of the tree. The tree's cache, watchers, git decorations and search stay bound to the workspace, and Host mode adds no watchers or searches of its own.ssh:browseDir.fs:browseHostDir. It calls the same functionfiles.browseServerDiralready uses (extracted asbrowseServerDirectory) and returns names and types only. It never touches the external-path allowlist.fs:resolveHostBrowseEntryin main. It runsrealpath, thenstat, and reports whether the canonical path is inside the canonical workspace root. Ownership follows that canonical verdict, so a workspace symlink pointing outside stays read-only.readOnlyis an editor property; main does not know about it). They are not persisted: neither the file nor its tab chrome. Opening a file that is already an open View Log tab only focuses that tab.FileExplorerFiles. Toggling re-renders only the Host controls, not the tree. While the Host overlay is shown, the tree's keyboard shortcuts stand down and the covered Project search inputs are inert, so no keystroke can act on the hidden tree or run a hidden workspace search.\\server\share,\\wsl.localhost\Distro,\\?\prefixes).Why
Why not grow the tree itself. Widening the tree (for example, extending the sparse-checkout root selector upward) would pull every workspace-bound system along with it:
~or/, which on SSH compete for the relay's watch capacity;rgover a whole home directory, including.sshand token files;A separate flat pane makes those impossible by construction, and it matches how SFTP browsers behave.
Why no parent-folder grants. The obvious shortcut is to call
fs:authorizeExternalPathon each parent folder. That grant covers a whole subtree, so it would silently make~(or/) readable, writable and deletable for the rest of the session. Host mode never grants a folder.Trust model, stated plainly
fs:authorizeExternalPathon any path, andfiles.browseServerDiralready lists any folder for a paired device.src/relay/context.ts), so no client-side allowlist is added. It would only be false reassurance. (Thedocs/relay-fs-allowlist-removal.mdthat comment references is not in the repo.)Worst case if something is wrong
Switching cost. In the first version, Host state lived in
FileExplorerFiles. Every toggle re-rendered every tree row: an 86–110 ms long task in the dev build on this repository, plus the Host bar pushing the search box down twice. Moving the state into a provider and layering the Host bar inside the overlay fixed both. Dev build, this repository, 75 tree rows, three enters and exits:Two cases still shift the layout once per toggle, by design: a sparse checkout hides its scope row (sum 0.021 over six toggles), and the Contents view hides its filter row (0.058). Leaving an empty gap there looked worse than one short shift. Entering Host mode fades the listing in; leaving is instant, because the tree underneath is already rendered.
Linked Issue
No existing issue covers this; happy to open one if you prefer.
Visual Proof
Captured from the dev app against a throwaway fixture home at
/tmp/orca-demo/home/allen(codesis the workspace;datasetslinks to a folder elsewhere anda-link.tslinks back into the workspace).A file outside the workspace opens read-only:
Testing
Manual, macOS, local workspace (dev app driven through CDP, isolated profile and home, workspace added through the
/tmpalias so its path is not canonical):notes.txtis deniedDocuments, …) listed; each segment navigatesnotes.txtoutside the workspacefocus()on those inputs failsSSH was not exercised manually. The SSH paths (
ssh:browseDirlisting, providerrealpath/statclassification,externalSshTargetIdfor outside files) are covered by unit tests with a mocked provider.Automated
filesystem-host-browse-handlers.test.ts: real temp folders plus a mocked SSH provider.FileExploreroverlay, and the mode hook with a real zustand store.Guards checked by removing them: each guard below was removed once, and the named test failed.
FileExploreroverlay tests.FileExploreroverlay tests.readOnly, or persisting Host tabs or their tab chrome: open and session tests.Three of these first passed when they should not have. The first virtualization check left the list virtualized, so it now replaces the list with a plain render and the 5,000-entry test fails. The first tree-remount test never re-rendered, because
FileExploreris memoized with no props, so it now toggles through hook state. The null-byte check was only exercised on the local path, where Node rejects\0before the guard runs, so an SSH-path test was added. All three now fail when the guard is removed.Checks run:
pnpm tc(node, cli, web).oxlint.check:code-quality:changed.build:electron-vite.maincheckout, or passes when run alone (timing-sensitive under full-suite load). None of the failing files are touched by this PR.verify:localization-runtime-catalogfails the same way onmain(sparsePreset.findPath):AI Disclosure
Implemented and tested with Claude Code (Claude Opus 5.5). A second Claude session reviewed each step adversarially, and the manual checks above were driven by Claude Code through CDP.
Notes
Known limitations
authorizeExternalPathgrants a path's subtree. If a granted file is later replaced by a folder, that folder becomes accessible. This existing behavior is shared with terminal links and View Log, and is unchanged here.ls -1Ap:Follow-ups
realpath/statand file opening outside the worktree).Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (typecheck, changed-code lint and the touched test areas pass locally; full lint, full test and build left to CI)🤖 Generated with Claude Code