# How cmux Handles Split Pane Geometry and Resizing in macOS

> Learn how cmux manages split pane geometry and resizing in macOS. Discover its layout-follow-up system using revision counters to sync logical trees with rendering surfaces.

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

---

**cmux prevents AppKit re-entrancy crashes by deferring split pane geometry updates through a layout-follow-up system that uses UInt64 revision counters to synchronize Bonsplit's logical tree with Ghostty rendering surfaces.**

cmux is a Swift-based terminal multiplexer that coordinates complex UI state between the Bonsplit layout engine, AppKit window frames, and Ghostty terminal surfaces. When users create or resize split panes, the application must keep three distinct geometry layers in perfect sync without triggering AppKit's layout re-entrancy limits or causing visual flashes in terminal and browser panels.

## The Three-Layer Geometry Stack

Every split operation in cmux spans three distinct architectural layers that must remain consistent:

- **Bonsplit logical layout** – The tree of panes and tabs maintained by the `BonsplitController` submodule
- **AppKit window geometry** – The actual `NSView` frames hosting terminals or browsers in `TerminalWindowPortal`, `BrowserPanelView`, and `GhosttyTerminalView`
- **Ghostty surface geometry** – The low-level rendering surface that draws terminal content or web views

According to the manaflow-ai/cmux source code, cmux coordinates these layers through a *layout-follow-up* system that watches for geometry changes, records revision counters, and schedules deferred layout passes to avoid crashes.

## The Layout Follow-Up Architecture

The core mechanism preventing layout re-entrancy centers on asynchronous coordination rather than immediate updates.

### Tracking Geometry with Revision Counters

Each host view maintains a `geometryRevision` counter to detect actual frame changes. In [`Sources/Panels/BrowserPanelView.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Panels/BrowserPanelView.swift) and [`Sources/GhosttyTerminalView.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/GhosttyTerminalView.swift), the view increments this `UInt64` property every time its layout updates:

```swift
private(set) var geometryRevision: UInt64 = 0

override func viewDidLayout() {
    super.viewDidLayout()
    geometryRevision &+= 1          // bump revision on any frame change
}

```

This pattern ensures that cmux can identify exactly which panels have moved, avoiding unnecessary surface reattachments that would cause visual flashes or performance overhead.

### Detecting Stale Geometry

When Bonsplit notifies cmux that geometry changed via `splitTabBar(_:didChangeGeometry:)` in [`Sources/Workspace.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift) (lines 9028-9034), the system sets `layoutFollowUpNeedsGeometryPass = true`. However, because this callback occurs inside SwiftUI's `.onChange(of: geometry)`—still within an active layout pass—cmux cannot safely mutate view hierarchies immediately.

### The Deferred Update Loop

To prevent `NSGenericException` crashes from exceeding AppKit's per-window layout-pass limit, `scheduleLayoutFollowUpAttempt()` defers work using `DispatchQueue.main.asyncAfter(0)`:

```swift
private func scheduleLayoutFollowUpAttempt() {
    guard layoutFollowUpTimeoutWorkItem != nil,
          !layoutFollowUpAttemptScheduled else { return }

    layoutFollowUpAttemptScheduled = true
    let delay = layoutFollowUpBackoffDelay()
    let version = layoutFollowUpAttemptVersion

    DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
        guard let self,
              self.layoutFollowUpAttemptVersion == version else { return }
        self.layoutFollowUpAttemptScheduled = false
        self.attemptEventDrivenLayoutFollowUp()
    }
}

```

The `layoutFollowUpAttemptVersion` token acts as a cancellation mechanism, aborting stale attempts scheduled before newer geometry changes arrive.

## Step-by-Step Implementation Flow

### Creating a Split Pane

When users initiate a split, `Workspace.newTerminalSplit` in [`Sources/Workspace.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift) (lines 28-42) first constructs the panel, registers a Bonsplit tab, and then requests the layout mutation:

```swift
let newPanel = TerminalPanel(
    workspaceId: id,
    context: GHOSTTY_SURFACE_CONTEXT_SPLIT,
    configTemplate: inheritedConfig,
    workingDirectory: splitWorkingDirectory,
    portOrdinal: portOrdinal,
    initialCommand: remoteTerminalStartupCommand
)
configureTerminalPanel(newPanel)
panels[newPanel.id] = newPanel

let newTab = Bonsplit.Tab(
    title: newPanel.displayTitle,
    icon: newPanel.displayIcon,
    kind: .terminal,
    isDirty: newPanel.isDirty,
    isPinned: false
)
surfaceIdToPanelId[newTab.id] = newPanel.id

isProgrammaticSplit = true
defer { isProgrammaticSplit = false }
guard bonsplitController.splitPane(paneId,
                                   orientation: orientation,
                                   withTab: newTab,
                                   insertFirst: insertFirst) != nil else { return }

```

