# Workspaces, Splits & Vertical Sidebar

> How cmux turns terminal tabs into a spatial workspace system with Bonsplit panes, draggable workspaces, sidebar metadata, pinning, and split equalization.

- Repository: manaflow-ai/cmux
- GitHub: https://github.com/manaflow-ai/cmux
- Human wiki: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a
- Complete Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/llms-full.txt

## Source Files

- `Sources/ContentView.swift`
- `Sources/Workspace.swift`
- `Sources/WorkspaceContentView.swift`
- `Sources/TabManager.swift`
- `Sources/Sidebar/SidebarState.swift`
- `Sources/Sidebar/SidebarDropPlanner.swift`
- `Sources/Workspace+EqualizeSplitsSupport.swift`
- `cmuxUITests/BonsplitTabDragUITests.swift`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [Sources/ContentView.swift](Sources/ContentView.swift)
- [Sources/Workspace.swift](Sources/Workspace.swift)
- [Sources/WorkspaceContentView.swift](Sources/WorkspaceContentView.swift)
- [Sources/TabManager.swift](Sources/TabManager.swift)
- [Sources/Sidebar/SidebarState.swift](Sources/Sidebar/SidebarState.swift)
- [Sources/Sidebar/SidebarDropPlanner.swift](Sources/Sidebar/SidebarDropPlanner.swift)
- [Sources/Workspace+EqualizeSplitsSupport.swift](Sources/Workspace+EqualizeSplitsSupport.swift)
- [Sources/TabManager+EqualizeSplits.swift](Sources/TabManager+EqualizeSplits.swift)
- [Sources/SplitEqualizer.swift](Sources/SplitEqualizer.swift)
- [Sources/cmuxApp+EqualizeSplitsMenu.swift](Sources/cmuxApp+EqualizeSplitsMenu.swift)
- [Sources/AppDelegate+EqualizeSplitsShortcut.swift](Sources/AppDelegate+EqualizeSplitsShortcut.swift)
- [Sources/GhosttyTerminalView.swift](Sources/GhosttyTerminalView.swift)
- [cmuxUITests/BonsplitTabDragUITests.swift](cmuxUITests/BonsplitTabDragUITests.swift)
- [cmuxTests/TabManagerUnitTests.swift](cmuxTests/TabManagerUnitTests.swift)
</details>

# Workspaces, Splits & Vertical Sidebar

cmux treats a terminal session as more than a flat tab. A workspace is the outer navigation unit, while Bonsplit manages panes inside that workspace and each pane can hold multiple terminal or browser surfaces. The vertical sidebar is the product surface that makes this model usable: it shows workspace identity, status, branch/directory metadata, unread signals, pin state, and drop targets.

This page follows the bundled Compound Engineering page-shape guidance provided for this run; implementation claims below are grounded in repository code and tests, not an installed local skill execution.

## Spatial Model

```text
Window / TabManager
  -> Workspace list in vertical sidebar
       -> Workspace
            -> BonsplitController split tree
                 -> Pane
                      -> Bonsplit tab
                           -> cmux Panel: TerminalPanel or BrowserPanel
```

`TabManager` still calls the outer list `tabs`, but the stored objects are `Workspace` instances. Each `Workspace` owns a `BonsplitController`, a panel map, and published workspace metadata such as title, custom description, pin state, color, current directory, panel titles, panel directories, and per-panel pin/unread state. `WorkspaceContentView` renders that controller through `BonsplitView`, mapping each Bonsplit tab back to a cmux panel before rendering `PanelContentView`.

Sources: [Sources/TabManager.swift:1104-1127](), [Sources/Workspace.swift:8976-9038](), [Sources/Workspace.swift:9121-9126](), [Sources/WorkspaceContentView.swift:148-190](), [Sources/WorkspaceContentView.swift:218-289]()

## Workspace Creation And Bonsplit Ownership

Creating a workspace is a `TabManager` responsibility. `addWorkspace` snapshots the current workspace list, derives working directory and config inheritance, computes an insertion index, creates a `Workspace`, wires ownership, inserts it into the live `tabs` array, and optionally selects it. The `Workspace` constructor then creates an initial terminal panel, creates a Bonsplit tab for it, removes Bonsplit’s default welcome tab, wires external drop/close/zoom callbacks, sets itself as the Bonsplit delegate, and forces initial pane/tab focus so the workspace starts interactive.

