feat(files): add "This computer" local file browser - #473
Draft
roxi3906 wants to merge 16 commits into
Draft
Conversation
Add a local-filesystem sibling to the remote SSH browser, opening local files as preview-workbench tabs through the shared preview pipeline. - shared/local-fs: pure types + validation, sensitive-path detection, dir sorting, bookmark key - main/local-fs: LocalFsService (list/preview/roots/reveal/openPath) over node:fs, entry-capped, realpath-canonicalized; reused by the managed preview resolver so local images/PDFs stream through validated paths - bridge: window.api.localFs (5 methods); 'local' added to PreviewFileSource / ManagedPreviewSource; regenerated web api map - UI: LocalFileBrowser (back/up/refresh, editable address bar, Go-to bookmarks, sensitive-file/dir confirm), LocalFileHeaderActions (reveal / copy path / open-with), wired into the Files-panel Artifacts dropdown Sensitive-path warning covers credential dirs (.ssh/.aws/.gnupg) on entry plus suffix-less secret files (SSH keys, cloud credentials). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SENSITIVE_BASENAMES: remove '.env' — startsWith('.env') already covers it
- buildMachineName: drop the owner='' reset in catch (already initialized)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render the local file browser as a stable preview-workbench tab instead of a modal, so it lives alongside file and notebook previews. - add a 'local-browser' toolKind + createLocalBrowserPreviewItem factory - refactor LocalFileBrowser from a Dialog to full-height tab content; load roots/bookmarks on mount, drop the open/onClose props and close button - relabel the dropdown section "LOCAL" -> "this computer" and show the resolved device name (falls back to "This computer") - make the address bar an always-on input (no click-to-edit, no folder icon) Also fixes a stale ProjectFilesView test that asserted the old pre-feature behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The local browser is no longer its own preview tab. The Files tab now
holds two containers and the source dropdown swaps between them: picking
an artifact scope shows the artifacts list, picking the device shows the
local file browser. Reverts the 'local-browser' toolKind and preview-item
factory added in the previous commit.
- ProjectFilesView owns a sourceMode ('artifacts' | 'local'); the trigger
swaps its icon/label to the device and the entry gets a radio check
- the header file count follows the visible container, via an entry-count
callback from the browser
- drop the browser's machine chip (the source picker carries that name)
and give it the same rounded bordered container as the artifact cards
- hover rows/menu entries use the light-gray bg-200 surface instead of
the gold accent token
- the address bar only re-lists when the path actually resolves somewhere
new, so blurring an untouched field costs no IPC call; equivalent
spellings snap back to the canonical path
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The local browser toolbar had two arrows side by side — back and up — which
read as a duplicate of the same action. Keep one arrow that goes to the parent
folder and drop the history stack it was the only consumer of, so navigation is
a single axis (parent arrow, address bar, Go-to, entry click).
Also:
- the address bar sat on bg-muted, which is darker than the bg-200 used for
row hover; move it to bg-200 so the field reads as a lighter inset while
keeping its border against the white container
- every toolbar control is icon-only or icon + a two-word label, so add
delayed (200ms) tooltips saying what each one does, including one on the
pinned-folder ✕ clarifying that it unpins rather than deletes
- the preview header's "…" button gains the same treatment, and Reveal's
tooltip now describes the action instead of repeating its aria-label
- title-case the "This computer" source label to match "Artifacts"
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Toolbar and Go-to refinements on the local browser, from a pass over the
rendered UI:
- the address bar moves to bg-000; bg-200 is what the listing rows use for
hover, so the field only reads as an input once it is lighter than that.
Its border still separates it from the container
- "Go to" drops its leading MapPin: the two-word label already says what the
control is, and the trailing chevron carries the dropdown affordance
- the parent arrow, refresh and pin icons thin out to strokeWidth 1.5, which
sits better next to the 12px address text than lucide's default 2
- bookmarks switch from Bookmark to a pushpin: Pin when unpinned, PinOff once
pinned, so the icon shows what a click does next. The Go-to menu follows,
including its per-row unpin control, which was an ✕
- Go-to rows now show the absolute path under the label. "Home" said nothing
about where it lands, and two folders pinned from different trees were
indistinguishable when their basenames matched
- the per-row unpin button gets a real 24px hover square filled with
surface-control-hover; at bg-300 it was 2% off the row's own hover fill and
effectively invisible
- title-case the Remote source label to match Artifacts / This computer
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Go-to list was a hand-rolled absolutely-positioned div behind a gotoOpen boolean, with no dismiss path: clicking anywhere else left it open, and its role="listbox" wrapping plain buttons was not a valid menu tree. Move it onto the shadcn DropdownMenu, so click-outside, Escape, focus handling and the trigger's aria-expanded all come from Radix, and the rows become real menuitems. The per-row unpin button now stops propagation so clicking it doesn't select (navigate to) the row it sits in; the menu stays open, which lets several folders be unpinned in one pass. Covered by a new test that opens the menu and dismisses it with an outside pointerdown/click. Also tighten the browser's bottom gap from mb-4 to mb-1.5 so the container sits closer to the panel edge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A mistyped path rendered the raw IPC rejection in destructive red — "Error invoking remote method 'local-fs:list-dir': Error: ENOENT: no such file or directory, realpath '/x'". Typing a path that isn't there is ordinary, not a fault, so map the errno to a sentence and show it in body text: a short summary line plus the offending path in mono beneath it. describeLocalListingError is pure and covers ENOENT / ENOTDIR / EACCES / EPERM / ELOOP / ENAMETOOLONG and both validation rejections, keeping unrecognized text rather than inventing a reason. Icons all go neutral: the sky-500 folder, the primary-tinted pins and the emerald copy checkmark carried color that no longer meant anything once the glyphs and labels said the same thing. The three toolbar icon buttons drop to text-300, darkening only on hover, and their stroke thins to 1.25 via one shared pair of constants. "Go to" moves up to text-sm: it is the only worded control in that row and was rendering as the smallest type in it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Go to" moves to text-000 so the row's only worded control is its most legible element. The address bar goes the other way, to text-100: the current path is context rather than content, and it darkens to text-000 on focus, when it is about to be edited. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…clid-35cb9b # Conflicts: # src/main/ipc.ts # src/renderer/src/pages/workspace/PreviewFileSurface.tsx # src/renderer/src/pages/workspace/ProjectFilesView.tsx # src/renderer/src/pages/workspace/previews/preview-file-reader.ts # src/renderer/src/stores/preview-workbench-store.ts # src/shared/preview-resources.ts
…clid-35cb9b # Conflicts: # src/renderer/src/pages/workspace/ProjectFilesView.test.tsx # src/renderer/src/pages/workspace/ProjectFilesView.tsx
Keep tool-type preview panels mounted (hidden) when inactive so ProjectFilesView and LocalFileBrowser do not unmount when the user opens a file preview. On return to the Files tab the component is already mounted: cwd, sourceMode, and scroll position are intact. File panels are unaffected — they continue to re-create on each activation via a key encoding path + mtime. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
After stageLocalPath copied the file into .pending/, the handler
returned without writing any SQL rows. listFiles({ collection:
'uploads' }) queries ManagedFile with source='upload', so the file
never appeared in the Your uploads panel.
Fix: after runLocalStaging succeeds, call
finalizePendingSessionUploads(STANDALONE_UPLOAD_SESSION_ID,
[attachment]) which runs publishAttachment → completeStagingUpload,
writing uploadFile + uploadVersion + ManagedFile in one transaction.
A new onStandaloneUploadSaved callback lets ipc.ts broadcast
project-files:changed so the renderer refreshes immediately.
Also add STANDALONE_UPLOAD_SESSION_ID = 'standalone-uploads' constant
in shared/uploads.ts for the sessionless publish path.
Tests: 103 passing (all uploads + project-files + ipc suites)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Show the full path with a "This computer" pill under the filename in the local preview header. - Replace the standalone Reveal in Finder button with a reload action that remounts the preview so the file is re-read from disk. - Regroup the "..." menu per the local-file design: a file identity header, Copy path, and an "On this machine" group holding Download and Save as artifact. - Let local files download through the existing file:save-managed pipeline by accepting the 'local' source. - Keep header tooltips above the full-screen preview modal (z-[70]) and stop the More actions tooltip from reappearing after the menu closes: it shows once per hover and stays suppressed until both pointer and focus have left the trigger. - After Save as artifact succeeds, show an in-memory Saved chip at the start of the action cluster and hide the menu item. Tests: 70 passing (file-save, PreviewFileSurface, PreviewPanel, FilePreviewDialog lifecycle suites)
Save-as-artifact uploads were stored under 'default-project' regardless of which project the workspace had open. useProjectFilesIndex filters project- files:changed events by projectId (line 392), so the broadcast was silently dropped and no reload triggered. listFiles also queries by projectId, so even a manual refresh found nothing. Fix: add optional projectId to StageLocalPathUploadRequest; LocalFileHeaderActions reads activeProjectId from the navigation store and passes it; the IPC handler uses request.projectId ?? DEFAULT_UPLOAD_PROJECT_NAME so fallback behaviour is preserved. Broadcast and SQL write now use the same resolved projectId. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tooltip is no longer wanted on the local preview header's More actions button, so the button keeps only its aria-label. Removing it also drops the entire suppression machinery that kept the tooltip from reappearing after the menu closed (menu open state, suppressed/open tooltip state, and the pointer/focus handlers on the trigger). Tests: 13 passing (PreviewFileSurface suite)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a local-filesystem file browser ("This computer") as a sibling to the existing remote SSH browser. Local files open as standard preview-workbench tabs and render through the shared preview pipeline. Entry point is a new LOCAL section in the Files-panel Artifacts dropdown.
What's included
src/shared/local-fs.ts+ tests): pure types,validateLocalPath,resolveLocalPath,sortLocalEntries,isSensitiveLocalPath, and the reservedLOCAL_BOOKMARKS_KEY.src/main/local-fs/):LocalFsService(list / preview / roots / reveal / openPath) backed bynode:fs, entry-capped and realpath-canonicalized. The same instance also feeds the managed-preview resolver, so local images/PDFs stream through the same validated path.window.api.localFs(5 methods);'local'added toPreviewFileSource/ManagedPreviewSource; regenerated web api map.LocalFileBrowser(back/up/refresh, editable address bar, Go-to with fixed Home + pin/unpin bookmarks, sensitive-file/dir confirm) andLocalFileHeaderActions(Reveal in Finder + Copy path / Open-with), replacing "Download" in the header and fallback surfaces for local files.Security model
Deliberately "Home start, full-disk navigable" — paths are not confined to a storage root; validation rejects only malformed input (non-absolute / control chars) and
realpathcanonicalizes symlinks/...The arbitrary-path read via
source: 'local'is reachable only by the trusted main renderer: theopen-science-preview://protocol resolves opaque, owner-scoped resource IDs (never renderer-supplied paths), the HTML preview sandbox runs underdefault-src 'none'; connect-src 'none'with no preload, and local Office files skip the OOPIF pipeline.acquire/readRange/releaseare isolated perownerId(webContents).Sensitive-path warning covers credential directories (
.ssh/.aws/.gnupg) on entry, dotenv files, and suffix-less secret files (SSH private keys, cloudcredentials,.pgpass,.p12/.pfx).Testing
tsc --noEmiton both node and web projects — clean.local-fs+ mainlocal-fs/servicesuites pass (20 tests, incl. new suffix-less-secret cases); previously verified managed-preview / office-preview suites unaffected.LocalFileBrowser,LocalFileHeaderActions) have no unit tests — verified via typecheck/lint only.Deferred / not included
docs/local-artifacts-mockup.html.🤖 Generated with Claude Code