Skip to main content

The Project Switcher

How Daintree ranks and bands your workspaces, what each row's status line means, and what happens to memory when you switch away.

Updated
Reviewed

Opening the switcher

The switcher is the one surface that answers "which workspace". Click the project button in the toolbar for the anchored dropdown, or press Option-Command-P Control-Alt-P Control-Alt-P for the wider modal.

Both forms make every registered workspace searchable; folded browse bands can hide their rows until expanded. The search box is labelled Search workspaces… and the list is a single listbox named Workspaces, because what you are searching now includes scratch workspaces alongside projects.

The toolbar button itself carries a badge dot when something outside the current project needs you: amber, unpulsed, when background projects are waiting for input (labelled by project count, because the question is "how many places need me?") and a pulsing accent dot when background agents are simply working, labelled by agent count.

The switcher's bands, in fixed order, with each row's one status line

The seven bands

Browsing the switcher (that is, with the search box empty) groups workspaces into seven bands in a fixed order. The order is the point: actionable work first, then explicit intent, then non-actionable system activity, then habit.

BandWhat lands hereOrder within it
Current projectThe project this window is showing.One row.
Needs attentionAgents blocked, waiting on input, or finished and not yet reviewed.Severity tier (blocked, then waiting, then ready-for-review) and oldest first inside each.
PinnedProjects you pinned.Alphabetical.
RunningLive agents, with nothing asking for you.Most agents first, then the freshest transition into working.
SnoozedAgents you deliberately quieted, and nothing else running.Frecency.
Other projectsEverything else, the residual catch-all.Whatever you picked; see Sorting.
UnavailableProjects whose directory is no longer at the stored path.Alphabetical.

Two of those placements are deliberate and worth stating outright. Pinned sits above Running: an explicit pin is a stronger signal than the operational fact that something is executing, so a pinned project with a running agent stays in Pinned: its status line still says "Agent running". And Running requires live agents; a project holding only leftover processes falls through to Pinned or Other, where its status line says "Process running".

Snoozed is its own band rather than a line inside Other, because the two mean opposite things. Other is the residual nothing-is-happening set; a snoozed project has live agents you chose to quiet. Sorting them together is what made a project with three snoozed agents read as dormant.

Empty bands are skipped. Browse headers with a chevron can fold their rows, and that preference is remembered. A folded header shows how many workspaces it contains. Searching ignores those folds and produces one ranked list, so a collapsed band cannot hide a matching project.

Note

The bands are a view over one flat array: the same array the arrow keys walk. There is never a second, narrower list underneath, which is what keeps the keyboard cursor and the row Enter commits to from drifting apart.

What a row says

Each row shows a leading mark, the project's emoji, its name, and one status line. That line answers two questions and keeps them apart: what this workspace wants from you, and whether anything in it is still executing. Answering both with one status was the old failure — a project with one agent still working and one waiting on you picked the wait out of a priority order and read like a project where everything had stopped.

The line is assembled left to right out of whichever of these the row has earned, separated by ·:

FragmentWhat it reads
Running count1 agent running / 4 agents running, or Assistant working where the machine's own session is the only thing executing. Absent when nothing is running, and the absence is half the signal: a demand with no count in front of it is a workspace that has fully stopped on you.
DemandWhat the workspace is asking for, in that demand's own tone. The table below lists them.
Ageoldest 10m, waiting 3m, just finished 3m ago, until 3:45 PM. Split out of the sentence and drawn in the quietest ink on the row, because it is the one fragment asking for nothing.
Path hintA fragment of the path, only where this project's folder name collides with another registered project's. A plain basename is never shown, because that is already the name beside it.

Every state phrase leads with its number, so it can be scanned against the running count in front of it without reading a sentence:

StateWhat the demand reads
Directory goneDirectory not found
Agents waiting on you1 needs input / 3 need input, plus · 1 blocked when some of them are, and the oldest wait age
Agents blocked on an error2 blocked
Finished, not yet seen1 ready for review · just finished 3m ago, or 3 ready for review · 4m–2h ago
The Assistant itselfAssistant blocked, Assistant waiting or Assistant working, only where it is not just repeating what the agents already say
Agents snoozed1 snoozed · until 3:40 PM, stated once, never counting down
Finished and acknowledged2 finished · latest 20m ago, muted
Bare processes only1 process running / 3 processes running
Auto-closed to reclaim memorySuspended to free memory

Working is deliberately not in that list. It is the second axis, not a state that has to win a contest to be reported, so the running count survives a wait, a snooze and a completion all outranking it.

A project with nothing running and nothing to say has no status line in the switcher at all. The underlying "Opened 13h ago" fallback exists, but the switcher declines it: on twenty rows it is twenty timestamps nobody reads, and in a frecency-ordered band a bare time-ago is easily misread as the sort key.

The mark in front of the row

The mark carries only the two facts the line elaborates, and it carries them in color: green and filled when something is executing, amber when something has stopped and is waiting on you, red when something is blocked on a failure. When both are true at once it is a green-and-amber pie, split in the proportion of the two counts — three runs against one wait draws three-quarters green — with a floor wide enough that one agent among fifty still reads as a wedge rather than a rendering artifact. A hollow ring means settled: finished and seen, or dashed for snoozed. A dormant project draws no mark at all, since its presence in the list already says it is open.

Demand takes the mark first; liveness only claims it when nothing is asking. So a project quietly running four agents marks itself green rather than leaving the slot empty, and a hollow ring never lands on a row with something in flight.

The palette header carries the same reading for the whole fleet — 3 running · 1 assistant working — and disappears when nothing is executing anywhere, so its absence is as readable as its content.

Note

Blocked agents are a subset of waiting agents, so a project with three waiting and one blocked reports both: collapsing it to "1 blocked" would hide two agents that are still asking you for something.

Sorting the Other projects band

The Other projects header is itself the sort control. It offers three modes:

ModeOrders by
Most used (default)Frecency, then last opened, then name.
RecentLast opened, then name.
A to ZName.

The visible trigger only appears once the band holds four or more rows: below that there is too little order for the control to be worth advertising. The preference still applies, and right-clicking the header reaches the same radio group at any size. The Welcome Screen's project list honors the same setting, so the two surfaces never disagree about order.

The setting is scoped to this band alone. Needs attention, Pinned, Running and Unavailable each encode a decision of their own: asking for A-to-Z in the residual band is not asking for your blocked agents to be alphabetized.

The Other projects header is the sort control; right-click reaches it below the four-row threshold

How frecency works

"Most used" ranks on a frecency score: one exponentially decayed number per project that folds frequency and recency together.

  • The half-life is four days. A score is decayed from the moment it was last written, every time it is read, never compared as a stored snapshot, because that would be comparing numbers frozen on different dates.
  • A counted open adds 1.0.
  • A project you have registered but never opened starts at 0.5: below one deliberate open, above the long-dead numerical tail. Registering it is itself evidence you care, and that prior decays away like any other access.
  • Increments are debounced to one per project per five minutes. Bouncing rapidly between two projects is navigation style, not engagement; counting every hop would let the pair you are toggling bury everything else. The "last opened" timestamp still updates on every switch.

There is no new-project boost beyond that 0.5 prior, and frecency does not order the switcher as a whole, only the Other band in Most used mode, plus tie-breaks in search.

Searching

Typing collapses the bands into one ranked list that mixes projects and scratch workspaces. Projects are scored on both name and path; scratch workspaces are scored on name only, since their paths are UUIDs under Daintree's own directory and a fragment of one disambiguates nothing.

Scoring rewards exact substrings, word-boundary hits and consecutive runs, and penalizes gaps, so a match has to be anchored to the words it came from and stay near them. Ranking then reads, in order: how well the name answered the query (exact, then prefix, then substring, then a scattered match, then a path-only one), what the workspace is asking of you (blocked, then waiting, then ready for review, then merely working, then nothing), how many agents are asking, the raw text score, projects ahead of scratches, and finally frecency for a project and last-opened for a scratch.

