Skip to main content

File Browser

A read-only file tree beside your agents: browse a worktree, scratch or plain folder, read files inline, and send an @file reference straight into an agent's input.

Updated
Reviewed

What the File Browser is

The File Browser is a read-only panel with two columns: a collapsible, resizable tree on the left and a viewer on the right that shows whatever the tree points at. It arrived in v0.29 and grew through v0.31 into the panel you reach for when the question is "what is in here, and what did the agent just touch".

It is a separate panel kind from the File Viewer. The File Viewer shows one file; the File Browser is the tree you navigate, with a viewer attached. Neither of them edits anything.

The File Browser can live in the grid, dock or a dialog. Docking keeps the browser available through a chip and opens its two-column view in the dock popover. As a dialog it is pinned at 85% of the window height so an expanding tree does not resize the frame under your cursor.

With nothing selected, the File Browser answers what changed in this worktree

Opening one

There are seven ways in, and as of v0.31 every global one opens the browser as a persistent grid panel rather than a modal dialog. A second launch does not open a second browser: it focuses the one already open for the same folder.

Entry pointNotes
Option-Command-F Control-Alt-F Control-Alt-F Opens the browser for the focused worktree, or for the project or scratch folder when no worktree is selected.
The toolbar Browse files buttonShips visible on the toolbar as of v0.31. The web browser and dev preview moved into a panel tray in the same change; no saved layout was altered.
Right-click a file path in a terminal → Open in file browserOpens the tree revealed at that path. Clicking a directory link in terminal output does the same thing directly.
The File Viewer toolbar → Show in file browserThe route back to the tree the file came from. Offered only for a file inside a live worktree.
The empty-grid launcher → Browse filesThe chip row you see in a workspace with no panels open yet.
A worktree card's menu → Open → Browse filesSelects that worktree first, then opens its browser: a person who names a worktree means to go there.
A worktree card's path rowThe same thing, from the folder path in the card body rather than from the menu.

The panel launcher also carries it, matching on files, browse, explorer, tree, folder, finder and assets.

Note
A path-targeted reveal (a terminal right-click, or Show in file browser) can also open a deliberately ephemeral dialog that never counts toward the panel limit and is never restored on restart. The two behaviors share the name "Browse files" because they answer the same question; the persistent one is what every keyboard, toolbar and palette route now gives you.

The two columns

Each column can be collapsed, and each column's toggle lives in the other column's header, which is what makes "both collapsed" unreachable.

  • Toggle file viewer sits in the tree's header. Collapsing the viewer gives the tree the whole panel.
  • Toggle file tree sits in the viewer's toolbar. Collapsing the tree leaves a single reading column.

The divider between them is a drag handle: the tree column runs from 200px to 600px, defaults to 288px, and remembers its width per panel. It is keyboard-operable: focus it and use the arrow keys, with Shift for a coarser step. A remembered width is stored even while the viewer is collapsed, so reopening the viewer restores the split you had.

Tree header

The root icon · the root path · View options · Toggle file viewer. Once the tree is re-rooted the root icon becomes a Back to worktree root button and an Up one level button joins the row. At the worktree root that leaves just two buttons, which is what a sub-300px header can carry: before v0.34 the dotfile filter and Refresh each held a slot of their own.

Viewer toolbar

Toggle file tree · a Source/Rendered toggle for Markdown and HTML · the file's path (click to copy) · reveal in the system file manager · Open in editor. It also picks up the View options menu, but only while the tree column is collapsed away.

Both rows are real ARIA toolbars as of v0.34: the whole row is one tab stop, Left and Right move between its controls and wrap, and Home and End jump to the ends. Before that every button was its own tab stop, so reaching the tree past a rooted header cost five presses. The two rows are named distinctly (File tree controls and File viewer controls) because both can be on screen at once, and two toolbars with the same name are two a screen-reader user cannot tell apart.

The viewer remembers its explicit Source/Rendered choice per browser panel. Before an explicit choice, Markdown opens rendered and HTML opens as source. Rendered Markdown has the shared Text size reading control. Copy file contents copies loaded raw text, even while it is rendered; it does not copy the path or a screenshot.

The view options menu

One menu owns everything that changes what the tree shows, and it belongs to whichever column is currently rendering the tree's chrome: the tree header normally, the viewer's toolbar while the tree is collapsed. Exactly one of it exists in every layout.

