Add cmux left-sidebar toggle|show|hide|focus|mode
Summary
cmux has no command for the left (workspace) sidebar. To hide it from a script, or check whether it's showing, you have to press View → Toggle Left Sidebar through macOS Accessibility and read the window's accessibility tree. That grants cmux, and every shell and agent running in it, control of the whole Mac. It also broke on 0.64, when the menu item was renamed.
This adds cmux left-sidebar toggle|show|hide|focus|mode, the left-side sibling of cmux right-sidebar, with the same --window <id|ref|index> and --workspace <id|ref|index> targeting. Without a target it acts on the window ⌘B would toggle.
toggle,showandhidechange that window's sidebar directly and never make a window key or activate the app, so a background script can hide the sidebar without taking over the screen. This is stricter thanright-sidebaron purpose: itstoggleandshowmay move focus.focusbrings the window forward, shows the sidebar if needed and focuses the workspace list through the existing Focus Sidebar path. It's the onlyleft_sidebarcommand the socket focus policy lets move focus.modeprints{"visible":bool}. The issue asked forstateprintingshown|hidden. I matchedright-sidebar modeinstead, without itsmodefield since the left sidebar has none, so scripts read both the same way. I can add astatealias if you'd prefer.- A sidebar the window collapsed for lack of room reports
visible: false, andshowandtogglebring it back the way ⌘B does.hidealso clears that auto-collapse, so the sidebar doesn't come back when the window widens.
set, the per-mode aliases and --no-focus only make sense for the right sidebar, so they're rejected. Over the socket this is the V1 left_sidebar line, routed through CmuxControlSocket like right_sidebar. There's no v2 method, and the cmux ssh relay never forwards V1 lines, so no relay allowlist change is needed.
Fixes #15196
Commits
The first three are behavior-preserving refactors, so the feature can reuse right-sidebar's code instead of copying it:
- Rename the right-sidebar targeting helpers to neutral names (
SidebarRemoteTarget,sidebarRemoteContext,resolveSidebarWindowId,resolveSidebarWorkspaceId,resolvedSidebarHandleID). - Move right-sidebar's CLI flag parsing and forwarding, and the app's
--workspace/--tab/--windowreading, into helpers that take each sidebar's own messages. The app-side helpers live inSources/SidebarRemoteArguments.swift. Right-sidebar replies are unchanged. - Pull ⌘B's window choice out of
toggleSidebarInActiveMainWindowintosidebarToggleTarget(), so the CLI picks the same window without also making it key. - The feature.
- The tests.
After review, with each regression test committed before its fix: a blank --window or --workspace value now fails instead of falling back to the default window, for right-sidebar too. The global --window no longer focuses a left-sidebar target before the command runs. The parser and focus-policy tests call the real code instead of DEBUG-only wrappers.
If you'd rather keep this smaller, I can drop commit 2 and give left-sidebar its own parsing instead.
Testing
On macOS 26.6.2 with Xcode 26.6, at b2d226640 on top of main 5da8121f9:
CMUX_DEV_BACKEND_MODE=local ./scripts/reload.sh --tag left-sidebar --build-onlybuilt the app.tests/test_cli_left_sidebar.py(new, 5 tests) andtests/test_cli_right_sidebar_aliases.pypass against that build's CLI and a fake socket that records requests. They cover what each action sends,modeoutput, target forwarding, and rejected input (no subcommand,state,set files,files, extra words,--no-focus, missing or blank--window/--workspacevalues) never reaching the socket, and the global--windowsending nowindow.focus. The 7 cases added after review failed ondc92b7798and pass now. The right-sidebar test passing is the check that commit 2 kept its CLI behavior.swift test --package-path Packages/macOS/CmuxControlSocket: 627 tests pass, including 3 newleft_sidebarcoordinator tests andleft_sidebarin the main-actor policy test../scripts/test-unit.sh build-for-testingcompiles the 7 new app tests incmuxTests/LeftSidebarRemoteCommandTests.swift: parsing, the focus policy, state changes, window and workspace targeting, the ⌘B default target,hideclearing auto-collapse, and the no-window error. I haven't run them locally, so they first run in CI.python3 scripts/verify-local.py(17/17) andgit diff --checkpass.- Manual run in a tagged build of an earlier commit (
81ac3c1a7, before the parsing refactor): visibility andmode, agreement with ⌘B and View → Toggle Left Sidebar,hide/showfrom a background terminal leaving focus and window order alone, window and workspace targeting, narrow-window auto-collapse, rejected input, andright-sidebarall behaved as described. The final commit hasn't been run in an app; for the refactor, the CLI tests above are the evidence.
Localization: 16 new keys (leftSidebar.remote.error.* and cli.leftSidebar.*, including the usage text) in all 9 locales, added with ./scripts/localize-changes. python3 scripts/localization_catalog.py check reports 0 parity errors. Like their neighbors, the V1 help line, the top-level synopsis line and a DEBUG-only test message aren't localized.
Not fixed here
Manual testing turned these up. I left them out to keep this PR to one feature:
- With both sidebars open,
focusleaves keyboard focus in the right sidebar. The Focus Sidebar shortcut does the same, and this PR doesn't change its code, so it looks likefocusSidebarForKeyboard(Sources/AppDelegate+SidebarKeyboardFocus.swift): it focuses the list once and doesn't take focus back. focuson a hidden sidebar shows it, but the list doesn't keep keyboard focus, probably the same function focusing before the sidebar finishes appearing. I haven't checked whether the shortcut fails this way.
I can open an issue for the first one if it's worth fixing separately.
Changelog
Added: cmux left-sidebar toggle|show|hide|focus|mode shows, hides, focuses and reads the left sidebar from scripts, without Accessibility
Demo
Rejected input stops before the CLI connects, with exit status 1:
$ cmux left-sidebar state
Error: Unknown left-sidebar command 'state'
$ cmux left-sidebar show --no-focus
Error: left-sidebar: unknown flag '--no-focus'
$ cmux left-sidebar toggle extra
Error: left-sidebar toggle received unexpected arguments
$ cmux left-sidebar hide --window
Error: left-sidebar: --window requires an id
No screenshots, since nothing on screen changes beyond what ⌘B already does.
Checklist
- Behavior changes have added or updated tests, or Testing says why not
- UI, settings, menu, schema, help-text or user-facing docs change: localization audited, and the result is stated above
- User-facing docs updated if needed (
docs/cli-contract.md) - Reviewed with a subagent before merge (cmux-review), and all bot and human review comments resolved
🤖 Generated with Claude Code
Summary by CodeRabbit
- New Features
- Added CLI and remote-control support for the left sidebar, including toggle, show, hide, focus, and visibility checks.
- Added optional workspace and window targeting for left-sidebar commands.
- Visibility checks return JSON state; focus brings the relevant window forward and focuses the workspace list.
- Bug Fixes
- Improved validation and error reporting for invalid sidebar commands, options, and targets.
- Documentation
- Documented left-sidebar commands, targeting, and behavior in CLI help and the command reference.