Live agent activity is a tie-break inside a name tier and can never climb out of one: two workspaces sharing a name prefix stop being ordered by frecency alone, but a worse-matching name never overtakes a better one because something started running in it. The activity reading is also frozen for the life of the palette session. Counts arrive continuously over IPC, and ranking against the live numbers would move a row out from under the pointer between deciding to click and clicking — and re-aim a pending ↵.

One mistyped character

A workspace whose name is within a single edit of what you typed is still offered a seat. The recovery is narrow on purpose:

  • It matches on the name only, never the path.
  • The match has to start at a word boundary, so a typo of "ebsite" is not recovered from inside "Website".
  • Exactly one edit, never two, and it has to land inside a word of at least four characters. There is nothing to recover in a two-letter token, and no way to tell a typo of it from a different word.
  • The query itself has to be at least four characters.

Recovered rows are seated strictly below every clean match, and the candidates are computed on every keystroke rather than only once the clean results come back empty. Both rules exist for the same reason: a fallback that switches on emptiness reorders the rows under your pointer at the keystroke that empties the list. Appending underneath leaves the strict results' contents and positions untouched, so the only thing typo tolerance can do to a query that already worked is add rows beneath it.

Scratch workspaces in the switcher

While browsing, scratch workspaces live in their own collapsible Scratch section below the project list, with a count badge on the header. Empty, it reads "No scratch workspaces yet. Create one for a quick one-off task."

Scratch rows carry the same agent-activity status line project rows get (the same sentences and the same tones) minus the states a scratch has no concept of: there is no missing directory, no auto-parking, and no path hint. In search results a scratch row is tagged with a muted · Scratch after its name.

Right-clicking a scratch row offers Rename scratch, Save as project… and Delete scratch…; right-clicking the section header offers Delete all scratch workspaces, which is also a visible button. Full details are on Scratch Workspaces.

The row context menu

Right-click any project row:

ActionWhat it does
Open in new windowOpens the project in its own window. Hidden for the current project and for a missing one.
Pin project / Unpin projectMoves the project into (or out of) the Pinned band.
Copy pathCopies the project's directory path.
Move or rename project…Opens the relocation dialog. See Moving or renaming a project.
Stop all agentsTerminates every running session in the project, behind a confirmation. Only shown when something is running.
Free memoryReleases the project's renderer and terminals without removing it. See below.
Close projectOn the current project: closes it and returns the window to the Welcome Screen.
Remove projectOn any other project: drops it from the list. Files on disk are untouched.
Locate moved projectOnly on a missing project. Opens the reattach flow.

Keyboard

Inside the palette, arrow keys walk the list and the footer hints react to the modifiers you hold: ↵ Switch becomes Command-Enter Control-Enter Control-Enter New window while the meta key is down. A highlighted scratch row drops the new-window hint, because a scratch has no second window to open. The footer also carries All agents, which opens Pilot, and Right-click for more.

Command-Backspace Control-Backspace Control-Backspace on a highlighted project row opens the same remove confirmation the context menu does.

Four global shortcuts touch workspaces and their agents directly:

ActionShortcut
Open project switcher Option-Command-P Control-Alt-P Control-Alt-P
Switch to last workspace Option-Command-= Control-Alt-= Control-Alt-=
View all agents (Pilot) Option-Command-O Control-Alt-O Control-Alt-O
View this project's agents (Pilot, by worktree) Option-Command-I Control-Alt-I Control-Alt-I

Switch to last workspace goes to the workspace this window was in before the current one, with no palette and no overlay. It is a workspace rather than a project on purpose: a scratch workspace is a valid destination, and returning to one used to be unreachable. Running it again returns you to where you started, which gives you Alt-Tab-style toggling between two workspaces. All four bindings are remappable in Settings > Keyboard Shortcuts.

Freeing memory

A background project is not free. Its terminals keep running and its renderer stays resident, which is exactly what makes switching back instant, and exactly what costs RAM. Two mechanisms give that memory back.

Free memory

