cmux NEXT: live resize content follows the window edge every frame

Merged
TE
23d ago
+365-73

Summary

Leo, on the latest nightly-next: live resize was smoother, but the content still snapped instead of following the window edge. During a drag, the content now keeps up with the window on every frame.

  • Terminal grid. ResizeCoordinator held each new grid until it had been stable for 3 frames (ResizeSettle). The size changes on every frame of a drag, so the grid trailed the window by 3–4 frames, then jumped. While the window is in a live resize, it now applies after 1 stable frame. At rest it still waits 3.

  • Springs. MotionPolicy now knows about live resize (Motion.isLiveResizing, from NSWindow's live-resize notifications). animatesMovement is false during a drag, so a layout change that lands mid-drag snaps to the next frame's size instead of springing toward a stale one.

  • Tab strip. The edge-fade mask no longer animates when overflow starts mid-drag.

  • Agent transcript. Rows rewrap to new heights when the width changes, and the 180 ms glide used to run on every one of those commits during a drag. It now runs only when the rows themselves change, and any glide already running finishes at once when the rows rewrap.

  • Sidebar and the web view frame: no change needed. Both already followed the window in the same frame.

  • One grid in flight. Mid-drag, a terminal keeps at most one grid waiting for the daemon's echo. A newer size waits in the settle queue until that echo lands, so a fast drag no longer floods the daemon with resizes or overflows its queue.

ResizeCoordinator moves into CmuxNextBridge, next to ResizeSettle, so CmuxNextApp stays under its line cap. The DEBUG per-frame resize trace used during the investigation is not part of this PR.

Testing

  • Frame trace. Measured on cmux-mac-mini-cua-4 (60 Hz) in tagged DEV builds: base 48afd67, fix d82294f. Each build ran the same scene through 4 scripted ±300 pt corner drags.

    basefix
    terminal grid lag behind the window (frames)p50 3, p95 4p50 1, p95 2
    live frames where the grid matches the window5/24091/240
    transcript samples with a glide running234/2380/240
    frame time p50/p95/max16.7 ms, 0 missed16.7 ms, 0 missed
  • Swift LiveResizeTrackingTests:

    • Red on the tests-only ref 5432c313 (aws-m4pro-4): 10 issues, e.g. settled("p1") false, the fade mask animating colors, and no grid sent for 81…85 columns.
    • Green on f12a2f78 (aws-m4pro-8).
  • Web agent-pane-chat-column:

    • Red: run 38515216419 on 178f27b, with 97 glide frames.
    • Green: run 38515841942 on d82294f.
  • One grid in flight, aDragKeepsOneGridInFlightPerTerminal:

    • Red on 2be5710b, which has the test only.
    • Green on 3911899b: 287 passed, and the release compile is green.
  • On the merged head:

    • Swift suites on ed9d94a6: 297 passed, 0 failed, including both live-resize tests.
    • Release compile on 6c1ce64f: green. Only test files changed after it.
  • Other checks: check-concurrency, check-motion, crash_ratchet, check-system-blue, check-no-godfiles (every touched Swift file is line-neutral), bun run check, and ui-rules.

Left over

  • The terminal still trails the window by 1–2 frames. That remaining lag is the daemon round trip (resize, then the resized event, then set_grid).
  • WebKit and CEF composite inside the engine, outside this change.
  • Motion.isLiveResizing is app-wide. A drag in one window also snaps springs and the transcript glide in other windows for its duration. This is cosmetic; making it per window is a possible follow-up.
  • ui.paneAnimations is off by default on base now, so the mid-drag spring snap matters only with pane animations on.
  • A spring already running when a drag starts is not snapped.
  • A reply streaming during a drag can still glide the transcript once between width frames: a new row clears the rewrap guard. Idle chats are covered by the web test.

Review fixes (2e77006): a terminal now keeps the claims still awaiting their echo, so a drag no longer leaves it re-claiming on the next key press; the agent page's motion tokens leave live resize out, so a page loaded mid-drag keeps its move duration; the DEBUG resize trace stops with the probe.

Changelog

Fixed: During a window resize, the terminal, tab strip and agent chat follow the window edge instead of snapping a few frames behind.

Proof

Before and after, the same scripted drag (sidebar, tab strip, a real agent chat, a terminal):

base vs head

Full quality:

base middrag

head middrag

The host name in the terminal prompt and the composer chip is blurred.

šŸ¤– Generated with Claude Code