feat: show the input name and downtime on the split-flap board (#154) - #270
Merged
Conversation
The board cycled five hardcoded word triples. It now opens on what the operator actually wants to know -- which input this is, that it has no signal, and how long it has been gone: HDMI 01 / NO SIGNAL / DOWN 04:31 ## The contract decision Option 3 of the three #154 weighed: the renderer pushes rows in via a new setMessages(), and the board stays a dumb renderer of whatever it is handed. All knowledge of inputs, sides and downtime stays in renderer.js. Chosen over passing app state into create() because this board is not a registry entry -- renderer.js imports it directly -- so nothing had to change about the saver contract to do it. The weather saver (#101) answered the same question the other way for a different reason: it needs state the renderer does not own, so it reads a cached value from its own module. Here the renderer owns everything, so pushing is strictly less coupling. Rows are applied on the board's NEXT message change rather than immediately, so a push never interrupts a flap in progress. With a single-entry list that means the board re-flaps the same rows each hold period, which is what makes a ticking clock read as a mechanism rather than a redraw. ## Not widening the grid MIN_COLS stays measured over the built-in messages only, and live text is truncated to fit rather than growing the grid. Growing `cols` at runtime would mean reallocating the tile arrays and the state texture mid-flight; #154 explicitly allows truncation, so the cheap option is also the sanctioned one. The budget, computed from the board's own formula: dual view, one side 3000x1200 16 columns single view, full wall 6000x1200 22 columns So an input named longer than that truncates -- "BLACKMAGIC ULTR." -- and renaming it in settings is the fix. Truncation ends in a period rather than clipping silently, because silent clipping is the exact failure #92 fixed: "NO SIGNAL" rendered as "NO SIGNA" reads as a broken renderer, not as an abbreviation. ## Two things that would have failed quietly **A repeated showNoSignal must not restart the clock.** It is idempotent and is called again for a synced same-device pair and on every re-entry, so restarting would peg the downtime line near zero forever -- looking like it works while never advancing. The clock is set only on the transition in, and cleared on the way out. **A malformed push must not blank the board.** setMessages validates and falls back to the built-in list, so a renderer with nothing to say leaves the static messages rather than a grid of spaces. ## Verification 23 new tests, 595 -> 618, each invariant confirmed by breaking it: restarting the clock on every call fails 1, clipping without a marker fails 1, dropping the validation fails 1. Driven in a browser with two sides down, one backdated four minutes and the other two hours: both boards started, both received live rows, and the clock advanced across the run. The fitted output at the real column count was "HDMI 01 / NO SIGNAL / DOWN 06:05" and "BLACKMAGIC ULTR. / NO SIGNAL / DOWN 2H 15M". Not verified: the flaps settling on that text. The Browser pane throttles requestAnimationFrame, so the board crawls mid-flip and never lands -- the screenshots show it animating toward the new rows rather than displaying them. Rendering arbitrary strings through this path has been shipping since #147.
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.
Closes #154.
The board cycled five hardcoded word triples. It now opens on what the operator actually
wants to know:
The contract decision, which #154 said was the real work
Option 3 of the three it weighed: the renderer pushes rows in via a new
setMessages(),and the board stays a dumb renderer of whatever it is handed. All knowledge of inputs, sides
and downtime stays in
renderer.js.Chosen over passing app state into
create()because this board is not a registry entry —renderer.jsimports it directly — so nothing had to change about the saver contract to do it.The weather saver (#101) answered the same question the other way for a different reason: it
needs state the renderer does not own, so it reads a cached value from its own module. Here the
renderer owns everything, so pushing is strictly less coupling.
Rows apply on the board's next message change, not immediately, so a push never interrupts
a flap in progress. With a single-entry list the board re-flaps the same rows each hold period —
which is what makes a ticking clock read as a mechanism rather than a redraw.
Truncation, not a wider grid
MIN_COLSstays measured over the built-in messages only. Growingcolsat runtime would meanreallocating the tile arrays and the state texture mid-flight; #154 explicitly allows
truncation, so the cheap option is also the sanctioned one.
Computed from the board's own formula:
An input named longer than that truncates —
BLACKMAGIC ULTR.— and renaming it in settings isthe fix. The cut ends in a period rather than clipping silently, because silent clipping is the
exact failure #92 fixed:
NO SIGNALrendered asNO SIGNAreads as a broken renderer, not asan abbreviation.
The clock has three forms so it reads correctly at both ends —
DOWN 04:31,DOWN 2H 14M,DOWN 3D 04H. The colon form only ever means minutes and seconds, so it is never ambiguousagainst the hour form. A test asserts every character it can emit is in
FLAP_CHARS.Two things that would have failed quietly
A repeated
showNoSignalmust not restart the clock. It is idempotent and gets called againfor a synced same-device pair and on every re-entry, so restarting would peg the downtime line
near zero forever — looking like it works while never advancing.
A malformed push must not blank the board.
setMessagesvalidates and falls back to thebuilt-in list, so a renderer with nothing to say leaves the static messages rather than a grid
of spaces.
Tests
23 new, 595 → 618. Each invariant confirmed by breaking it:
showNoSignalVerified, and not
Driven in a browser with both sides down, one backdated four minutes and the other two hours.
Both boards started, both took live rows, and the clock advanced across the run. Fitted output
at the real column count:
HDMI 01 / NO SIGNAL / DOWN 06:05andBLACKMAGIC ULTR. / NO SIGNAL / DOWN 2H 15M.Not verified: the flaps settling on that text. The Browser pane throttles
requestAnimationFrame, so the board crawls mid-flip and never lands — the screenshots show itanimating toward the new rows rather than displaying them. Rendering arbitrary strings through
this path has been shipping since #147, and only the strings changed here.
One correction worth recording: my first in-page column derivation used
TILE_RATIO = 0.72dividing, when it is
1.45multiplying. Both produced 16 for that canvas becauseMIN_COLSbound either way, so the fitted strings above are right — but the table above comes from the
real constant, not that one.