cmux Layout System Optimization
Pane zoom (⌘⇧↩ maximize / minimize) benchmark & fix — July 10, 2026
Goal: benchmark and eliminate the lag when maximizing a pane and then minimizing it with multiple panes open.
Root cause
Toggling zoom rebuilt the entire split layout, twice over, on every keypress:
- bonsplit (layout engine):
SplitViewContainerrenderedzoomedNode ?? rootNode. Swapping the rendered node changes SwiftUI structural identity, so the whole AppKit hierarchy — everyNSSplitViewand oneNSHostingControllerper pane — was torn down on maximize and fully rebuilt on minimize. Cost scales with pane count. - cmux (app):
WorkspaceContentViewadditionally re-keyed the whole Bonsplit subtree with.id(zoomState)(an old workaround for stale pane chrome stacking above browser portals). That forced a second guaranteed full teardown/rebuild in both directions, and every minimize triggered 18–20 terminal-portal detach/attach events (Metal surface rebinds).
The fix: collapse-in-place zoom
Zoom no longer swaps or re-keys anything. The split tree stays mounted; zoom is applied by collapsing the side of each split that doesn't contain the zoomed pane (hidden arranged subviews are excluded from NSSplitView tiling, so the zoom path expands to the full container). Un-zoom unhides and re-applies the stored divider ratios.
- O(1) toggles: no teardown, no hosting-controller churn, no terminal reparenting — pane frames are preserved through the zoom, so minimize is almost free.
- Synchronous apply: split coordinators register a collapse applier with the controller; the AppKit collapse happens in the same runloop turn as the keypress instead of waiting for SwiftUI to re-thread state through every nested hosting controller (which is display-paced). The SwiftUI pass still runs afterwards as an idempotent backstop that reads live controller state.
- Zoom-aware portal visibility: because collapsed panes now stay mounted, the app-side pane content visibility (
isVisibleInUI) accounts for zoom, so SwiftUI updates can't flash hidden terminals back over the zoomed pane.
Benchmark 1 — isolated layout engine (bonsplit package)
New reproducible benchmark: Tests/BonsplitTests/ZoomTogglePerformanceTests.swift hosts the real BonsplitView in an NSWindow, builds an 8-pane layout, and measures wall-clock settle latency per toggle over 12 cycles plus AppKit content-view churn. Run with swift test --filter ZoomTogglePerformanceTests. Identical harness for before/after.
| Metric (debug, 8 panes, median) | Before | After | Change |
|---|---|---|---|
| Maximize settle | 443.8 ms | 166–190 ms * | ≈2.5× faster |
| Minimize settle | 1303.4 ms | 3.3 ms | ≈395× faster |
| Pane content NSViews recreated per maximize | 1 | 0 | eliminated |
| Pane content NSViews recreated per minimize | 8 | 0 | eliminated |
Release mode before: maximize 146.7 ms / minimize 406.8 ms median → after: minimize 2.5–3.3 ms; churn 0.
Benchmark 2 — real app A/B (end-to-end)
Two identical Debug builds of cmux (tags layout-zoom-before / layout-zoom-bench), differing only in the fix. Same machine, same 11-pane workspace, same scripted driver: 10 maximize/minimize cycles via real ⌘⇧↩ key events, timings extracted from the timestamped debug event log with the same parser and settle definition (toggle begin → last layout/portal activity).
| Metric (11 panes, median of 10) | Before | After | Change |
|---|---|---|---|
| Maximize settle | 315 ms | 115 ms | 2.7× faster |
| Minimize settle | 825 ms | 130 ms | 6.3× faster |
| Keypress → all split frames applied (direct metric) | n/a (rebuild) | ~31–34 ms | one event-loop turn |
| Terminal portal detach/attach per minimize | 18–20 | 0 | eliminated |
| NSSplitView instances rebuilt per toggle | full tree | 0 | same view pointers across all 20 toggles |
The remaining ~115–130 ms “settle” tail is the trailing SwiftUI pass applying visibility flags and chrome updates; the layout itself (what the user sees move) lands in ~31–34 ms from the keypress — within about two display frames, with zero Metal-surface rebinds.
Visual verification
Bugs found & fixed along the way
- Stale-state flap: SwiftUI update passes carrying a pre-toggle zoom value could briefly restore and re-collapse the split (double terminal resize + visible flash). Fixed by having the AppKit update path read live controller state; the threaded value only guarantees an update pass reaches every nested split.
- Ghost terminals over the zoomed pane: with panes now staying mounted, the app’s per-pane visibility computation (which ignored zoom, because unmounting used to handle it) periodically flipped hidden terminals visible. Fixed by making pane content visibility zoom-aware.
Validation
- bonsplit package: 138/138 tests pass (137 existing + 1 new benchmark) — includes divider persistence, drag, structural update, and layering regression tests.
- Real-app behavior: zoom in/out on an 11-pane workspace via real key events × 20 toggles; zero portal churn in steady state; zero visibility flaps; screenshots above confirm correct maximize, no ghosting, and pixel-perfect restore.
- Divider ratios survive a zoom round-trip (restore re-applies the model ratio; zoom state never persists into divider positions).
Changes
vendor/bonsplit Sources/Bonsplit/Internal/Views/SplitViewContainer.swift— always render the root tree; thread zoom state down as a value.vendor/bonsplit Sources/Bonsplit/Internal/Views/SplitContainerView.swift— collapse-in-place (applyZoomCollapse), synchronous applier registration, zoom guards for divider sync/persistence/entry-animation.vendor/bonsplit Sources/Bonsplit/Internal/Controllers/SplitViewController.swift— zoom-collapse applier registry, notified synchronously onzoomedPaneIdchange.vendor/bonsplit Tests/BonsplitTests/ZoomTogglePerformanceTests.swift— new reproducible window-hosted benchmark (latency + churn).Sources/WorkspaceContentView.swift— removed the.id(zoomState)subtree re-key; zoom-aware pane content visibility.Sources/Workspace.swift— DEBUG-only timing probes (zoom.toggle.begin,zoom.followUp.settled).
Reproduce
# Isolated layout benchmark (before/after by stashing the bonsplit fix)
cd vendor/bonsplit && swift test --filter ZoomTogglePerformanceTests
# Real-app A/B
./scripts/reload.sh --tag layout-zoom-bench # fixed build
# create 10 splits, then drive ⌘⇧↩ cycles and parse /tmp/cmux-debug-<tag>.log
# for zoom.toggle.begin → last split/portal activity per toggle
Report generated by Claude (cmux layout benchmark session). Localization audit: no user-facing strings, shortcuts, or settings were added or changed — rendering-internal changes plus DEBUG-only log lines only.