Skip to content

fix: draw a frame only when there is something new to show - #104

Merged
vyncint merged 1 commit into
mainfrom
idle-repaints
Sep 8, 2026
Merged

fix: draw a frame only when there is something new to show#104
vyncint merged 1 commit into
mainfrom
idle-repaints

Conversation

@vyncint

@vyncint vyncint commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Closes #102.

The bug

app.redraw gated only the full clear, not the draw itself, so the loop bracketed and drew on every 80 ms tick regardless of whether anything had changed. An idle chart put a DEC 2026 open/close pair on the wire ~12.5 times a second, forever, for a picture that was not moving.

Measured: 244 repaints over a 20-second idle wait, against 2 immediately after load. Now: 0.

The fix

Draw when something changed. Three sources:

  • input — a key, a mouse report, a resize;
  • the full-clear flagapp.redraw implies a draw, or the clear would blank the screen with nothing to repaint it;
  • a fetch landing — the one model change no event announces, so it is detected rather than reported, by watching Load::Loading leave.

The spinner is the exception that shapes the guard

ui::loading() turns off app.tick with no event behind it, so a naive "draw only on change" would freeze it mid-fetch. The guard is dirty || matches!(app.load, Load::Loading) for exactly that reason, and tick still advances every iteration so it turns at the rate it always did. This was the case I flagged as deciding whether #102 was a one-line guard or a real change — it is why the condition has two halves.

Four tests had to move, and three of them get better for it

Three used "wait for N more repaints" to mean "give it time to have acted, then check nothing did" — an idiom the free-running timer was propping up. They now wait for the picture to hold still (wait_stable), which is both what they actually meant and what survives the app getting quieter still.

The fourth is the interesting one. an_idle_chart_stops_writing counted printable characters in idle frames and asserted they were zero — the next best thing available while the frames could not be stopped. Its claim is now strictly stronger: there are no idle frames. It asserts that, and that a keystroke still draws, so "idle" cannot be confused with "wedged".

Known and accepted

today() reads the clock when --today is absent, so a chart left open across midnight no longer self-corrects on the next tick. Any key fixes it, and the alternative is a wakeup every 80 ms for the life of the process. Recorded here rather than discovered later.

240 tests pass; stress dispatched against this branch.

The loop bracketed and drew on every 80ms tick regardless of whether
anything had changed — `app.redraw` gated only the full clear above it, not
the draw itself. An idle chart therefore put a DEC 2026 open/close pair on
the wire about twelve times a second, forever, for a picture that was not
moving: 244 repaints over a 20-second idle wait against 2 after load.

It draws when something changed instead. Three sources: input (a key, a
mouse report, a resize), the full-clear flag, and a fetch landing — which
is the one model change no event announces, so it is detected rather than
reported, by watching `Load::Loading` leave.

The spinner is the exception that shapes the rest. It turns off `app.tick`
with no event behind it, so a fetch in flight keeps the loop painting; the
guard is `dirty || matches!(app.load, Load::Loading)` for exactly that
reason. `tick` still advances every iteration, so it turns at the rate it
always did.

Three tests used "wait for N more repaints" to mean "give it time to have
acted, then check nothing did" — an idiom the free-running timer was
propping up. They now wait for the picture to hold still, which is both
what they meant and what survives the app getting quieter. The fourth,
`an_idle_chart_stops_writing`, gets a stronger claim than it had: it
counted printable characters in idle frames because it could not stop the
frames; now it asserts there are none, and that a keystroke still draws.

Known and accepted: `today()` reads the clock when `--today` is absent, so
a chart left open across midnight no longer self-corrects on the next tick.
Any key fixes it, and the alternative is a wakeup every 80ms for the rest
of the process's life.

Closes #102

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint
vyncint merged commit 0ae213a into main Sep 8, 2026
23 checks passed
@vyncint vyncint mentioned this pull request Sep 8, 2026
6 tasks
vyncint added a commit that referenced this pull request Sep 8, 2026
Patch release: the idle-repaint fix (#102/#104) and the termlens 0.10.1 upgrade with its two new suites (#103). cargo-semver-checks against 0.8.0 reports no semver update required. Gated on stress green at 10/10 on main.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
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.

The event loop repaints every 80ms tick whether or not anything changed

1 participant