The split insertion occurs **before** Bonsplit mutates its internal layout, ensuring the UI never flashes an empty panel.

### Synchronizing Across Layers

During `attemptEventDrivenLayoutFollowUp()`, cmux calls `flushWorkspaceWindowLayouts()` to force a layout pass, then walks every panel to compare revisions. In [`Sources/Panels/BrowserPanelView.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Panels/BrowserPanelView.swift) and similar terminal views, the coordinator updates its tracked revision only when necessary:

```swift
if coordinator.lastSynchronizedHostGeometryRevision != host.geometryRevision {
    coordinator.lastSynchronizedHostGeometryRevision = host.geometryRevision
    // Re‑attach web view or force a terminal redraw here
}

```

This check prevents redundant surface reattachments while guaranteeing that Ghostty surfaces match their host view frames exactly.

## Handling Rapid Resize Events

During divider drags, geometry can churn multiple times per frame. The `layoutFollowUpBackoffDelay()` method in [`Sources/Workspace.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift) (lines 44-48) implements an exponential backoff that prevents flooding the main queue with async attempts. If a follow-up timeout exceeds 2 seconds without progress, `clearLayoutFollowUp()` removes observers and resets state to prevent resource leaks.

## Practical Usage Example

Programmatically create a split and let cmux handle the geometry synchronization automatically:

```swift
if let leftPaneId = workspace.activePaneId,
   let newPanel = workspace.newTerminalSplit(
       from: leftPaneId,
       orientation: .horizontal,
       insertFirst: false,
       focus: true) {
    
    // The split is created, Bonsplit notified, and layout follow‑up scheduled.
    // No further code is needed – cmux will asynchronously flush the layout
    // and synchronize the terminal surface when the host view’s frame changes.
    print("Created new split with panel id \(newPanel.id)")
}

```

Behind the scenes, this triggers the geometry-revision workflow, deferred layout pass, and eventual surface synchronization without requiring manual intervention.

## Summary

- **Three-layer synchronization**: cmux maintains consistency between Bonsplit's logical tree, AppKit `NSView` frames, and Ghostty rendering surfaces
- **Revision counters**: Each host view tracks frame changes via a `UInt64 geometryRevision` property that increments in `viewDidLayout()`
- **Re-entrancy protection**: The `scheduleLayoutFollowUpAttempt()` method uses `DispatchQueue.main.asyncAfter(0)` to break layout cycles and prevent `NSGenericException` crashes
- **Backoff handling**: Rapid resize events are smoothed via `layoutFollowUpBackoffDelay()` to prevent main queue flooding while ensuring eventual consistency
- **Key files**: Primary implementation resides in [`Sources/Workspace.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift), [`Sources/Panels/BrowserPanelView.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Panels/BrowserPanelView.swift), and [`Sources/GhosttyTerminalView.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/GhosttyTerminalView.swift)

## Frequently Asked Questions

### How does cmux prevent crashes during split pane resizing?

**cmux avoids `NSGenericException` crashes by never calling `displayIfNeeded()` directly inside Bonsplit geometry callbacks.** Instead, the `scheduleLayoutFollowUpAttempt()` method defers layout work to the next run loop iteration using `DispatchQueue.main.asyncAfter(0)`, breaking the re-entrancy chain that would otherwise exceed AppKit's per-window layout-pass limit.

### What is the purpose of the geometryRevision counter?

**The `geometryRevision` counter provides stale-view protection by tracking exactly when host view frames change.** Each `BrowserPanelView` and `GhosttyTerminalView` increments this `UInt64` property in `viewDidLayout()`, allowing the coordinator to compare against `lastSynchronizedHostGeometryRevision` and update only panels that actually moved, eliminating unnecessary surface reattachments.

### How does cmux handle rapid resize events during divider drags?

**The `layoutFollowUpBackoffDelay()` method implements an exponential backoff strategy.** When geometry churns rapidly during a divider drag, this function increases the delay between async layout attempts, preventing the main queue from flooding while still guaranteeing that the final geometry state eventually synchronizes across all three architectural layers.

### Which source files manage split geometry synchronization?

**According to the manaflow-ai/cmux source code**, [`Sources/Workspace.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Workspace.swift) lines 28-46 and 9028-9034 orchestrate the layout-follow-up system and Bonsplit delegate callbacks, while [`Sources/Panels/BrowserPanelView.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/Panels/BrowserPanelView.swift) and [`Sources/GhosttyTerminalView.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/GhosttyTerminalView.swift) implement the `geometryRevision` tracking and surface reattachment logic for browser and terminal panels respectively.