Free memory, on a background project's context menu, reclaims all three resident tiers at once: it gracefully stops the PTYs in a way that preserves their sessions, destroys the cached renderer, and shuts down the workspace-host process. The project stays in your list, marked closed, and reopening it is non-destructive: the panel layout survives.

It refuses to run on a project that is on screen in any window, not just the focused one: "Cannot free memory for a project that's open in a window. Switch that window to another project first." Running it on an already-closed project is a no-op rather than an error.

Auto-close idle projects

Daintree can also do this on its own. Settings > General > Auto-close idle projects sweeps background projects that have been idle past a threshold and hold zero terminals, closing them the same way Free memory does. Their switcher rows then read Suspended to free memory, and reopening restores them.

BehaviorValue
DefaultOff: it is a structural lifecycle change, so it is opt-in.
Default threshold15 minutes (presets: 15m, 30m, 1h, 2h; range 15 minutes to 24 hours)
Sweep intervalEvery 5 minutes, after a 2-minute quiet period at startup
Never touchedAny project active in any window, any project with a live terminal, and any project running the Daintree Assistant

The terminal gate is absolute: one live terminal means the project is skipped, because agent scrollback is not something to destroy silently.

Warm and cold switching

Each background project is backed by a live Chromium renderer held in memory. Switching to a project that still has one is a warm switch: the cached view is re-attached and repainted, without booting a new renderer. Switching to one that doesn't is a cold switch: a renderer has to load and rebuild the project UI.

Rather than freeze on the outgoing project for that whole boot, a cold switch reveals the incoming view as soon as its themed first-paint skeleton is in the DOM before React mounts. The skeleton is an opaque themed cover over the view's own themed background, which gives the incoming project a themed loading surface while it boots. Actual latency and memory use depend on the project and machine.

Background projects also stop doing work you can't see: git-status polling, fetches and PR checks are all paused while a project is hidden, and hidden cached views drop out of full-rate terminal work.

Cached project views

The cap on how many project views stay warm lives at Settings > Terminal > Performance > Cached project views, as a radio group from 1 to 5. Changes apply immediately; eviction happens on the next switch if you dropped the cap below what is currently cached.

The cap counts the active view, so N means N − 1 warm background views. On first launch the default scales with total system memory, because every extra warm view converts a cold switch into an instant one and capable machines can afford the rotation:

Total system RAMDefault cached views
64 GiB or more5
32 GiB to under 64 GiB4
16 GiB to under 32 GiB3
Under 16 GiB2
RAM can't be read2

The hardware default only applies when no preference is stored, or when the stored value is out of range. Once you set it yourself, Daintree leaves it alone.

Tip

The Reset to hardware-recommended defaults button on the same Performance tab applies to panel limits, not to Cached project views. They sit next to each other and are easy to mix up. Cached project views has no reset of its own.

What gets evicted

When the cache is over its cap, Daintree evicts least-recently-used first, in this order of preference:

  • Idle views go first.
  • A view with an active agent is evicted only once no idle view is left.
  • A view that an MCP client session is bound to goes after that, because evicting it breaks the binding until you reopen the project. A view with an MCP request in flight is skipped outright until the request finishes.
  • A project with Keep workspace resident turned on in its settings is given up last. Low memory can still unload it.
  • A view with a running Daintree Assistant session is never evicted, at any pressure level. This is the view the session is pinned to, while its Claude Code or Codex process is still alive. Destroying that view would end the session and kill the CLI's process tree, including any subagent or background shell it spawned, and would buy no measurable memory, because the Assistant's process lives outside the renderer. Agents it launched into their own panels are not part of that tree.

Eviction destroys the UI layer, not the work. Terminals, PTYs and agent processes live outside the view and keep running; the next visit to that project is simply a cold switch.

Available system memory can also lower the effective cap below what you configured. Above the warning threshold your setting stands; between the warning and critical thresholds the cap steps down one view at a time, one per pass, so warm switching degrades gradually; below the critical threshold the cache collapses to the active view alone. This ladder is decoupled from the resource profile: it reacts to memory, not to battery or thermal state.