m14 drags: each terminal follows its pane in every frame of a divider drag (delete the drag hold)

Merged
LA
1d ago
+293-84

cmux-ad m14 and m18 (Lawrence's P0, "resizing splits/columns, still not animating the terminals"): in every frame of a divider or column-edge drag, and of the spring its release starts, each terminal takes that frame's pane size and its text re-wraps at that size. The program in it gets its new size when the drag lands.

Why

  • The drag hold (Debug tunable terminalDrag.holdSurface, RESIZE VIDEOS A3, on by default) kept each terminal's surface at its drag-start size for the whole drag: cut where the pane was narrower, the terminal background beside it where the pane was wider.
  • At the release spring's start, restAt moved the hold to the final size, so the release spring showed the final grid, not the pane's size.
  • pixel-perfect's trace (STATUS 13764): 157-159 held ticks in every col-drag-2 film; the surface was up to 150 pt off its pane. That was so with motion.frameOwner.terminals on or off.

Fix

  • Delete TerminalDragTunables.holdsSurfaceDuringDrag (terminalDrag.holdSurface) and its path (TerminalSurfaceView+PaneMotion.swift).
  • At the drag's first frame each terminal leaves a spring hold the drag caught (a split, a close or a press still landing: endPaneMotion()).
  • From there the surface takes its pane's size in every frame through the placement every resize uses (TerminalHostView.placeSurface and the frame owner's place(_:anchor:)), with the present rule of motion.frameOwner.terminals:
    • off: the bounds change at once, and Ghostty's frame of that size lands 1-4 refreshes later;
    • on: ready-or-keep, through TerminalFramePresenter (frame-owner-s4's T-KEYS changes "keep").
  • The reflow preview (Ghostty's render-only re-wrap, no SIGWINCH) re-wraps the text at each size. During a drag no final grid is reported, so TerminalGridPolicy.previewsViewGrid stays true.
  • The release spring of a drag holds no final size and reports no final grid at its start (paneMotionWillRest and joinPaneMotion skip restAt while the drag is followed). A final grid at the start would set geometry.final and turn the preview off for the spring.
  • The PTY still gets one grid per drag, when the release lands, as before. frame-owner-s4's T-KEYS PR puts a 25 ms coalescer (Ghostty.app's rule) in the common report path; drags then use it with no change here.
  • stopFollowingResizeGesture() has no drag hold to release any more.

No present-mode override for drags: the drag follows the switch, which T-KEYS turns on by default (m18).

Changelog

  • Improved: While you drag a divider or a column edge, each terminal resizes with its pane and wraps its text to the new width in every frame.

Frames

  • Frames: every terminal in the panes a divider or column-edge drag resizes, in every drag frame and every frame of the release spring.
    • Before: the surface kept its drag-start size, cut or beside the terminal background, until the release spring, which held the final size.
    • After: the surface has its pane's size in every frame and its text re-wraps there. With motion.frameOwner.terminals off, Ghostty's frame of each size shows 1-4 refreshes after the bounds (top-left, not stretched); with it on, the frame owner's ready-or-keep.
  • Frames: a drag that catches a split's or a close's spring: the terminals in it leave the spring's final-size hold at the drag's first frame.
  • No content stops following its pane.

Tests

Red first (2399b3523bf, tests only, on feat 9ac955e274c), then the fix (96ad37a03a3); a second red/green pair for Codex round 1's blocker (ca64d25e72e test, b78df0c4f55 fix). The head merges feat (216a6d14b74).

RunRefStepResult
red, non-display2399b3523bf7c4ee94d75b6ddb8d88e290dDividerDragTerminalSteadyTests FAIL: p1's surface took 1 width in 30 drag steps (widths.count → 1), "drag step 1: p1's surface (600.0, 800.0) in a (592.0, 800.0) area" ...; the swap test: the surface held at 600 pt. DividerDragTerminalResizeCountTests passes.
red, display (gui-session-v1)2399b3523bf58d8d7065b215146f8919a23TerminalDividerDragFrameTests FAIL, both switch values: "new sizes in drag frames [0, 0]" of 20; frames behind their pane [20, 20] (off), [21, 21] (on)
green, 36 suites (first fix)96ad37a03a3807d9ee21deb7bfe0713d1ff35 suites pass; SplitSpringMotionTests.fiveRapidSplitsAreOneMotion fails on its 8.33 ms frame-time bound (28.1 ms), a timing test with no drag, the same test that failed at #35940's head (2ff367f37c3d6070185f8309, 11.66 ms)
red: a surface swapped in mid-drag (Codex round 1)ca64d25e72e4de6e21ebb4e7fb530560fffaSurfaceSwappedInMidDragFollowsTheDrag FAIL: fresh.isFollowingResizeGesture → false
green, 36 suites216a6d14b745cf38b4cf9c34221b74af108ci-step m14d2-green exited 0: 36 suites, 187 tests
green, display (gui-session-v1)216a6d14b74dd5500c490fe9bed76d6b35eci-step m14d2-display-216a6d14b74 exited 0. TerminalDividerDragFrameTests: switch off, new sizes in drag frames [20, 20] of 20, 0 frames behind their pane; switch on, [17, 17] of 20, 20 frames behind (the presenter's keep path: each size shows one commit later; T-KEYS deletes "keep"); misplaced 0, unsized 0, stretched 0, PTY grids [1, 1]
app build216a6d14b74bdb41a9fe9c381e4682715ac (tag m14-drags-v1)done

Observed / Not run

  • Observed: the fleet runs above, the prepush gate, check-no-godfiles (TerminalHostView.swift kept at its base line count), check-motion, the package conventions lint.
  • Codex round 1 at b72c7e2d3fa: FIX. (1) blocker: a surface swapped in mid-drag did not follow the drag; fixed red-first (ca64d25e72e, b78df0c4f55): the host follows the drag and a surface joins its host's drag. (5) should-fix: the display test pins the switch before each frame; fixed. (4) The PTY count is asserted by DividerDragTerminalResizeCountTests (one grid per drag, none mid-drag; unchanged and green); this PR keeps that rule until T-KEYS' 25 ms coalescer. (2) A forced report (a terminal shown mid-drag) still sends at once, as before this PR. (3) Not verified: a drag that catches a running key-resize hold (FRAME OWNER S3 followedHold). With the switch off the host's follow rule may keep the key hold's size until that hold ends; no test here covers a drag during a key spring.
  • Known effect, not this PR: with motion.frameOwner.terminals on (the default from #36311), a drag shows each size one commit late, through the presenter's "keep" (display step dd5500c490fe9bed76d6b35e: 17 of 20 drag frames take a new size, 20 frames one commit behind the pane). frame-owner-s4's T-KEYS PR deletes "keep". With the switch off: 20 of 20, 0 behind.
  • Observed (pixel-perfect film, reported to the RESIZE OWNER; tables in STATUS 14353): px-col-drag-2-sw-terminals at 96ad37a03a3 against base feat 9ac955e: held terminal frames 155-161 at the base, 0 at the head. One terminal edge frame per film at the release (p0's high side cut by 102 px or more). No other new red.
  • Observed at head 9d5718797f84a02bed13ebd4b57d527e84e449d5 (after the feat merge): launch smoke PASS on macOS 14.8.7 and 15.7.7 (os-matrix step 5d88e49aad86d5579008326d, build dc809e30886d6b7643c3ce94, 0 crashes, 0 hangs); focused suites 8b429b07ee626b091b4c0cfc exit 0 (10 suites, 51 tests).
  • Not run: films (after merge), a tagged dogfood build.