That ownership is the v0.34 change. The two settings used to be split by column — the dotfile filter had a dedicated button in the tree header, sort sat in the viewer's toolbar behind a "no file open" gate — so opening any file, or collapsing the viewer, removed the only control for a setting that was still reordering the rows on screen. Sort governs the tree just as much as the filter does: every level of it is sorted.

ItemWhat it does
Sort byName, Modified, Size or Type.
OrderAscending or Descending. Its own labeled group rather than a direction that flips when you re-pick the active key: re-picking leaves the checked item checked, so a screen reader reports no change, and it gives no way to ask for a direction outright.
Show dotfilesA checkbox, with a trailing count of how many rows the filter is removing right now. The label carries the positive sense and never changes wording; the checkbox state says which way it is set.
Collapse allCloses every expanded branch. Disabled when nothing is expanded, so it cannot promise a no-op. New in v0.34: before it, the only way back from a deep tree was collapsing each branch by hand.
RefreshThe manual re-read, demoted from a dedicated header button. It is a fallback, not a primary — the tree already ticks off the worktree watcher, and a poll covers roots no watcher owns — so it exists for what slips through, notably a write into a gitignored folder, which git status never reports.

The trigger carries no badge. A number inside a fixed-width icon button reads fine at "3" and squeezes the glyph beside it at "25", and a trigger sized to its content would drag the whole button cluster sideways every time you expanded a folder. The count goes where there is room for words and a recovery: the strip under the tree.

The tree distinguishes moving through a folder from asking to see it, because those are different intentions and conflating them kept hijacking the viewer.

GestureOn a fileOn a folder
Click, or arrow onto itOpens it in the viewer columnExpands it. The viewer stays on whatever you were reading.
Enter or double-clickOpens the file as its own File Viewer panel in the gridRenders its contents as a listing in the viewer column
→NoneExpands, or steps into the first child if already expanded
←Jumps to the parent folderCollapses, or jumps to the parent
Home / EndFirst / last visible row
Shift-F10 Shift-F10 Shift-F10 or the Context Menu keyOpens the row's context menu without the mouse

Opening a file as its own panel reuses a panel already showing that file rather than piling up duplicates. If the browser was a dialog when you pressed Enter, the dialog closes: otherwise the panel you just asked for would be sitting behind it.

The tree scrolls to keep the cursor visible only when the cursor actually moves or first appears. Expanding a folder re-splices every row below it, and treating that as movement used to drag the view off the folder you had just opened; since v0.31 the tree stays put.

Two rows can be highlighted at once, and they mean different things: the cursor is where the tree is, and the open row is what the viewer column is showing. They diverge whenever you arrow past a folder, which is the whole point of the split. Neither uses the accent color. Collapsing a branch that holds the cursor rehomes the cursor onto the branch itself, so the keys that read it never go dead.

The tree is single-select, and there is no search box in it. To find a file by name, open it from somewhere that already knows the path (a terminal link, the changed-files summary, or the File Viewer's own file picker), and the tree expands to it.

A restored browser paints its last-known tree structure instantly while the live refresh runs behind it. The snapshot stores names and paths only (never contents, sizes or timestamps), which is why a just-restored row shows a placeholder dash for Size and Modified until the refresh lands.

Since v0.34 symbolic links are listed rather than silently dropped. node_modules/.bin is nothing but relative in-root links, and it used to render as an empty folder.

Listing a link is not following it, so containment governs descent, not visibility: a link is shown whatever it points at, and what changes is what the row will do.

The link resolves…What the row does
Inside the tree's rootIt behaves like the real thing. A link to a directory expands; a link to a file opens. Size and Modified report the target's stat, because that is what opening the row would give you.
Outside the rootA terminal node naming its target. No expansion, no kind, no size, no modified time — nothing about the target is read, so nothing about it is disclosed.
NowhereA broken link, named as one. A target whose own parent is a file counts as broken too: it cannot exist either, and calling that "unreadable" would hide a dangling link behind a vaguer word.
UnreadablyA cycle, a depth limit or a refused permission says unreadable. None of those establishes a location, so none of them is reported as pointing outside.

Both spellings of the root count as inside it. A root reached through a symlink (macOS /tmp and /var both are) has two absolute names, and a link written with either is still pointing at the same workspace. The decision that binds is made on the fully resolved path, so a link that is lexically inside the root but canonically outside it — link → ./sub/escape/etc where sub/escape → / — is external and stays non-descendable. Windows junctions go through the same rule rather than being special-cased: their stored target comes back in extended-length form, which is normalized before containment is decided.

A link says so in its own glyph rather than through a badge layered over the file-type icon, and the row carries a description for screen readers: "Symlink to /repo/real", "Symlink to /repo/real, outside this folder", "Broken symlink to /repo/real" or "Symlink to /repo/real, unreadable". A row can be both a link and changed, and the two descriptions are announced together. The folder listing on the right does the same, in the same words.

What the right-hand column shows

Three states, in priority order:

  • A file is selected: the file is read inline, with a Source/Rendered toggle for Markdown and HTML.
  • A folder is selected: a listing with Name, Size and Modified columns and an item count. A single click in the listing re-points it at a subfolder or opens a file; there is no double-click counterpart, because either way the listing's contents change.
  • Nothing is selected shows the Changed files summary: every uncommitted change in the worktree, with its status marker, path, and +/- line counts. Clicking one reads the file (its diff belongs to the Review Hub), and expands its ancestors in the tree so the row stays findable afterwards. Deleted files stay listed (the change set would be lying without them) but are not openable. On a clean worktree the pane reads Worktree is clean.

The idle summary is the reason the browser exists in the shape it does: you open it to answer "what did the agent just change in here", so on a dirty worktree that answer is the pane's primary content rather than a placeholder apologizing for the absence of one.

Sorting

Sort lives in the view options menu, as a Sort by key and an Order direction. One value orders both the tree and the listing, so the two columns can never disagree about the same directory. It is the single home for sort: the listing's column headers are deliberately not clickable, because a header that looks sortable in one column with no counterpart in the tree would misdescribe what the setting governs.

It does not govern Changed files, which is a churn-ordered list. The menu used to sit directly above that list while claiming to sort it, with its own checkmarks contradicting the order underneath.

Git status on rows

Tree rows carry a status marker for every uncommitted change, and a folder carries the worst status found anywhere beneath it, so a collapsed folder can say "something in here changed" without its children ever having been listed. Worst-first:

conflicted → deleted → modified → added → renamed → copied → untracked → ignored

That order is specific to this badge. A conflict blocks the branch outright, so it outranks everything; a deletion means the content is gone rather than merely edited; gitignored noise comes last. The index is built from the changed-file set alone (dozens to hundreds of entries), never from the tree's own listings, which run to tens of thousands of nodes on a real repository.

Rows also carry file-type icons, so a directory of mixed assets is scannable without reading every extension.

Sending a file to an agent

Two gestures put a file reference into an agent's input:

  • Drag a row onto an agent's terminal.
  • Press Command-I Control-I Control-I while the tree has focus and the cursor is on a row.

Nothing gates either gesture on the row being a file: a folder reference works the same way, which is often what you want when the instruction is "look at everything under here". Both append an @file token to the agent's draft, in the same cwd-relative form autocomplete already produces. The cwd used is the destination agent's, not the browser's: that is what keeps a reference into the agent's own worktree short while a cross-worktree one falls back to an absolute path. Duplicates are preserved: referencing the same file twice is something people do on purpose.

The destination is the agent you were last typing to; failing that, the sole eligible agent in the view. If neither resolves, the gesture refuses with "No agent is available for a file reference". It also stands down while two or more agents are armed for a fleet broadcast, so one reference cannot fan out to a whole fleet by accident.

The same command is in the row menu as Insert file reference. If it is disabled, its explanation distinguishes no agent, multiple possible agents, and an armed fleet; focus the intended agent or disarm the fleet, then return to the file.

Neither gesture moves focus. You are mid-flow in the tree and likely to reference several files; a locator pill names the destination instead.

Note
Command-I Control-I Control-I is a deliberately unregistered, tree-local gesture, not a global binding: you will not find it in Keyboard Shortcuts. The command is only meaningful while a particular tree owns focus and knows its cursor row, and the global registry dispatches before any tree could supply one. Auto-repeat is ignored, so holding the combo appends one token rather than hundreds.

Hidden files: two layers

Two independent rules decide whether a row appears, and they are not the same rule twice.

Always-hidden patternsThe dotfile filter
ScopeApp-global: every file browser panelPer panel
WhereSettings → Worktree → Always-hidden filesThe view options menu's Show dotfiles checkbox
Default.DS_Store, Thumbs.db, desktop.ini, ._*, .gitDotfiles are visible
MatchingBasename only, with * as a wildcard. Slashes are rejected: this is a junk list, not an ignore file.Any entry whose name starts with a dot
RecoveryEdit the list in SettingsOne click, from the strip under the tree

The always-hidden list is checked first and unconditionally: a name it matches stays hidden whatever the dotfile toggle says. That is why .git lives there rather than relying on the dotfile rule: turning dotfiles on to see .env should not also unfold a repository's object store into the tree. The list is editable, resettable, and capped at 100 patterns of 200 characters each; clearing it entirely is a valid "hide nothing" choice.

When the dotfile filter empties a folder, the tree says so rather than claiming the folder is empty: "Dotfiles are hidden here", with a Show dotfiles action. That action is offered only when unhiding would actually reveal something; a genuinely empty folder gets the plain empty state. A selection the filter has just hidden gets a reveal strip below the tree instead of silently vanishing.

The hidden-dotfiles strip

Whenever the filter is removing rows from the branches on screen, a strip under the tree says so in words — "3 dotfiles hidden" — with a Show button that turns the filter off. New in v0.34, and it is the answer to the hidden-file trap: leave a filter on, come back later, and rows that are merely absent read as files someone deleted. A lit icon never said that; it only said a setting was on.

The strip appears only while it has hidden files to explain, leaving the reading area available at other times. It also sits below the tree rather than above it: mounting and unmounting above the rows would shift them mid-gesture, landing the second click of a double-click one row off.

It counts only what the toggle can reveal. Entries the always-hidden list removes are excluded deliberately: .git is on that list in every git repository, so including them would leave the strip on screen forever, offering Show as a recovery for rows that gesture cannot reveal. The count in the view options menu follows the same rule.

The count is bounded to loaded, expanded branches. A collapsed folder's contents have usually never been fetched, so a deeper total would be a guess that changes as listings arrive — a number climbing on its own while you do nothing is worse than no number.

Re-rooting the tree

A monorepo's worktree root is rarely where you are working. Any folder row's menu offers Set as root, which reframes the tree at that folder. From there the header gives you Up one level and Back to worktree root.

While the tree is re-rooted, the header's root path becomes a button: click it to copy the absolute path, the same thing a row's Copy path does. Its tooltip carries the untruncated path.

Beyond git worktrees

Since v0.30 the browser opens scratch workspaces and project folders with no repository, not only git worktrees, the same "a git repo is no longer required" shift that runs through projects and worktrees. When no worktree is selected, the browser resolves the workspace folder behind the view and roots itself there.

Note
A workspace-rooted browser gets its live signal a different way. Daintree's filesystem watcher is keyed by worktree id, and a scratch or repo-less project has no entry there, so those roots are polled instead: the tree root, the open file and the expanded folders are fingerprinted every two seconds, and only while the view is visible, so a backgrounded project polls nothing. Worktree-rooted browsers ride the watcher; see below.

Some worktree-scoped items are absent for a workspace root for the same reason: Copy context is worktree-scoped, so it does not appear on rows in a workspace-rooted tree rather than sitting there dead.

Staying current

A worktree-rooted browser refreshes on the worktree's own change signal, which is already coalesced upstream by an adaptive burst debounce: an npm install or a generated asset batch arrives as one tick rather than hundreds, so the tree needs no debounce of its own. File browsers also re-read when you return to a project view that was sitting cached while you worked elsewhere.

If a refresh fails while a tree is already on screen, the last-known files stay usable under an error banner with a Retry ("Couldn't refresh this worktree. Showing the last known files.") rather than the tree being replaced by an error.

The row menu

Right-click any row. Since v0.32 the same file menu appears on all four surfaces that list files (the worktree card, this browser, the Review Hub and the diff sidebar) instead of the browser's rows inheriting a menu aimed at the whole worktree.

ItemApplies to
Show contents · Set as rootFolders
Open diffFiles with local changes
Open file · Open in editorFiles that still exist on disk
Insert file reference (Command-I Control-I Control-I )Any row. Disabled with a reason when no agent resolves, the destination is ambiguous, or fleet arming prevents a single destination.
Copy > ContextAny row in a worktree (see CopyTree)
Copy > Path · Relative path · File nameAny row
Reveal in Finder / Show in Explorer / Show in folderAny row

Plugins reach file rows through the Extensions submenu, with the clicked file's path passed as the item's argument, so a plugin item is contributed once and appears on every file surface, this browser included.

M is a tracked file with edits, ? an untracked one; the counts are the working-tree diff