cmux NEXT sidebar: session tree with PR badges and keycaps (sidebar.sessionTree, off by default)
FYI @lawrencecchen: this is in your sidebar area. It stays behind sidebar.sessionTree (off by default) and lands under Leo's merge directive; no action needed.
Leo pointed at Claude desktop's Code tab session list. This PR brings its look into cmux NEXT's native sidebar behind a new setting, sidebar.sessionTree (Settings → Appearance → Sidebar → Session Tree). The setting is off by default, so the default look doesn't change.
With it on:
- Nesting. A workspace whose agent another workspace's agent started is listed right under that workspace, indented, with a thin guide line (
Palette.separator). Nesting is one level deep, matching acpmux'sspawnedBy: a child's child also sits under the top parent. It works for loose rows and inside groups. A parent in another list, or a cycle, leaves the row where it is. - PR badge. Each row shows a PR glyph (
gitPullrequest) and a count, in the status color. All colors are theme tokens:- green (
Palette.success): open, with checks passing or still running - orange (
Palette.attention): a draft, a merge conflict, or a failing check - purple (the theme's
GroupColor.purple): merged - Several PRs show their count in the most urgent color. Closed PRs are left out.
- green (
- Keycaps. The rows Command-1…9 reach show their digit in a small keycap, in the unread mark's slot. A row with an unread mark keeps the mark, and other rows keep their unread and activity dots. The Command-number order (
itemOrder,visibleWorkspaceIDs) follows the drawn order, so a keycap always matches its shortcut.
Where the data comes from (no new source)
- Session → workspace: each workspace's agent chat tabs (
TabModel.agentSession.session, this Mac's host only). - Parent: the acpmux
_acpmux/watchsummary already carriesspawnedByandcwd.AgentActivityLanenow keeps both. The lane feed runs at launch, not behind Labs. - PRs: the branch PR of each session folder, read with the chat header's existing
AgentChatPullRequestReader(gh pr view, two at a time, never prompts). A newSidebarPullRequestStorekeys answers by folder. It runs only while the setting is on, and reads each folder at most once every five minutes (the chat chip shares its two gh slots), when a lane or workspace changes. Nothing polls. - Limitation: the count is the distinct branch PRs across a workspace's session folders. One branch has one PR, so a session that opened PRs from several branches in one folder shows only its current branch's PR. The smallest source that would count every PR a session opened is for acpmux to record the URLs from
gh pr createin its session summary. I left that as a follow-up rather than adding it here.
Code
CmuxNextSidebar:SidebarSessionTree(shared store) andSidebarLayout.treeOrder.WorkspaceRowContentgainstreeDepth,keycapandpullRequests.- Row views:
SidebarPullRequestBadgeView,SidebarKeycapViewandWorkspaceRowView+SessionTree. - A row patch keeps its depth.
- The god-file budget is respected:
SidebarModel.swift,SidebarBridgeand the other over-budget types don't grow.
CmuxNextAgentLanes/SidebarSessionTreeFeed.swift: the feed and itsSidebarPullRequestStore. The app only passes its workspaces, from where the turn-state feed starts, because CmuxNextApp may not grow (check-app-size). Thegh pr viewreader (AgentChatPullRequestReader) moved unchanged from CmuxNextApp into CmuxNextAgentPane, next toAgentPanePullRequest, so the chat chip and the sidebar share it. CmuxNextApp ends 231 lines under its cap.ci-target-graph.jsonis regenerated for CmuxNextAgentLanes' new dependencies.- Setting: parsed in
SidebarSectionsSetting, listed in the schema and the agent policy. - Generated exports:
schemas/settings/settings-schema.json,docs/mdm/*and the source stamp come from the fleet settings export (cmux-ci stepb05e67a26082185e11778f94);schemas/defaults/cmux-next.jsonfromdefaults_guard.py snapshot;webviews/src/pages/settings/generated/strings.jsonfromgen-strings.mjs. They are committed alone in3031e0c.
l10n
Five new keys, translated in all 21 locales:
settings.sidebar.sessionTreeandsettings.sidebar.sessionTree.helpsidebar.a11y.pullRequests.{passing,attention,merged}: the PR badge text in the row's VoiceOver label
check-l10n.sh shows 95 errors on both the base and this branch (base reds such as sidebar.doc.unavailable), with none from these keys.
Tests
Tests/CmuxNextAppTests/SidebarSessionTreeTests.swift covers:
- the layout order and depth, with the tree on and off
treeOrderchains, cycles, and parents in another list- badge combining and PR state → color
- the lane keeping
spawnedByandcwd - a rendered
SidebarView: the child's guide line, its indent, the badge, consecutive keycaps and the VoiceOver text
Verified on a Mac (macOS 27, at the restructured head):
swift build --build-testsof CmuxNext is clean.swift testpasses all 19 tests inSidebarSessionTreeTests,AgentChatPullRequestTests,AgentChatPullRequestStatusTestsandAgentLanesTests.- The
Sidebar|WorkspaceRow|Settingssuites pass, apart fromManagedPreferencesManifestTestsandSettingsSchemaExportTests, which fail only until the settings exports are regenerated (CI's generated-files job; that export step runs only in CI).
Other local checks that passed: check-no-godfiles --base, check-app-size --base, check-concurrency, check-crash-safety, check-motion, check-module-resources, check-swift-type-scale, lint-ios-package-conventions, ci-target-graph --check, tour.py --check.
Screenshots / media
This PR carries the media-tour label. The core tour now ends by turning on sidebar.sessionTree, which shows the keycaps. The CI tour has no agents that start other agents and no PRs, so the nesting and badges need a dogfood run with real lanes:
cmux settings set sidebar.sessionTree true
The Linux-pool dev build (cmux-ci build) couldn't be submitted from this lane's host: dev-backend: cannot reach ubuntu@cmux-dev-backend-1, and gcloud isn't installed, so this host is outside the tailnet.
Changelog
Added: Sidebar Session Tree setting (off by default). A workspace started by another workspace's agent lists under it, rows show their pull requests in green, orange or purple, and the first rows show their Command-number keys.
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Adds an optional session tree to cmux NEXT's native sidebar behind a new sidebar.sessionTree setting (Settings → Appearance → Sidebar, off by default), bringing Claude desktop's Code tab look to agent-started workspaces without changing the default view.
Behavior
- A workspace whose agent another workspace's agent started (acpmux
spawnedBy) lists under it, indented with a guide line, one level deep; parents in another list or cycles leave the row in place. - Rows show a pull request badge in the most urgent status color (green passing, orange draft/conflict, red failed check, purple merged) and Command-1…9 keycaps that follow the drawn order.
- Hovering a badge opens a card listing the workspace's pull requests with a state pill,
owner/repo #N, and+adds −dels; a click opens the PR in the focused pane's browser.sidebar.pullRequestCard(on by default) turns it off. - Reads run only while the setting is on and share the chat chip's
ghlimits: open PRs recheck every five minutes, settled PRs read once, failed reads keep cached answers, and data for rows no longer shown is dropped.
Data
- acpmux now records the URLs each session's
gh pr createcalls print, so the tree counts every PR a session opened in addition to its current branch's. - The card reads live state in one batched GraphQL query per refresh, falling back to
gh pr viewper URL for other hosts. - The feed lives in
CmuxNextAgentLanessoCmuxNextAppstays under its size cap; the sharedghreader moved toCmuxNextAgentPanenext to its model.