feat: scrub and play thirty days on the control room - #24
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The control room showed a finished run. It now shows the run happening — a
slider across thirty simulated days, with a play control that replays the whole
thing in twelve seconds while the money accumulates on a curve beneath it.
The ledger already made this possible: it holds every intermediate state rather
than a final summary, and
replay.state_atfolds the stream up to a timestamp.That capability had been sitting there since the ledger was built, unused.
Precomputed, because scrubbing cannot afford a request
state_atis O(events) per call. Fine for one moment; wrong for a slider thatwants two hundred of them faster than a hand can move — that would be 200 full
scans, or 200 round trips, and either makes the control feel like a form rather
than an instrument.
So all 200 frames are folded in one pass at
demotime, written todata/frames.json, and embedded in the page. Scrubbing is an array lookup with nonetwork in it: no request per frame, no loading state, no way for it to feel laggy.
Safe because the run is frozen and deterministic — the same property that lets
every other screen read a precomputed artifact.
Frames are spaced evenly in simulated time, not event volume, so a quiet
stretch still produces frames. Otherwise the slider races through busy periods and
stalls through calm ones, which misrepresents when the money actually came back.
There is a test for that.
56KB → 16KB
The first payload sent thirteen JSON key names per frame — about 45KB of repeated
strings carrying no information, landing on first paint since the data is embedded
rather than fetched. The payload is now a
columnsheader and bare rows.Page-load cost also went from ~400ms to zero: folding happens in
recoup demoalongside the summary and the explanations. If
frames.jsonis absent — an olderrun — the route rebuilds from the ledger, so it degrades to slow rather than to
missing.
Details
The card ships
hiddenand the script reveals it. If JavaScript fails there isno half-working slider, and every other figure on the page is server-rendered
anyway.
Indian grouping is reimplemented in JS to match
recoup/money.py. The scrubbersits directly beneath the scoreboard; ₹5,41,724 above and ₹541,724 below would be
worse than no scrubber.
A test asserts the last frame equals the scored arm — recovered, count,
observed, at-risk, vetoes, contacts, cost. The replay and the headline cannot drift
apart.
No cumulative total may go backwards. A number that dips as you drag is a
folding bug; there is a test over every cumulative field.
open_countis exempt andtested separately — it is a level, not a total, and legitimately falls as payments
resolve.
prefers-reduced-motionhides the play button but keeps the slider. Dragging isthe reader's own choice; autoplay is ours.
Verification
pytest— 502 passed, 15 new.ruff check .clean.Driven in the browser at 1150px: no console errors, no horizontal scroll, 16KB
payload confirmed in the DOM. Scrubbed to frames 0/50/100/150/199 and checked every
figure accumulates correctly with the marker tracking from x=5 to x=1000. Play
started from the end, reset to zero, and reached frame 50 of 200 after 3 seconds —
on pace for the intended 12. Every new colour resolves from tokens in both themes,
read from computed styles rather than judged by eye.