Sources: [Sources/TabManager.swift:2500-2585](), [Sources/Workspace.swift:9700-9810]()

## Vertical Sidebar As Workspace Index

The left sidebar is mounted by `ContentView` as `VerticalTabsSidebar` and framed at the current sidebar width. `SidebarState` keeps only visibility and persisted width, while `ContentView` sanitizes restored width, persists width changes, and schedules portal geometry synchronization when the sidebar changes. This keeps the sidebar layout separate from terminal/browser portal geometry.

Sources: [Sources/ContentView.swift:1977-1997](), [Sources/ContentView.swift:2148-2152](), [Sources/ContentView.swift:2626-2640](), [Sources/ContentView.swift:3107-3182](), [Sources/Sidebar/SidebarState.swift:4-17]()

The sidebar body builds a render context from `tabManager.tabs`, selected workspace ids, shortcut hints, remote workspace state, and terminal scrollbar visibility. It uses a non-lazy `VStack` for workspace rows because drag-state invalidations can recurse through lazy layout. Each row is an equatable `TabItemView` with precomputed inputs and plain object references for action handlers, reducing unnecessary row redraws while still allowing actions such as selection, drag, context menu, close, and move accessibility actions.

Sources: [Sources/ContentView.swift:9592-9846](), [Sources/ContentView.swift:10737-10922](), [Sources/ContentView.swift:13299-13415](), [Sources/ContentView.swift:14048-14100]()

## Sidebar Metadata

Sidebar rows are not just labels. The row snapshot can include title, custom description, pin state, custom color, remote workspace text/status/help, latest conversation or notification text, structured metadata entries/blocks, latest logs, progress, branch/directory lines, pull request rows, and listening ports. The row renders those details conditionally based on sidebar settings, so the same workspace model can support compact and information-dense sidebar modes.

Sources: [Sources/ContentView.swift:13321-13343](), [Sources/ContentView.swift:13628-13743](), [Sources/ContentView.swift:13743-13907]()

## Reordering Workspaces

Workspace reordering flows through the sidebar row drag/drop delegates and then into `TabManager.reorderWorkspace`. The sidebar delegate computes a legal target index from the current workspace ids, current drop indicator, and pinned workspace ids. `TabManager` applies the reorder, posts `.workspaceOrderDidChange`, and publishes a `CmuxEventBus` reorder event with full workspace ordering and pinned workspace ids.

Sources: [Sources/ContentView.swift:15682-15865](), [Sources/Sidebar/SidebarDropPlanner.swift:14-84](), [Sources/TabManager.swift:5400-5435]()

Pinned workspaces form a protected leading group. The drop planner clamps unpinned workspaces after the pinned count and pinned workspaces within the pinned region. `TabManager` applies the same rule when toggling pin state or clamping a direct reorder index, so sidebar drag/drop and programmatic reorder stay consistent.

Sources: [Sources/Sidebar/SidebarDropPlanner.swift:192-224](), [Sources/TabManager.swift:5561-5589]()

## Dragging Bonsplit Tabs Into Workspaces

The sidebar also accepts Bonsplit tab drags. Dropping a Bonsplit tab onto an existing workspace calls `AppDelegate.moveBonsplitTab` through the sidebar drop delegate. Dropping over the workspace-list geometry can also choose between an existing workspace and a new workspace insertion point; the overlay builds `WorkspaceDropTarget` values from row frames, then calls either `moveBonsplitTab` or `moveBonsplitTabToNewWorkspace`.

Sources: [Sources/ContentView.swift:10758-10821](), [Sources/ContentView.swift:15682-15723](), [Sources/Sidebar/SidebarDropPlanner.swift:86-168]()

## Pane And Surface Pinning

cmux has two pin layers. Workspace pinning is on `Workspace.isPinned` and controls the outer sidebar order. Surface pinning is per panel inside a workspace via `pinnedPanelIds`; changing it updates the Bonsplit tab and normalizes the pane’s tab order so pinned surfaces stay before unpinned surfaces. Bonsplit tab context actions expose this as `togglePin`.

