Worktree Cards
The anatomy of a worktree card in the sidebar: lifecycle chips, row status signals, alarm salience, the quick state filter, collapsed states, filters, and per-card recovery.
Worktree Cards
Each worktree shows as a card in the sidebar. This page covers how a card is built: the lifecycle chip, row status signals, alarm salience, the quick state filter, collapsed states, filters, and per-card recovery. For creating, switching and deleting worktrees (and for what a deleted worktree's card does), see Worktrees.
A card carries:
- Branch name, with a badge for the main worktree (it moves to the secondary row when an issue is linked)
- Mood indicator: stable, active, stale or error
- Issue title as the headline when one is linked, plus PR badges for PR state
- Git status: changed file count, insertions and deletions
- Last commit summary
- Terminal list: every panel in this worktree with its kind and state
- Quick actions: launch agents, run recipes, open in an editor
Local git state on cards (commits, branch switches, file changes) comes from file-system event watching rather than polling, so an agent writing files in a background worktree shows up live instead of staying invisible until it commits. A polling fallback covers the case where the OS-level watcher isn't available, which on Linux usually means the inotify watch limit is exhausted.
On Linux, sidebar updates that lag by tens of seconds usually mean the inotify watch limit is exhausted. Check the log (Settings > Troubleshooting > Open Log File) for a watcher initialization warning, then raise the limit with sudo sysctl -w fs.inotify.max_user_watches=524288. To persist it, add fs.inotify.max_user_watches=524288 to /etc/sysctl.d/99-inotify.conf and run sudo sysctl --system.
Double-click the card header to collapse it. A collapsed card takes less vertical space while still showing status. Double-click again to expand.
Lifecycle Stage Chip
A small triangular chip in a card's top-left corner names the next action that worktree needs. It only appears when there is something to act on, and it is absent while git data is still resolving.
| Chip | Tooltip | Meaning |
|---|---|---|
| Amber | Agent waiting for input | An agent in this worktree is blocked on you. |
| Blue | Complete: in review | A linked issue, an open PR, no uncommitted changes, and no active agent. It is ready for review. |
| Purple | Ready for cleanup | The PR is merged. The worktree has served its purpose and can be deleted. |
Blue, not green, for complete. Across the app green means working and blue means in review or finished; a green "done" chip would compete with the one signal that means work is happening right now.
When several conditions apply the chip shows the highest-priority state: cleanup, then complete, then waiting. A merged PR always wins, because deleting the worktree is the most definitive thing left to do.
Only cleanup is gated to secondary worktrees. Waiting and complete render on the main card too: the main worktree can have an agent waiting on it like any other.
A purple chip also adds a trash-icon button to the row toolbar, next to the three-dot menu, so Delete is one click rather than a menu dive. The icon stays muted red until hover. Once a worktree reaches complete, open the Review Hub from its menu to stage, review and push.
Row Status Signals
To the right of the branch name sits a set of small signals. Each is independent and degrades on its own, so a stale freshness pill never hides a fresh commit chip beside it.
| Signal | What it shows |
|---|---|
| Git state indicator | An inline label for an in-progress git operation: rebasing, merging, cherry-picking, reverting or detached, amber; conflicted, red. It is a passive label, not a control. |
| Commit chip | An activity dot and the relative time of the last commit ("2m"). The dot fades from accent to idle as time passes. Hover for author, subject and absolute timestamp. |
| Upstream sync badge | ↑N / ↓N against the upstream remote. When upstream and the base branch have diverged from each other, a second pair of counts appears against the base branch. |
| Freshness pill | How recently the working tree status was read. Hidden under 30 seconds. Between 30 and 60 seconds, a dim relative-time pill; between one and five minutes it brightens; past five minutes it becomes a clickable Refresh. |
| External badge | A folder-out icon on a worktree git reports from outside the project directory, with the full path in its tooltip. See Worktrees outside the project. |
| Pin | A pin icon on a worktree you pinned to the top from the card menu. |
| Environment | The resource-environment popover, when the worktree runs on non-local compute. See Remote Compute. |
| Dev server | A dev-preview indicator when one is running for this worktree. See Dev Preview. |
The author avatar is deliberately left off the row: a column of identical "you" avatars adds noise and no information. The commit chip's tooltip surfaces the author when you need it.
Where mid-rebase recovery lives
The git state indicator tells you a worktree is stuck mid-rebase or mid-merge. It does not offer to fix it. The Continue and Abort buttons that used to sit on the card were removed: a two-button recovery on a narrow row, with no view of the conflicted files, invited exactly the wrong call.
Recovery lives in the Review Hub's conflict panel instead, next to the conflicted file list, the per-file resolve controls and the rebase sequence rail. Continue is only enabled once no conflicts remain.
Status still resolving, and the review button
Before git has answered for a worktree, its subtitle reads Checking status… rather than "No recent activity", an all-clear the data can't yet support.
The card's review-opener tracks what the worktree actually needs:
- Review & Commit when the tree has changes.
- Review & Push when the tree is clean but commits are unpushed. The next step is still in the Review Hub, so the button stays; only the label shifts.
- Nothing, when the tree is clean and everything is pushed.
Alarm Salience and the Collapsed Pill
A row can carry a failing CI run, an upstream auth failure, a behind-upstream count and more at once. Salience tiers keep it from becoming a wall of badges: the most important signal renders first, on the left.
| Tier | Signal | Tone |
|---|---|---|
| 3 (highest) | CI failed | Red |
| 2 | Auth failed (couldn't fetch from upstream) | Amber |
| 1 | Behind upstream | Amber |
| 0 | No alarm | None |
Only a failure CI state climbs above tier 0. Pending and neutral states stay quiet, so a long push session doesn't flicker the row between tiers as checks queue and finish. Detached HEAD is deliberately not an alarm either: the git state indicator already labels it in the title row, and a colored pill would double-label the same fact.
What makes CI "failed"
The CI signal is derived from the PR's required checks, not the raw status rollup, the same derivation the PR dropdown in Code Forge uses, so the two never disagree. Tooltips read like "2 of 5 required checks failing", or "No required checks" when the branch has none configured.
The derivation refuses to invent a green. If the checks page is truncated, if a context arrives in a shape it can't classify, or if no required checks are configured at all, it falls back to the raw rollup state and shows no summary rather than reporting a pass over checks it never saw. A stale required run counts as failing, because it has not resolved to a passing state.
Collapsed, the badge row reduces to a single pill in the header carrying the highest active tier's color and label: "CI failed" in red, "Behind" in amber. Expand the card to get the individual badges back.
Quick State Filter
The filter bar above the worktree list filters by agent state. Four segments: All, Working, Waiting, Finished. "All" keeps its text label; the other three are an icon plus a count, which is what keeps the bar legible in a narrow sidebar.
- Working: a green spinner. When the count is above zero it turns slowly, gated by
prefers-reduced-motion. It is a glance signal, not a change in polling cadence. - Waiting: an amber hollow circle.
- Finished: a blue check circle.
The active chip carries a subtle inset bottom rule in neutral text color, not the accent. Accent stays scarce. Clicking an active chip resets to All. Each chip's accessible name carries noun and count together ("Working, 3 worktrees"), so dropping the visible text label costs screen-reader users nothing.
"Finished" is not "Completed." Finished is a worktree-level label covering complete and cleanup worktrees. Completed is a session-level state: one agent that finished a turn. Both labels appear on the same card: the filter bar counts worktrees, the collapsed card indicators count sessions.
Counts are disjunctive. Each chip's count is what the list would show if only that chip were active, not the count within the current filtered set. With All selected and "Waiting (2)" on the bar, clicking Waiting reliably shows two worktrees, regardless of what you clicked before.
The trailing arm button
A small lightning-bolt button sits at the trailing edge of the bar, past a divider. One click arms every unarmed agent in the currently filtered set and opens the Fleet Arming Ribbon, ready for an Accept, Interrupt or broadcast.
It addresses agents only. Plain shells are excluded (they stay armable individually and through the header fleet picker), which matches the state filters beside it, since those count agents too. The tooltip and accessible name track the state:
| State | Label |
|---|---|
| No filter, nothing armed | Arm all N agents |
| Filter active, nothing armed | Arm N matching agents |
| Some already armed | Arm N more agents / Arm N more matching agents |
| Nothing eligible | No agents to arm / No agents match the filter |
| Everything already armed | All agents are armed / All matching agents are armed |
The button never disappears (it dims and disables), so its place in the bar stays predictable.
PR and Issue Badge Freshness
PR and issue badges come from cached provider responses. The badge's glyph tells you how much to trust the number right now, without opening a panel or reading a tooltip.
| Glyph | Meaning |
|---|---|
| None | Fresh: the cached value is recent and no refresh is queued. |
| Clock | Stale: older than the freshness threshold, with a refresh in flight or queued. |
| Cloud-off | Disconnected: a rate limit, an open circuit breaker after repeated failures, or PR detection paused. The last known value stays on screen; the glyph says don't treat it as live. |
The tooltip carries the relative timestamp ("Updated 4 minutes ago") and, when disconnected, the reason and a retry hint. With no forge token configured the badge grayscales and the tooltip says so.
Collapsed Cards
Collapsing a card hides the body (git status, last commit, terminal list, quick actions), leaving the branch name and a row of compact session indicators. Each indicator is an icon plus a count.
| State | Icon | Tone | Animated |
|---|---|---|---|
| Working | Spinner circle | Green | Yes |
| Directing | Interacting circle | Blue | No |
| Waiting | Hollow circle | Amber | No |
| Done | Check circle | Slate | No |
| Exited | Exited circle | Dim | No |
Hovering the group shows the full summary, such as "3 sessions: 2 working, 1 waiting". Idle sessions are never shown: when every session is idle, or there are none, the collapsed card is just the branch name. For what each session state means, see Agent States.
Focused Sub-Line
An expanded card reveals a thin line of context below the branch name when it has focus, from hover or keyboard. It is hidden by default, expands with a short height transition and a brief fade, and collapses again when focus leaves. The animation respects prefers-reduced-motion.
Up to three dot-separated segments, in order: the changed file count when the tree is dirty ("3 files"), the last activity time as a live relative timestamp, and the short status label the header uses. It stays out of the way until you're looking at the card, then gives you enough for a triage call without expanding the body.
Primary Worktree Card
The main worktree's card uses a two-row header that doubles as a project-level overview.
The top row shows the project name rather than the branch, a sprout icon, a collapse button and the actions menu. The second row, visible when expanded, shows the branch label, upstream sync, and an aggregate summary of the secondary worktrees.
- Upstream sync:
↑Nin green for commits ahead,↓Nin amber for commits behind, with the full sentence on hover. - Aggregate summary: a branch icon with the total worktree count, then colored counts for working and waiting. The finished count only appears once nothing is working or waiting, so finished work never drowns out work that still needs you.
Expanded, the body adds the same worktree and agent counts, plus a health row with CI status, open PR count and open issue count. That row needs a configured forge token.
The Worktree Sidebar
The sidebar is where you navigate worktrees. It is virtualized and keyboard-first: only the rows in view plus a small overscan buffer render to the DOM, so scroll performance holds on projects with hundreds of worktrees.
The main worktree renders outside the virtual surface and stays at the top. Below it, pinned worktrees, then everything else by the active sort, then external worktrees. Deleted-worktree rows sit at the position they were deleted from.
When rows sit fully offscreen, small pills appear at the top and bottom edges ("Scroll up, 12 more above"), and clicking one jumps to that end of the list.
The search box, filter popover and results list follow the ARIA combobox pattern, so filter changes and result counts are announced. See Search and Filters for what the controls do.
Refreshing
The refresh button in the sidebar header is a true full refresh, not a nudge. It discards cached forge data and re-resolves worktree state, issue and PR titles, and CI badges, re-reading from disk and from the provider. That costs provider round trips against your rate limit, so it is an escape hatch for when something changed outside the app, not a habit to build on top of routine polling.
The pass is watchdogged and each phase is isolated: a slow PR fetch can't sink the worktree refresh, topology re-discovery failing doesn't stop the status pass, and the button always resolves. If the host doesn't respond at all, a Refresh failed toast says so rather than the button quietly doing nothing.
Keyboard navigation
The sidebar has two focus modes. List mode is the default, with focus on a row. Toolbar mode moves focus into the focused row's inline action buttons. The grid holds a single tab stop and tracks the active row through aria-activedescendant.
List mode
| Key | Action |
|---|---|
| ↑ / ↓ (or j / k) | Move row focus |
| PgUp / PgDn | Move one viewport page |
| Home / End | First / last worktree |
| Enter or → | Enter the focused row's toolbar |
| Space | Select / switch to the focused worktree |
| Alt+↑ / Alt+↓ | Reorder the row in the custom sort |
Toolbar mode
| Key | Action |
|---|---|
| ← / → | Move between toolbar buttons (wraps) |
| Home / End | First / last toolbar button |
| Ctrl+Home / Ctrl+End | Return to list mode, jump to first / last row |
| ↑ / ↓ | Return to list mode and move row focus by one |
| Esc | Return to list mode, keeping the active row |
Leaving the window drops back to list mode, so re-entering the app never lands inside a toolbar. The Command Control Control modifier is ignored throughout: the grid has no bindings of its own that use it.
Reordering
Alt+↑ and Alt+↓ move the focused worktree in the list. The custom order is per-project and survives restarts, but it only applies when Sort by → Custom order is selected in the filter popover. Reorder under any other sort and the keybinding does nothing.
Reordering is also off while the list is grouped by type or a search query is active. Both controls sit above the worktree list, and with either on the Alt+Arrow keys and drag-to-reorder do nothing. The order itself is kept, you just cannot edit it until you clear the filter.
Focus follows the card as it moves, and the screen-reader announcement reports the new position once the key release settles, so holding the key does not fire an announcement per step.
With the mouse, drag a card and a thin directional line shows where it will land. Escape mid-drag cancels, and the card snaps back with an assertive "drag cancelled" announcement.
Placeholder cards during creation
While a worktree is being created, single or bulk, a placeholder card holds its eventual position so the list doesn't reflow each time a bulk item finishes out of order. Pending placeholders show pulsing skeleton lines with aria-busy="true" and a label like "Creating worktree {branch}". A failure switches the card to an error state titled Couldn't create worktree, carrying the branch name, the error message, and Retry and Dismiss links.
Reconnecting and load errors
When the renderer loses contact with the worktree host (rare, and usually after a sleep/wake cycle or a host crash) the sidebar header shows a spinner and Reconnecting…. Past a short threshold it escalates to Reconnecting… last updated {relative time}, so you can tell fresh data from a stale snapshot. A failed initial load shows a top banner titled Couldn't load worktrees with the error and a Retry button.
Search and Filters
The search bar at the top of the sidebar finds worktrees by name or branch and filters as you type. Press Escape to clear the text and any active filters in one action; press it again on an empty search to blur the input. The X at the right edge does both.
The filter icon inside the search bar opens the advanced popover. A small green dot on the icon marks any active non-search filter.
| Category | Options |
|---|---|
| Sort by | Date created, Recently updated, Alphabetical, Custom order |
| Group by type | Toggle: groups worktrees by branch-type prefix, plus an "Outside the project" section when external worktrees exist |
| Status | Active, Dirty, Stale, Idle |
| Branch Type | Feature, Bugfix, Refactor, Chore, Docs, Test, Release, CI, Deps, Perf, Style, WIP, Main, Detached, Other |
| Issues & PRs | Has Issue, Has PR, PR Open, PR Merged, PR Closed |
| Sessions | Has Terminals, Working, Waiting, Completed, Exited |
| Activity | 15m, 1h, 24h, 7d |
| Dev server | Has server, Running, Starting, Error |
The "Issues & PRs" section is provider-neutral: it filters on whatever forge the project is connected to, not on GitHub specifically. "Custom order" is hidden while "Group by type" is on. A Clear all filters button appears at the bottom of the popover whenever anything is active.
Chip counts inside each category use the same disjunctive logic as the quick state filter: the number you see is the number you get if you click.
Activity recency is unified across worktrees, so "Recently updated" and the activity windows mean the same thing everywhere rather than each worktree tracking its own notion of recent.
Visual Hierarchy
With many worktrees open, Daintree dims idle and stale cards so active work stays prominent. A card is muted when it is idle or stale, has no waiting agent, isn't the selected worktree, and isn't hovered or focused. Muted cards use a dimmer text color for the branch name and labels.
One exception matters: a card with a waiting agent is never muted, however idle the worktree otherwise looks. Waiting is the state that needs you.
Lifecycle Failure Recovery
A setup lifecycle script can fail on a missing dependency, a wrong path or a network blip. The failure surfaces in the card footer rather than in a toast you can miss after a context switch, and the worktree holds that state until you act on it.
The footer reads Setup didn't finish. Re-run when you're ready. with two controls:
- Retry setup: re-runs the failed phase. The label becomes "Retrying…" while it runs, then the footer clears on success. The retry is pinned to the card's own worktree, not whichever one is focused, so you can trigger it from anywhere.
- Show details: expands the captured error and output in a scrollable monospaced block. Daintree keeps the last 8192 bytes.
The footer survives card collapse and persists across restarts, so a half-set-up worktree never quietly disappears.
Restarting a Session
Restarting an agent session uses a two-click armed pattern, which guards against accidental scrollback loss without putting a modal in the way.
- Open the panel's three-dot menu and click Restart Session. The item arms: its background tints amber and the label becomes Confirm Restart (3s) with a ticking countdown.
- Click again within three seconds. The menu stays open between the two clicks, so there is no moving target to chase.
Without the second click the item disarms itself. It also resets if you switch panels or if restart eligibility changes mid-countdown. The armed item's accessible name reads "Armed: click again to confirm restart. N seconds remaining", so the countdown isn't visual-only.
The Card Menu
The card's three-dot menu gathers everything you can do to a worktree without leaving the sidebar: reordering, launching, browsing, dev-server control, forge links, review, and context export.
Sessions Submenu
The menu's Sessions submenu fans one action across every session in the worktree. Each item shows its eligible count and disables at zero.
| Item | What it does |
|---|---|
| Dock All | Move every grid-resident session into the dock. |
| Maximize All | Restore every docked session to the grid. |
| Close All | Close every active session. |
| Terminate All | Send SIGTERM to every agent process, for when Close All would wait too long. |
| Reset All Renderers | Force-reload the terminal renderer for every session here, useful after a GPU driver hiccup or a font-loading glitch. |
| Clear Session History | Drop this worktree's resumable session journal. |
| Select All Terminals | Arm every agent terminal here and open the Fleet Arming Ribbon. |
| Select Waiting Agents | Arm only the sessions currently waiting. |
| Select Working Agents | Arm only the sessions currently working. |
Close All and Clear Session History confirm from every dispatch source, including the action palette and agent-initiated calls, not only from the menu.
WSL Git Banner
On Windows, when a worktree lives inside a WSL distribution, Daintree probes whether git can be routed through the interop layer and shows a banner on the card describing what it found.
The WSL git banner does not appear on macOS. Daintree always runs git natively.
The banner has four states.
Eligible: git is installed in the distribution and reachable. The title reads Speed up git on this WSL worktree:
This worktree lives in WSL ({distro}). Routing git through wsl git avoids the Windows–Linux filesystem boundary and can make status polling 5–10× faster. Two buttons: Enable WSL git switches this worktree to WSL-native git; Not now dismisses. You can flip the setting back later.
Ineligible: the distro is stopped, isn't the default distro, or has no git. The title reads Git runs via Windows:
This worktree is in {distro}, which isn't the default WSL distro. Git can't be routed through this distro automatically: it will run from Windows and may be slower than usual. Two buttons here too: Re-check (which reads Re-checking… while it runs, and adds "Couldn't re-check. Try again." if it errors) and Got it.
Unreadable: the default distro couldn't be read at all, so routing is undetermined. The title reads Couldn't check WSL distro, git runs from Windows in the meantime, and a Re-check button is the only control. You are never stranded on a spinner.
Probing: a short skeleton while the probe resolves. It appears only if the probe is slow enough to be worth acknowledging.
The WSL git banner does not appear on Linux. Daintree always runs git natively.
Worktrees Outside the Project
Git will happily report worktrees that live outside the project directory. Daintree identifies those and sorts them apart: they sink below everything the project owns, ahead of the pin check, so a stale pin can't lift one back to the top. In grouped mode they get their own Outside the project section.
On the card, a FolderOutput icon carries the tooltip "Outside the project directory" and the full path. External worktrees can't be pinned, and the pin entry is ignored if one exists.
Containment is only asserted on an explicit answer from the workspace host. When the host can't resolve the boundary, the worktree is treated as internal: a transient path-resolution failure shouldn't demote every worktree in the project at once.
Pinning is the counterpart: Pin to Top / Unpin on any non-main, non-external card's menu. Pinned worktrees sit directly under main and every sort honors that.