cmux NEXT sidebar: the session tree at real depth, with folds and roll-ups
With sidebar.sessionTree on (off by default), a session that started other sessions now draws them at their real depth, can fold them away, and shows what the hidden ones are doing.
Leo: "there's literally only 1 level of nesting/grouping so everything literally goes under you even tho new sessions start their own new sessions".
#21814 walked the whole spawnedBy chain but drew every descendant at depth 1 under the top parent. Here SidebarLayout.treeOrder does a depth-first walk: each workspace draws right after its parent, one level deeper, before the parent's next sibling, at any depth. Siblings keep the list's order. A parent in another list, or a cycle, still leaves the row a root. It is part of the design in manaflow-ai/cmux-design-references#3; #21944 (stable order) and #21945 (needs-you tray) build on it.
Folds and roll-ups
- A parent row gets a chevron: always shown on a folded parent, on hover or selection on an open one.
- The chevron folds the parent's whole subtree and the list redraws right away, like the tabs disclosure. Folds live in
SidebarSessionTree.collapsedand are saved in UserDefaults (sidebar.sessionTree.collapsed), so the feed's next pass and a relaunch keep them. A saved fold is dropped once that workspace is no longer a parent in the live tree. - A folded parent draws chips for its hidden sessions: waiting on you (attention color), failed (danger), running (quiet), finished unread (highlight). Idle sessions aren't counted.
- A filter draws every fold open, so search still finds sessions inside a folded subtree. Selecting a session opens the folds above it and keeps every other saved fold (at launch the feed may not know every parent yet).
- VoiceOver: the row's label reads the roll-up, and the row has Show / Hide Started Sessions actions.
- Workspace groups' member rows keep #21814's drawing: depth, but no folds or roll-ups.
Where the states come from. SidebarAgentState.of decides it in one place, and the tray PR reuses it. The feed maps each workspace's acpmux lanes (input โ waiting on you, failed/lost โ failed, working โ running, idle โ idle; with several lanes the strongest wins). An idle workspace with an unread mark counts as finished unread. A workspace with no lane (a terminal agent reporting through hooks) reads its own row: waiting, error, working, then unread. Unread alone never counts as waiting on you.
Strings. Seven new keys in CmuxNextSidebar/Resources/Localizable.xcstrings, translated in all 21 locales. The catalog keeps base's key order, so its diff is only the new keys.
Base merges
#21814 has landed on feat-cmux-next, so its code here is now base's. The branch last merged feat-cmux-next at d072c32b; its diff against base is only this PR's change, 21 files under CmuxNextSidebar, CmuxNextAgentLanes and the tests. Conflicts were additive:
- the loose rows go through the tree walk (
treeRows) and then base's Done fold (#23633), which leaves the rows alone while the tree is on; SidebarSessionTreekeeps base's every-rowpullRequests(#23495) beside the saved folds, and the list reads the tree asSidebarSessionTree.shared.drawn(live state plus folds);- the row keeps base's
machineMark(#22763) and Prioritychip(#23390) besidetreeFold/rollUp.
The loose-row tree drawing lives in SidebarLayout+SessionTree.swift so check-no-godfiles --base passes, and the root walk in treeChildren loops on its own condition (the concurrency scanner refuses while true).
Tests
Tests/CmuxNextAppTests/SidebarDeepTreeTests.swift:
- depth across three levels; siblings in list order;
- a fifty-workspace tree, four levels deep, in shuffled model order: every row at its depth, every subtree one unbroken run;
- a fold hides the whole subtree; a folded parent rolls up its descendants by state;
- a filter draws a folded parent open; a deep chain keeps every link in
treeChildren; - the no-lane reading is strict;
- a fold survives the feed's next pass, and a closed parent's fold leaves the saved list;
- revealing a session opens only the folds above it and keeps the rest.
#21814's one-level test is narrowed to what still holds (other lists and cycles).
Verification
Locally (no build): scripts/cmux-next/prepush-gate.py (no new violations), scripts/ci/risk_tier.py classify (normal). full-ci stays on so CI's cmux-next swift test builds and runs SidebarDeepTreeTests and SidebarSessionTreeTests; see the checks on the head for their result.
No capture: the chevrons and roll-ups draw only for workspaces whose agent sessions started other sessions (acpmux spawnedBy), and no tour or scene seeds such a tree yet (the core tour's session tree step shows keycaps only). Native gallery wiring for sidebar states is #21964.
Changelog
Changed: With the Session Tree setting on, sessions nest at their real depth under the session that started them, a parent can fold its sessions, and a folded parent shows what they are doing.
Agent session: cosmic-gray
๐ค Generated with Claude Code