Sources: [Sources/Workspace.swift:8995-9000](), [Sources/Workspace.swift:10325-10348](), [Sources/Workspace.swift:10470-10484](), [Sources/Workspace.swift:16910-16928]()

## Split Creation And Pane Events

Programmatic terminal and browser splits pre-create the cmux panel, pre-generate a Bonsplit tab, install the tab-to-panel mapping, then call `bonsplitController.splitPane`. This avoids transient empty panels and lets cmux publish split-created events with kind and origin. Terminal splits inherit working directory and terminal config from the source panel where possible; browser splits carry browser profile and remote workspace proxy context.

Sources: [Sources/Workspace.swift:12312-12500](), [Sources/Workspace.swift:12615-12712]()

When Bonsplit itself reports a split, the delegate distinguishes programmatic splits from UI splits. Programmatic splits only normalize pins and reconcile geometry. UI-originated splits auto-create a terminal in the new pane, and drag-to-split repairs placeholder-only source panes by replacing placeholder tabs with real terminal surfaces or dropping extra placeholders.

Sources: [Sources/Workspace.swift:16560-16608](), [Sources/Workspace.swift:16610-16690](), [Sources/Workspace.swift:16693-16755]()

## Split Equalization

Equalization is a shared action, not a one-off UI patch. The command palette, menu command, app shortcut, and Ghostty action all call `TabManager.equalizeSplits`. That method runs `SplitEqualizer` over the Bonsplit tree snapshot, then tells the workspace that split geometry changed so normal geometry reconciliation flows run.

```swift
// Sources/SplitEqualizer.swift
let totalLeafCount = firstLeafCount + secondLeafCount
let position = CGFloat(firstLeafCount) / CGFloat(totalLeafCount)
controller.setDividerPosition(position, forSplit: splitId, fromExternal: true)
```

`SplitEqualizer` recursively counts leaves under each split and sets each divider proportionally, so a three-leaf tree becomes balanced by subtree leaf count rather than blindly setting every divider to 50%.

Sources: [Sources/TabManager+EqualizeSplits.swift:3-22](), [Sources/SplitEqualizer.swift:5-75](), [Sources/Workspace+EqualizeSplitsSupport.swift:1-4](), [Sources/ContentView.swift:7288-7307](), [Sources/ContentView.swift:7894-7906](), [Sources/cmuxApp+EqualizeSplitsMenu.swift:3-16](), [Sources/AppDelegate+EqualizeSplitsShortcut.swift:1-22](), [Sources/GhosttyTerminalView.swift:4437-4444]()

## Verification Surface

The UI tests cover the high-risk parts of this model: Bonsplit tab drag reorder, sidebar row top inset across minimal and standard presentation modes, standard titlebar control placement, and minimal-mode hover reveal behavior for sidebar controls. Unit coverage also asserts that equalization succeeds and produces a proportional split tree.

Sources: [cmuxUITests/BonsplitTabDragUITests.swift:18-68](), [cmuxUITests/BonsplitTabDragUITests.swift:307-470](), [cmuxTests/TabManagerUnitTests.swift:2268-2285]()

## Product And Architecture Notes

This design stays BYOC/BYOK friendly because the workspace, split, sidebar, pinning, and equalization model is local app state plus repository code. The sidebar metadata can display remote workspace and provider-sourced details, but the core interaction model does not require a specific model provider, hosted connector, or proprietary catalog. A Grok-Wiki-style integration should therefore document the feature from portable source artifacts: files, repository history, tests, and optional skill-pack text as guidance, while treating the code paths above as source of truth.

The key reusable idea is the split between spatial state and presentation state: `Workspace` owns Bonsplit and panel identity, `TabManager` owns workspace ordering and selection, `VerticalTabsSidebar` owns the navigable index, and `SplitEqualizer` owns layout balancing. That separation is what lets cmux support draggable workspaces, Bonsplit pane tabs, sidebar metadata, pin boundaries, and equalization without making any one UI surface the only implementation path. Sources: [Sources/Workspace.swift:8976-9038](), [Sources/TabManager.swift:5400-5435](), [Sources/ContentView.swift:10758-10922](), [Sources/SplitEqualizer.swift:14-75]()
