Skip to content

fix(tui): stop talking to the terminal when nobody is using it - #1

Merged
M0LTE merged 1 commit into
mainfrom
fix/tui-idle-churn
Aug 21, 2026
Merged

fix(tui): stop talking to the terminal when nobody is using it#1
M0LTE merged 1 commit into
mainfrom
fix/tui-idle-churn

Conversation

@M0LTE

@M0LTE M0LTE commented Aug 21, 2026

Copy link
Copy Markdown
Owner

The one item from your list on the 21st that did not make v0.6.0: "the UI seems to have gone laggy after a few minutes". The previous session could not reproduce it in a 90-second check and asked which terminal you were on; that question died with the session, so this picks it up from the measurements instead.

What the tool was doing wrong

Terminal.Gui runs its main loop 25 times a second whether or not anything has changed, and rewrites cursor state every time round. Sitting there with nothing happening at all, the TUI was emitting ~315 bytes a second, in 25 separate writes per second, for as long as it was open. Locally that is invisible. Over SSH it is 25 packets a second the far end can never stop servicing, and a link that is congested never gets a quiet moment to catch up.

The loop now steps down when the tool is left alone, and the first key or mouse event puts it straight back to the library's own rate:

since you last touched it loop rate idle output cost
under 10s library rate (25/s) ~315 B/s none, typing is unaffected
10s to 60s 10/s ~128 B/s waking key measured at 45ms
over 60s 4/s ~54 B/s waking key measured at 60-248ms

Two stages rather than one keeps the cost off the case that would be felt. Ten seconds is far longer than any pause in typing, so a burst of keystrokes never pays anything; only walking away costs the full quarter second, once, on the key that wakes it.

Measured, before and after

Behind a pty that answers the driver's size and cursor queries like a real terminal:

BEFORE - v0.6.0 as released
  typing normally        : [60, 33, 45, 50, 41, 46, 43, 47, 43, 45] ms
  after a 20s pause      : 315 bytes/sec idle, first key 29 ms
  after a 70s pause      : 315 bytes/sec idle, first key 25 ms, next key 2 ms

AFTER - two-tier quiet idle
  typing normally        : [29, 35, 48, 45, 41, 49, 45, 47, 47, 43] ms
  after a 20s pause      : 128 bytes/sec idle, first key 45 ms
  after a 70s pause      :  54 bytes/sec idle, first key 25 ms, next key 2 ms

Four further samples of the waking keypress after 65s idle, which is the only thing this makes worse: 248, 60, 235 and 70 ms, with the keypress after it back to 29-58 ms.

What this is not

A confirmed fix for your lag. It does not reproduce on a local terminal, and I went looking properly rather than for 90 seconds:

  • 12 minutes idle, sampled every minute: keypress latency flat at 44-73 ms, CPU flat at 2.1%, output flat at ~610 B/s, RSS 84 to 102 MB.
  • 150 open-and-close cycles of the channel editor: latency flat at ~60 ms (26 to 77), file descriptors constant at 142, RSS plateaus at ~99 MB. No leak, no accumulating handler, no degradation with use.
  • Mouse motion was already ruled out by the previous session: 2000 synthetic motion events produced no output or CPU amplification.
  • Terminal.Gui 2.4.18-develop.31 behaves identically on the same measurement, so there is nothing to pick up from a version bump, and 2.4.17 is still the newest stable.

So the constant output is what the tool was doing wrong regardless, and it is the only mechanism I can see that could produce "fine at first, laggy after a few minutes" over a link. If it still goes laggy after this, the useful thing to say is which terminal and what connection - SSH, tmux, mosh, Windows Terminal - because the remaining suspects all live there.

Testing

TuiIdlePolicy.RateFor is a pure function of how long it is since the last input, so it is unit tested directly: the rates, both thresholds, that it is monotonic across two minutes of idling, and that it never speeds the loop up past what the library asked for. 66 tests pass. The test project now references the CLI project (with InternalsVisibleTo) to reach it.

Merging this wants a v0.6.1 tag to ship it; the CHANGELOG section is already written for the release notes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FkDFej82QnbjMJYFcwYyAZ

Terminal.Gui runs its main loop 25 times a second whether or not anything has
changed, and rewrites cursor state every time round. Measured on an idle TUI with
nothing happening at all: ~315 bytes/second of escape sequences, in 25 separate
writes per second, for as long as the tool is open. On a local terminal that is
invisible. Over SSH it is 25 packets a second the far end can never stop
servicing, and a link that is congested never gets a quiet moment to catch up.

The loop now steps down when the tool is left alone - 10/s after ten seconds
untouched, 4/s after a minute - and the first key or mouse event puts it straight
back to the library's own rate. Two stages rather than one keeps the cost off the
case that would be noticed: a pause to think slows the waking key by ~45ms, and
only walking away costs the full quarter second.

Measured behind a pty that answers the driver's queries like a real terminal:

  typing normally     29-49ms  ->  29-49ms   (10 keypresses, unchanged)
  idle after 20s      315 B/s  ->  128 B/s
  idle after 70s      315 B/s  ->   54 B/s
  waking keypress        25ms  ->  60-248ms  (4 samples: 248, 60, 235, 70)

This is a candidate fix for "the UI goes laggy after a few minutes", not a
confirmed one, and it is worth being straight about which. The lag does not
reproduce locally: twelve minutes idle held keypress latency flat at 44-73ms, CPU
at 2.1% and file handles constant, and 150 open-and-close cycles of the channel
editor held latency flat at ~60ms with no handle growth either. The constant
output is what the tool was doing wrong regardless of whether it turns out to be
the cause.

Terminal.Gui 2.4.18-develop.31 behaves identically on the same measurement, so
there is nothing to pick up from a version bump.

The policy is a pure function of how long it is since the last input, so it is
unit tested directly: rates, the two thresholds, that it is monotonic, and that it
never speeds the loop up past what the library asked for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FkDFej82QnbjMJYFcwYyAZ
@M0LTE
M0LTE merged commit 5111c6b into main Aug 21, 2026
1 check passed
@M0LTE
M0LTE deleted the fix/tui-idle-churn branch August 21, 2026 11:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant