perf(layout): pane content takes its pointer from the screen's one tracking area; a moving pane costs AppKit no cursor-rect walk
Summary
RESIZE MAIN BUDGET (cmux-ad m16 item 5 / m18 SMOOTH), pointer areas PR 1. Each pane-content view that owned an NSTrackingArea or cursor rects (the terminal surface, the tab strip, the hover-reveal probe, the pane toolbar's corner) made AppKit invalidate cursor rects when its pane moved, walk the window's views and rebuild the window's structural regions in that display cycle. A spring or a drag moves panes in every frame, so this ran in every motion frame: 480 to 510 cursor-rect invalidations and 136 re-added tracking areas in one 27-frame key resize of a 2x2 terminal layout.
The fix follows Chrome's model (one native pointer area per window that does not move; the pointer is hit-tested against the current frames). It extends the divider path that already worked this way (ScreenDividerPointer), with no second path:
CmuxNextDesign/PanePointer.swift:PanePointerFollowing(a view the area hit-tests: its cursor, enter, move, exit),PanePointerRegion(a region by geometry: a probe that takes no clicks),PanePointerArea.ScreenDividerPointer: after the divider hover, the content under the pointer hears it come in, move and leave, and shows its cursor unless a divider shows its resize cursor. Leaving content that set a cursor gives it back (the arrow, then the cursor rects around, once). The area is.activeAlways, as the terminal's was.- Inside a screen:
TerminalSurfaceViewhas no tracking area and no cursor rect (its I-beam or Ghostty's shape comes from the area; a shape change sets it at once).TabStripViewhas no tracking area.HoverRevealProbeand the pane toolbar's corner are regions. Outside a screen each view keeps its own area, as before.
Owners told: PL-ghostty (terminal), PL-instant (tabs), PL-motion-core (layout).
Tests
Focused command: scripts/ci/package-test-lane.sh suite Packages/macOS/CmuxNext PaneMotionPointerAreasTests (display worker).
- Red at a7a7b05542 (step a0747e0948fa1ef27f6f04d7):
invalidations=480 [TerminalSurfaceView:480] areas_added=136 [TerminalSurfaceView:68,TabStripView:68](own frames off), 510 and 136 (own frames on). - Green at 2280755ac0f (step cb4fefa2285c92d270f07ce4): passes, with DividerCursorNoRegionRebuildTests, DividerCursorUnderDockTests, PointerHoverRefreshCostTests, TabStripFollowsPaneTickTests, KeyboardResizeContentHoldTests and PaneGroundCoversPaneTests (7 suites passed). The test also checks the cursor over a terminal (I-beam) and over its strip (arrow) before and after a key resize.
Bench (same mini cmux19-perf, 120 Hz virtual display, motion.frameOwner.terminals ON)
Main-thread ms per motion frame, rounds with background daemons over 1 s CPU dropped.
| Row | before p50 / p95 / p99 / max | missed | after p50 / p95 / p99 / max | missed |
|---|---|---|---|---|
| keys | 2.33 / 6.58 / 11.37 / 13.29 | 6 | 1.79 / 5.59 / 10.61 / 11.56 | 4 |
| keys-held | 2.26 / 6.92 / 9.91 / 12.72 | 16 | 2.19 / 6.21 / 9.18 / 13.15 | 11 |
| light (drag) | 3.39 / 6.04 / 7.22 / 10.82 | 1 | 3.07 / 6.30 / 7.21 / 7.77 | 0 |
| stress (drag) | 12.04 / 17.48 / 19.60 / 21.96 | 257 | 5.67 / 9.52 / 11.57 / 13.85 | 49 |
| keys-stress | 10.69 / 14.48 / 15.83 / 16.91 | 65 | 4.54 / 8.03 / 10.26 / 12.09 | 8 |
| keys-stress-held | 12.33 / 15.87 / 16.76 / 32.09 | 280 | 5.10 / 9.59 / 11.15 / 14.62 | 37 |
Jobs: keys, keys-held, light, stress from ba53f9ae (before) / 4e9aca87 (after); keys-stress rows from eec91eb1 (before) / b0a5f31c (after), because mediaanalysisd used 2.6-4.7 s CPU in each keys-stress round of 4e9aca87.
Not yet under 8.33 ms p99: the rest is browser chrome cursor rects (PR 2, stacked on this one), the tab strip layout (SMOOTH-STRIP) and the per-pane layout cost (next PR).
Changelog
none
Observed / Not run
- Observed: the red and green display runs and the bench table above.
- Not run: the full CmuxNextAppTests suite; launch smoke on macOS 14/15 (RESIZE PR WORKER runs it).
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Moves pane content's pointer tracking to each layout screen's single, stationary tracking area, so a moving pane costs AppKit no cursor-rect invalidation or window-region rebuild.
Pane content that owned its own tracking area or cursor rects — the terminal surface, the tab strip, the hover-reveal probe, and the pane toolbar's corner — made AppKit invalidate cursor rects, walk the window's views, and rebuild structural regions in every motion frame. Now the screen's one tracking area (ScreenDividerPointer, already the divider hover path) hit-tests the current frames and delivers enter/move/exit and the cursor through the new PanePointerFollowing and PanePointerRegion protocols; the area stays .activeAlways as the terminal's was. Views outside a layout screen keep their own tracking areas and cursor rects as before, and the terminal's cursor still updates immediately on shape change.
Benchmarks
- Main-thread milliseconds per motion frame (120 Hz,
motion.frameOwner.terminalson): stress drag p50 12.04→5.67, keys-stress p50 10.69→4.54; missed frames drop 257→49 (stress) and 65→8 (keys-stress). PaneMotionPointerAreasTestscounts zero cursor-rect invalidations, tracking-area additions, and drag-margin rebuilds across a key-resize spring; on the base commit those counts were 480/510 and 136.