cmux NEXT sidebar: workspaces as tabs prototype (Debug, off): one-line rows, Cmd-Shift-[ / ] step workspaces

Merged
TE
16d ago
+248-8

Summary

Leo asked for a "workspaces as tabs" mode: a compact, Claude-desktop-style list with one line per workspace (title, status/PR badge, needs-you icon), and ⌘⇧[ / ⌘⇧] moving to the previous/next workspace in sidebar order, wrapping, across groups. This adds it as a prototype behind a Debug Settings tunable, sidebar.workspacesAsTabs, which is off by default.

With the tunable on:

  • Every workspace row is one line at any density. It shows the icon, name, working spinner, status glyph (waiting is the needs-you mark), PR badge and unread mark. There's no second line, status block, meta line or tab rows. This is a new row preset (SidebarLayoutOptions.tabRow) next to minimal mode's. Minimal hides the PR badge, so I didn't reuse it.
  • ⌘⇧] / ⌘⇧[ run nextSidebarTab / prevSidebarTab with scope: "workspaces". That scope walks only workspaces and collapsed groups, in sidebar order across groups, and always wraps, whatever sidebar.stepping / sidebar.steppingWraps say. A collapsed group is one stop: stepping onto it shows its first workspace, as ⌃⌘] does. The keys never land on Home, the App Store or another top item. From Home (opened by a click), ⌘⇧] goes to the first workspace. ⌃⌘] / [ pass no scope and keep following sidebar.stepping.
  • Home keeps its own ⌘⇧[ / ]: the bindings' when is sidebar.workspacesAsTabs && !onHome.

How the keys route: KeyBindingDefaults.workspacesAsTabs holds the two entries. The binding table only includes them while the tunable is on (ActionRegistry.includesWorkspaceTabKeys, which the app points at the tunable), and a table built with the other answer is rebuilt. They come after the Chrome tab keys, so while on they win over nextSurface / prevSurface. KeyRouter sets the sidebar.workspacesAsTabs context key from the same tunable.

Off, nothing changes, in Release builds too. The table, Settings > Keyboard Shortcuts, keybinding.list and their conflict badges are the shipped ones. ⌘⇧[ / ] are Ghostty's default previous_tab / next_tab. Lawrence (2026-10-15) said they must cycle tabs, not workspaces, and this request reached me relayed, not from Leo directly. So nothing moves unless someone turns the Debug toggle on. The action-surfaces export is untouched, and the prepush gate's Ghostty-default-keys check passes.

Testing

WorkspacesAsTabsTests (CmuxNextAppTests) covers:

  • Keys, toggle on: ⌘⇧] / [ resolve to nextSidebarTab / prevSidebarTab with scope: workspaces in a terminal, a page and an agent chat.
  • Keys, toggle off: they resolve to nextSurface / prevSurface.
  • Home: Home and docked Home resolve the same with the toggle on and off.
  • Real item order: with the top block (Home, App Store), a group and default settings:
    • the last workspace's ⌘⇧] wraps to the first, and the first's ⌘⇧[ to the last, never to a top item;
    • stepping goes through the group's workspace;
    • from Home, ⌘⇧] goes to the first workspace;
    • it still wraps with steppingWraps off;
    • ⌃⌘] / [ settings are unchanged.
  • Shortcut list: with the toggle off, no row names the toggle, and the ⌘⇧[ / ] rows are only nextSurface / prevSurface. With it on, the list has exactly two more rows.
  • Key context: it carries the toggle.
  • Row: a busy workspace gives one-line content at both densities, keeping its PR badge and waiting glyph.

scripts/ci/run-swift-testing-suites.sh on the fleet (3 shards):

Observed / Not run

  • cmux-ci compile-check --app passed on f3c2bfcec1c3 (job e79b1b8a950a9f6df2541101).
  • prepush-gate.py reported no new violations on every push.
  • Other suites at the green head: 25 failed. 24 of them also fail at base d6dcbed05b13, run the same way (steps 9428fdb4b2847328364a5c88, f76ff359be84f4ffcc793ebb, ca9496bf5411e73f120bb8e3).
  • The exception is MotionMasterSwitchSurfaceTests.aPopupOpensAndClosesStillWithTheMasterSwitchOff (:83, window alpha 1.0, expected 0). It passed at base, at the merged head and at the red head, and this PR touches nothing in popups or motion. A re-run at the same head is under way to tell a flake from a real failure.
  • Not run: the toggle in a running app, and captures.

Changelog

none (a Debug Settings prototype, off by default)

Proof

No capture: a Debug-only prototype, off by default. A capture will come with a dogfood pass once the tests can run.

Agent session: cosmic-gray

🤖 Generated with Claude Code