# How Window, Workspace, Pane, and Surface IDs Work in cmux: A Complete Technical Guide

> Understand cmux window, workspace, pane, and surface IDs. Learn how UUIDs manage UI, persistence, and scripting in this technical guide.

- Repository: [manaflow-ai/cmux](https://github.com/manaflow-ai/cmux)
- Tags: deep-dive
- Published: 2026-03-29

---

**cmux uses a hierarchy of UUID-based identifiers—Window IDs for top-level NSWindows, PaneIDs from Bonsplit for split containers, and Surface IDs (TabID/UUID) for individual terminal or browser panels—to enable precise UI manipulation, session persistence, and remote scripting.**

cmux is a terminal multiplexer built on Bonsplit and Ghostty that relies on a sophisticated ID system to manage its UI hierarchy. Understanding how window, workspace, pane, and surface IDs work is essential for developers integrating with cmux's socket API or extending its functionality. According to the manaflow-ai/cmux source code, these identifiers form a complete addressing scheme that allows precise targeting of any UI element across the entire application.

## Window IDs: The Top-Level UUIDs

A **window** in cmux corresponds to a top-level `NSWindow` that hosts exactly one `Workspace`. When a new window is created, `AppDelegate` assigns a fresh `UUID` and stores it in `mainWindowContexts`.

### Creating and Storing Window IDs

The window ID generation occurs during window initialization. The `AppDelegate` maintains the mapping between window instances and their identifiers:

```swift
// AppDelegate.swift – Window ID retrieval for a TabManager
// https://github.com/manaflow-ai/cmux/blob/main/Sources/AppDelegate.swift#L4415-L4417
func windowId(for tabManager: TabManager) -> UUID? {
    mainWindowContexts.values.first(where: { $0.tabManager === tabManager })?.windowId
}

```

### Bidirectional Window Lookup

cmux supports reverse resolution through `AppDelegate.windowId(for window:)`, which extracts the UUID from an `NSWindow` instance. This bidirectional mapping enables the v2 socket protocol, UI-automation tests, and the scriptable main windows API (`scriptableMainWindow(_:)`).

## Workspace and Pane IDs: Bonsplit Integration

A **workspace** is the logical container for panes and panels, maintaining a **one-to-one** relationship with a window's `TabManager`. The workspace stores mapping tables that connect **pane IDs** to **surface IDs**.

### PaneID Structure and Origin

**PaneID** originates from the Bonsplit library and uniquely identifies a split pane within a workspace. It is a lightweight struct wrapping a UUID that Bonsplit returns from its tree API. When a split is created, Bonsplit generates a new `PaneID` that cmux uses for all subsequent operations.

Most `Workspace` methods accept an `inPane paneId: PaneID` argument for creating surfaces, moving panels, or focusing specific splits:

```swift
// Workspace.swift – Moving a surface between panes
// https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift#L8261-L8265
func moveSurface(panelId: UUID, toPane paneId: PaneID, atIndex index: Int? = nil, focus: Bool = true) -> Bool {
    // Implementation handles the surface relocation
}

```

### Resolving Pane UUIDs to Context

When external commands provide a pane UUID (`UUID`), `TerminalController` resolves it to the complete context tuple:

```swift
// TerminalController.swift – Resolving pane UUIDs
// https://github.com/manaflow-ai/cmux/blob/main/Sources/TerminalController.swift#L3118-L3122
private func v2LocatePane(_ paneUUID: UUID) -> (
    windowId: UUID, tabManager: TabManager, workspace: Workspace, paneId: PaneID
)? {
    // Returns complete context for the specified pane
}

```

This method is the primary entry point for any external request targeting a specific pane, returning the window ID, tab manager, workspace reference, and the Bonsplit PaneID.

## Surface IDs: Terminal and Browser Panels

A **surface** represents a concrete UI element—whether a terminal, browser, or markdown panel. Internally identified by **TabID** (which is simply a `UUID` alias in cmux), surfaces are mapped to their rendering panels through bidirectional dictionaries in the Workspace class.

### The Surface-to-Panel Mapping

The `Workspace` class maintains `surfaceIdToPanelId`, a dictionary providing two-way lookups between surface identifiers and panel UUIDs:

```swift
// Workspace.swift – Surface ID to Panel UUID
// https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift#L5949-L5950
func panelIdFromSurfaceId(_ surfaceId: TabID) -> UUID? {
    surfaceIdToPanelId[surfaceId]
}

```

```swift
// Workspace.swift – Panel UUID to Surface ID
// https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift#L5945-L5946
func surfaceIdFromPanelId(_ panelId: UUID) -> TabID? {
    surfaceIdToPanelId.first { $0.value == panelId }?.key
}

```

**Forward lookup** (`panelIdFromSurfaceId`) enables surface-oriented commands like "focus surface X." **Reverse lookup** (`surfaceIdFromPanelId`) allows panels to notify the workspace of state changes, title updates, and remote-drop events.

### Practical Surface Operations

When a terminal panel triggers a visual notification (such as a bell flash), it uses the reverse mapping to locate its containing pane:

```swift
// Workspace.swift – Pane flash triggered by surface event
// https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift#L53-L60
private func configureTerminalPanel(_ terminalPanel: TerminalPanel) {
    terminalPanel.onRequestWorkspacePaneFlash = { [weak self, weak terminalPanel] reason in
        guard let self, let terminalPanel else { return }
        self.triggerWorkspacePaneFlash(panelId: terminalPanel.id, reason: reason)
    }
}

```

## ID Resolution in Practice: Socket Commands to UI Actions

The complete ID resolution chain enables cmux to handle complex operations like moving a browser surface from one pane to another across different windows.

### Locating Context from External Commands

The typical resolution flow begins with a socket command containing a pane or surface UUID:

1. **Input**: Socket payload provides a pane UUID
2. **Resolution**: `TerminalController.v2LocatePane(_:)` resolves to `(windowId, tabManager, workspace, paneId)`
3. **Operation**: The workspace performs the requested action using the resolved IDs

### Moving Surfaces Between Panes

To relocate a surface, cmux chains the ID lookups:

```swift
// Example: Moving a browser surface between panes
let surfaceId: TabID = /* source surface UUID */
let targetPane = PaneID(id: UUID(uuidString: "target-uuid")!)

if let panelId = workspace.panelIdFromSurfaceId(surfaceId) {
    workspace.moveSurface(panelId: panelId, toPane: targetPane, focus: true)
}

```

This operation requires resolving the surface ID to its panel UUID, then invoking `moveSurface` with the target `PaneID`.

## Code Examples for ID Manipulation

### Resolving Window IDs from Workspaces

Retrieve the window UUID for a given workspace through the `AppDelegate` singleton:

```swift
import AppKit

if let tabMgr = workspace.tabManager,
   let winId = AppDelegate.shared?.windowId(for: tabMgr) {
    print("Current window UUID: \(winId)")
}

```

*Source:* `AppDelegate.windowId(for:)` – [`Sources/AppDelegate.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/AppDelegate.swift) lines 4415-4417

### Finding Pane IDs for Specific Surfaces

Locate which pane contains a given surface by chaining the mapping methods:

```swift
let surfaceId: UUID = /* surface UUID from remote command */
if let panelId = workspace.panelIdFromSurfaceId(surfaceId),
   let paneId = workspace.paneId(forPanelId: panelId) {
    print("Surface \(surfaceId) lives in pane: \(paneId)")
}

```

*Source:* `Workspace.paneId(forPanelId:)` – [`Sources/Workspace.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift) lines 7963-7965

### Handling Socket Command Resolution

Process v2 socket protocol messages that reference panes by UUID:

```swift
// Payload from v2 socket command
let paneUUID = UUID(uuidString: payload["pane_uuid"] as! String)!

if let ctx = TerminalController.shared?.v2LocatePane(paneUUID) {
    // ctx contains windowId, tabManager, workspace, and paneId
    print("Pane belongs to window \(ctx.windowId)")
    print("Bonsplit PaneID: \(ctx.paneId)")
}

```

*Source:* `TerminalController.v2LocatePane` – [`Sources/TerminalController.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/TerminalController.swift) lines 3118-3122

## Summary

- **Window IDs** are UUIDs stored in `AppDelegate.mainWindowContexts`, providing top-level window addressing via `windowId(for:)` methods.
- **Pane IDs** (`PaneID`) originate from Bonsplit and identify split containers within workspaces, resolved through `TerminalController.v2LocatePane`.
- **Surface IDs** (`TabID`/`UUID`) represent individual terminal or browser panels, mapped bidirectionally to panel UUIDs via `Workspace.surfaceIdToPanelId`.
- **Resolution chains** allow socket commands to target specific UI elements by converting UUIDs into complete context tuples containing window, workspace, and pane references.
- **Session persistence** serializes all three ID types to enable complete state restoration across application restarts.

## Frequently Asked Questions

### What is the difference between a Pane ID and a Surface ID in cmux?

A **Pane ID** (`PaneID`) identifies a split container within the Bonsplit tree structure and manages layout geometry, while a **Surface ID** (`TabID`/`UUID`) identifies the actual content panel (terminal, browser, or markdown view) rendered inside that pane. A single pane can contain multiple surfaces (in tabbed configurations), or a surface can move between panes while retaining its Surface ID.

### How does cmux resolve a pane UUID from a socket command to the actual UI element?

cmux uses `TerminalController.v2LocatePane(_:)` (defined at `Sources/TerminalController.swift:3118`) to resolve a pane UUID into a complete context tuple containing the `windowId`, `TabManager`, `Workspace`, and `PaneID`. This method searches across all windows to locate the specific pane and returns the full addressing context needed to perform UI operations.

### Can surfaces move between windows while maintaining their IDs?

Yes. Because **Surface IDs** are UUIDs and **Pane IDs** are separate Bonsplit identifiers, cmux can move a surface between different panes and windows using `Workspace.moveSurface(panelId:toPane:atIndex:focus:)`. The surface retains its original `TabID` throughout the move operation, while the workspace updates the internal `surfaceIdToPanelId` mapping to reflect the new location.

### Where are these IDs stored for session persistence?

Session snapshots store all three identifier types in [`Workspace.swift`](https://github.com/manaflow-ai/cmux/blob/main/Workspace.swift) around lines 3525-3535. The serialization includes the window UUID, pane tree structure with `PaneID` references, and surface mappings. When cmux restores a session, it reconstructs the `surfaceIdToPanelId` dictionary and recreates the Bonsplit pane hierarchy with consistent IDs to restore the exact UI state.