Before submitting
Area
apps/server
Problem or use case
Typing a path in the add-project autocomplete (e.g. ~/) hides two kinds of valid directory targets:
- Directory symlinks — common on every platform (e.g. ~/Dropbox -> ~/Library/CloudStorage/Dropbox created by Dropbox, stow-managed dotfiles,
/opt/homebrew shortcuts).
- macOS Finder aliases — binary bookmark files created via Make Alias or Option+Cmd drag.
Root cause in apps/server/src/workspace/Layers/WorkspaceEntries.ts → browse(): it filters dirents on dirent.isDirectory(), but readdir uses lstat
semantics so symlinks are excluded, and Finder aliases are plain files that no fs call can follow.
Proposed solution
- In browse(), also consider isSymbolicLink() dirents and keep those whose stat() target is a directory.
- On macOS, also consider isFile() dirents, detect Finder aliases by their book… magic bytes, and batch-resolve via a single osascript call
using Finder's original item of ….
- Add two optional flags to FilesystemBrowseEntry: isSymlink (icon hint, set for both) and isAlias (navigation hint, set when fullPath is a
resolved target).
- In the command palette, show a distinct icon (e.g. lucide FolderSymlink) for isSymlink entries. For regular dirs and symlinks, keep the
existing "append clicked name" navigation so ~/ style paths are preserved. For aliases, jump directly to the resolved fullPath — appending the
alias name would point at a non-directory and readdir would fail.
Scope stays inside the interactive browse() path. The recursive workspace indexer in the same file is left alone to avoid cycles.
Why this matters
Common developer shortcuts are silently invisible, forcing users to paste resolved paths or fall back to the native picker — which defeats the
typed-path autocomplete's purpose. Electron's native folder dialog already handles both cases transparently, so the current gap is an
inconsistency, not a design choice.
Smallest useful scope
Just the symlink fix: detect symlinks, stat() them, include if the target is a directory. Single-function change, no contract change. Covers the
Dropbox and dotfiles cases. Finder alias support + contract additions + icon + alias navigation can ship as a follow-up.
Alternatives considered
- Return resolved real paths for symlinks — rejected; the user typed ~/Dropbox, they should pick ~/Dropbox. Alias asymmetry is unavoidable since
the alias file itself isn't a directory.
- Parse the bookmark binary format in JS — rejected; format varies across macOS versions, osascript is simpler and battle-tested.
- Add a native module for xattrs/bookmarks — rejected; not worth the dependency for one UI affordance.
Risks or tradeoffs
- One extra stat() per symlink candidate — negligible, interactive only.
- One osascript subprocess per browse call with alias candidates on macOS — ~50–200 ms, short timeout so a hung Finder can't block the UI; on
failure entries drop silently.
- macOS may prompt once for Finder automation permission the first time an alias is resolved.
- Symlinks keep the typed path, aliases jump to the resolved path — the distinct icon mitigates the asymmetry.
Examples or references
- Repro (symlinks): on a default macOS + Dropbox install, ls -la ~/Dropbox shows … Dropbox -> /Users//Library/CloudStorage/Dropbox. Type ~/
in the add-project picker — Dropbox is missing.
- Repro (aliases): create a Finder alias (Cmd+L) somewhere under ~/. file reports MacOS Alias file, xxd shows book…mark… magic. Type ~/ in the
picker — the alias is missing.
Contribution
Before submitting
Area
apps/server
Problem or use case
Typing a path in the add-project autocomplete (e.g. ~/) hides two kinds of valid directory targets:
/opt/homebrew shortcuts).
Root cause in apps/server/src/workspace/Layers/WorkspaceEntries.ts → browse(): it filters dirents on dirent.isDirectory(), but readdir uses lstat
semantics so symlinks are excluded, and Finder aliases are plain files that no fs call can follow.
Proposed solution
using Finder's original item of ….
resolved target).
existing "append clicked name" navigation so ~/ style paths are preserved. For aliases, jump directly to the resolved fullPath — appending the
alias name would point at a non-directory and readdir would fail.
Scope stays inside the interactive browse() path. The recursive workspace indexer in the same file is left alone to avoid cycles.
Why this matters
Common developer shortcuts are silently invisible, forcing users to paste resolved paths or fall back to the native picker — which defeats the
typed-path autocomplete's purpose. Electron's native folder dialog already handles both cases transparently, so the current gap is an
inconsistency, not a design choice.
Smallest useful scope
Just the symlink fix: detect symlinks, stat() them, include if the target is a directory. Single-function change, no contract change. Covers the
Dropbox and dotfiles cases. Finder alias support + contract additions + icon + alias navigation can ship as a follow-up.
Alternatives considered
the alias file itself isn't a directory.
Risks or tradeoffs
failure entries drop silently.
Examples or references
in the add-project picker — Dropbox is missing.
picker — the alias is missing.
Contribution