TerminalGridReportCoalescer: PTY grid at most once per 25 ms, the newest (Ghostty's min_ms rule)

Merged
LA
1d ago
+353-0

Summary

Adds TerminalGridReportCoalescer (CmuxNextTerminal): the PTY size rule of Ghostty.app (Thread.zig min_ms 25). The first new grid opens a 25 ms window and is not sent at once. A later grid in the window replaces it. The newest grid is sent when the window ends.

  • viewSized(_:): opens the window, or replaces the waiting grid.
  • flush(): sends the waiting grid now (not if it equals the last sent grid).
  • reportNow(_:): a forced report, sent at once, even if it equals the last sent grid.
  • cancel(): drops the waiting grid.
  • DemandTimer with an injected Clock. It never sleeps or blocks the main thread. A stale fire after flush() or cancel() sends nothing.

This PR has no caller yet, so the new type is dead code for now; the RESIZE OWNER accepted that for one day. It lands alone so that key resizes (T-KEYS, #36478) and divider drags (T-DRAGS, frame-owner-s2) can both use it. Those PRs then rebase onto it.

Owner note: CmuxNextTerminal is owned by PL-ghostty. This PR adds one new file and changes no existing file.

Observed / Not run

Observed:

  • Fleet step 3c009ace2df88e5202eface1 at 47bbe042 (the same two commits on the older base): all 7 TerminalGridReportCoalescerTests pass (ManualClock).
  • prepush-gate.py at this head: no new violations.

Not run (pending at the time of writing; I add the results here):

  • The compile and the TerminalGridReportCoalescerTests run at this head.
  • The app launch smoke on macOS 14 and 15. The type has no caller, so app behavior does not change.

Changelog

none


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Adds TerminalGridReportCoalescer, applying Ghostty.app's 25 ms min_ms rule so a terminal view sends at most one PTY grid report per window, always the newest. The first change opens the window; later changes replace the waiting grid; the newest is delivered when the window ends. flush() sends the waiting grid immediately, reportNow(_:) forces an immediate send even for a duplicate, and cancel() drops the waiting grid.

The new type has no caller yet, so it is dead code for now; it lands alone so the key-resize (T-KEYS) and divider-drag (T-DRAGS) changes can rebase onto it. One file is added; no existing file changes.