m14 drags: each terminal follows its pane in every frame of a divider drag (delete the drag hold)
Merged
LA
1d ago+293+293+012345678901234567890123456789-84-84-01234567890123456789
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,
restAtmoved 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.terminalson 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.placeSurfaceand the frame owner'splace(_:anchor:)), with the present rule ofmotion.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.previewsViewGridstays true. - The release spring of a drag holds no final size and reports no final grid at its start (
paneMotionWillRestandjoinPaneMotionskiprestAtwhile the drag is followed). A final grid at the start would setgeometry.finaland 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.terminalsoff, 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).
| Run | Ref | Step | Result |
|---|---|---|---|
| red, non-display | 2399b3523bf | 7c4ee94d75b6ddb8d88e290d | DividerDragTerminalSteadyTests 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) | 2399b3523bf | 58d8d7065b215146f8919a23 | TerminalDividerDragFrameTests 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) | 96ad37a03a3 | 807d9ee21deb7bfe0713d1ff | 35 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) | ca64d25e72e | 4de6e21ebb4e7fb530560fff | aSurfaceSwappedInMidDragFollowsTheDrag FAIL: fresh.isFollowingResizeGesture → false |
| green, 36 suites | 216a6d14b74 | 5cf38b4cf9c34221b74af108 | ci-step m14d2-green exited 0: 36 suites, 187 tests |
| green, display (gui-session-v1) | 216a6d14b74 | dd5500c490fe9bed76d6b35e | ci-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 build | 216a6d14b74 | bdb41a9fe9c381e4682715ac (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 byDividerDragTerminalResizeCountTests(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 S3followedHold). 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.terminalson (the default from #36311), a drag shows each size one commit late, through the presenter's "keep" (display stepdd5500c490fe9bed76d6b35e: 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-terminalsat96ad37a03a3against base feat9ac955e: 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 step5d88e49aad86d5579008326d, builddc809e30886d6b7643c3ce94, 0 crashes, 0 hangs); focused suites8b429b07ee626b091b4c0cfcexit 0 (10 suites, 51 tests). - Not run: films (after merge), a tagged dogfood build.