Apply pane zoom by collapsing splits in place instead of swapping the rendered node
Problem
Toggling pane zoom rendered zoomedNode ?? rootNode, which changes SwiftUI structural identity — the entire AppKit split hierarchy (every NSSplitView plus one NSHostingController per pane) was torn down on maximize and fully rebuilt on minimize. Cost scales with pane count: with 8 panes, maximize took 443.8 ms and minimize 1303.4 ms median (debug), and minimize recreated all 8 pane content views. In cmux this shows up as a very laggy cmd+shift+enter with multiple panes open.
Fix
Zoom keeps the tree mounted and collapses the side of each split that does not contain the zoomed pane (hidden arranged subviews are excluded from NSSplitView tiling, so the zoom path expands to the full container).
- Synchronous apply: split coordinators register a collapse applier with
SplitViewController; the AppKit collapse runs in the same runloop turn astogglePaneZoom()instead of waiting for SwiftUI to re-thread state through every nested hosting controller (display-paced, hundreds of ms on deep trees). - Idempotent backstop:
updateNSViewre-applies the same state reading live controller state, so stale SwiftUI passes cannot flash the pre-toggle layout (restore-then-recollapse flap). - Model protection: divider sync/persistence and entry-animation placement are suspended while a split is zoom-collapsed, so the temporary full-size layout never overwrites the stored divider ratio.
Results
New window-hosted benchmark Tests/BonsplitTests/ZoomTogglePerformanceTests.swift (8 panes, 12 cycles, swift test --filter ZoomTogglePerformanceTests):
| metric (median) | before | after |
|---|---|---|
| maximize settle | 443.8 ms | ~170 ms* |
| minimize settle | 1303.4 ms | 3.3 ms |
| pane content NSViews recreated per maximize | 1 | 0 |
| pane content NSViews recreated per minimize | 8 | 0 |
* residual is offscreen-window SwiftUI update scheduling in the headless harness; a probe confirms AppKit frames are final within the toggle call. In cmux (11 panes, real key events) end-to-end settle went from 315→115 ms (maximize) and 825→130 ms (minimize), with terminal-portal detach/attach per minimize going from 18–20 to 0.
Full benchmark report: https://polite-sandal-rgcv.here.now/
All 186 package tests pass.
Note for cmux integration: cmux's WorkspaceContentView re-keyed the Bonsplit subtree with .id(zoomState) (workaround for stale pane chrome above browser portals under the old node-swap rendering). That re-key must be removed together with this change, and pane content visibility made zoom-aware, since collapsed panes now stay mounted; the cmux-side commit is prepared to land with the submodule bump.
🤖 Generated with Claude Code
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.
Summary by cubic
Apply pane zoom by collapsing the non-zoomed side of each split in place, and move divider syncing out of SwiftUI. Keeps the AppKit split tree mounted, makes zoom toggles much faster, and cuts CPU during resize storms.
-
Refactors
- Always render the full tree; hide the side that doesn’t contain the zoomed pane in each split.
- Synchronous collapse:
SplitViewControllernotifies registered split coordinators onzoomedPaneIdchange in the same runloop turn. - Idempotent backstop:
updateNSViewre-applies collapse using live controller state to avoid layout flashes. - Divider syncing: observe the model’s
dividerPositionand apply changes directly toNSSplitView(noupdateNSViewdependency, nolayoutSubtreeIfNeededon ticks); reduces main-thread work under heavy resizes (cmux: app CPU ~93% → ~50–60%). - Suspend divider syncing/persistence and entry animations while zoomed; restore the model ratio on unzoom.
SplitViewContainerpasseszoomedPaneIddown; appliers are registered inmakeNSViewand unregistered indismantleNSView.- Added
ZoomTogglePerformanceTests.swift(8 panes): minimize 1303.4 ms → 3.3 ms; maximize 443.8 ms → ~170 ms; pane content NSView churn → 0.
-
Migration
- In cmux, remove
.id(zoomState)fromWorkspaceContentViewand make pane content visibility zoom-aware, since collapsed panes now remain mounted.
- In cmux, remove
Summary by CodeRabbit
- Bug Fixes
- Improved pane zooming/restoration across nested split layouts by collapsing the non-zoomed side and reliably restoring the previous layout.
- Reduced visual flicker and eliminated unnecessary split hierarchy rebuilding when toggling zoom.
- Kept divider behavior consistent during zoom by suspending/safeguarding divider syncing and position persistence.
- New Features
- Enabled more accurate zoom-aware propagation so nested split containers respond correctly to the current zoomed pane.
- Tests
- Added performance benchmarks for zoom toggle latency and view churn in an 8-pane window scenario.