cmux NEXT sidebar: session tree with PR badges and keycaps (sidebar.sessionTree, off by default)

Merged
TE
17d ago
+4094-29

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's spawnedBy: 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.
  • 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/watch summary already carries spawnedBy and cwd. AgentActivityLane now 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 new SidebarPullRequestStore keys 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 create in its session summary. I left that as a follow-up rather than adding it here.

Code

  • CmuxNextSidebar:
    • SidebarSessionTree (shared store) and SidebarLayout.treeOrder.
    • WorkspaceRowContent gains treeDepth, keycap and pullRequests.
    • Row views: SidebarPullRequestBadgeView, SidebarKeycapView and WorkspaceRowView+SessionTree.
    • A row patch keeps its depth.
    • The god-file budget is respected: SidebarModel.swift, SidebarBridge and the other over-budget types don't grow.
  • CmuxNextAgentLanes/SidebarSessionTreeFeed.swift: the feed and its SidebarPullRequestStore. The app only passes its workspaces, from where the turn-state feed starts, because CmuxNextApp may not grow (check-app-size). The gh pr view reader (AgentChatPullRequestReader) moved unchanged from CmuxNextApp into CmuxNextAgentPane, next to AgentPanePullRequest, so the chat chip and the sidebar share it. CmuxNextApp ends 231 lines under its cap. ci-target-graph.json is 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 step b05e67a26082185e11778f94); schemas/defaults/cmux-next.json from defaults_guard.py snapshot; webviews/src/pages/settings/generated/strings.json from gen-strings.mjs. They are committed alone in 3031e0c.

l10n

Five new keys, translated in all 21 locales:

  • settings.sidebar.sessionTree and settings.sidebar.sessionTree.help
  • sidebar.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
  • treeOrder chains, cycles, and parents in another list
  • badge combining and PR state → color
  • the lane keeping spawnedBy and cwd
  • 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-tests of CmuxNext is clean.
  • swift test passes all 19 tests in SidebarSessionTreeTests, AgentChatPullRequestTests, AgentChatPullRequestStatusTests and AgentLanesTests.
  • The Sidebar|WorkspaceRow|Settings suites pass, apart from ManagedPreferencesManifestTests and SettingsSchemaExportTests, 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


View with [code]smith Autofix with [code]smith
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 gh limits: 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 create calls 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 view per URL for other hosts.
  • The feed lives in CmuxNextAgentLanes so CmuxNextApp stays under its size cap; the shared gh reader moved to CmuxNextAgentPane next to its model.