Restyle: redundant encoding, density, drill-down IA, and an honest website - #525
Merged
Conversation
PHASE 1 of the restyle. Two pieces of groundwork the seven remaining
directions depend on.
TOKENS. Twelve values are numerically identical across the app and the website.
Six are one brand fact spelled twice and are now marked SPINE on both sides,
with the reason no build-time single source is reachable: the Vite-Tailwind and
Astro-Tailwind pipelines share no build step. 60-30-10 is written out as
explicit section labels so a new saturated colour is a visible decision rather
than a quiet addition.
One pair was a COINCIDENCE, not a fact: the app's --warn ("reconnecting,
degraded, near clip") and the website's --color-cross ("active crosspoint
ONLY") had landed on the same literal. Unrelated meanings, and folding them is
the mistake -- a designer retuning a crosspoint would move an operator's
degraded-stream colour. Marked NOT SPINE on the website side. An earlier pass
also nudged the hue by 1 degree to make them numerically distinct; that is
reverted, because changing a visual value to encode a non-visual fact does not
survive a rounding pass and spends a real colour on a comment's job.
DEMO SEED. scripts/demo-seed.sh brings up three live programmes on one SRT
port, a 1080p/720p/480p ladder with destinations on different rungs, thirteen
destinations across six kinds, live audio with moving envelopes, and five real
multitrack recordings. Screenshots of an empty install cannot show what this
product is for, and the two bugs in #514 and #515 are invisible without it.
Every URL is .invalid and every key is obviously fake; the script runs gitleaks
on its own fixture and refuses if it fires. Platform destinations are created
DISABLED on purpose -- enabled they sit in failed/reconnecting and photograph
as a broken product.
Claude-Session: https://claude.ai/code/session_01A8N3W5ct9SZtHK9sCDD9cL
PHASE 2 of the restyle. Seven directions across the two surfaces. APP. Nothing that matters is signalled by colour alone any more. toneMark in lib/signal.ts gives every state a silhouette that survives greyscale -- live, warn, down, armed and idle each get their own -- and the guard asserts the real property rather than a literal: strip every colour-only class and no two tones may render the same mark. The meters gain three channels, one of which was a plain bug: the zone-break ticks were drawn UNDER the bar, so a hot signal covered the very mark saying where hot starts. Density is one line of mechanism. Tailwind v4 compiles spacing to calc(var(--spacing)*n), so rescaling that variable rescales everything; scoped to main so chrome and Radix portals stay comfortable, type left alone, and controls floored in literal rem so a 21px Stop button cannot happen. An OnAirBar answers "what is on air, and what is wrong" above the dashboard, with a fault list that is EMPTY when nothing is wrong rather than a row of reassurances. Across-the-room mode reuses the same silhouettes at 32px. WEBSITE. The hero showed an uncopyable grey strip whose docker run had drifted from README: no --name, no :latest, and no 1935, so a visitor pasting it got a container with no RTMP ingest and nothing saying why. It is now the real command, byte-for-byte, keeping the /udp note that explains the classic silent ingest failure. make build appeared nowhere on the site at all. The primary call to action is now "Read the quickstart" rather than "Install it": for self-hosted infrastructure the documentation IS the conversion path, because the visitor is deciding whether they can operate this thing. The comparison page gains the loss tables verbatim, including that Restreamer is more mature and you should probably use it if you do not need per-destination audio. That candour is the asset and is not softened. No new colour on either side; the token spine is untouched. tsc -b clean, vitest 614/614. Claude-Session: https://claude.ai/code/session_01A8N3W5ct9SZtHK9sCDD9cL
…etween programmes Both found by photographing a seeded three-programme install, and neither is visible on the single-source box every developer runs. BITRATE WAS BLANK ON EVERY HEALTHY SRT INSTALL. The card read ingest.progress.bitrateKbps -- the ingest PROCESS's number -- and for SRT there is no ingest process: reconcileIngest returns early on purpose, because srtserver delivers datagrams straight into the hub and a second thing on that socket would crash-loop. So the most prominent number on the dashboard printed "—" forever on the primary operated ingest path. Same root as the "Offline" badge in #514, fixed there and left behind one line below in the same card. The fallback is not an estimate. It is the same relay byte series useIngestLive already trusts to decide whether a broadcast is going out at all, so the card now shows the bytes that made it say "Live" one line above. The process is still preferred where it exists, because on RTMP or a pull ingest that number is measured at the ingest and the relay series is one hop later. It returns nothing rather than 0 when nothing is arriving: a printed 0 claims a running, empty stream. THE DESTINATION LIST SHOWED ONE PROGRAMME AT A TIME. The status socket is install-wide and every engine publishes onto it, so the app kept the last snapshot and the list -- which is the whole install on one page, grouped by programme -- rendered a heading for each and the destinations of whichever engine spoke most recently: 9, then 2, then 2, every two seconds. Hidden until Engine.Status was scoped to its own source, because the default engine's snapshot used to carry every row on the machine. Fixed at the consumer. publishStatus fires per process transition and is deliberately synchronous: coalescing it handed a destination a backwards decode timestamp at a failover switch in 3 runs out of 10, and a platform drops the connection on a backwards DTS. Asking every engine for its status on that path would multiply a database read per transition to buy what the fold does free. The fold keys on BOTH the programme a snapshot names and the rows it carries. Naming alone was the first attempt and it was wrong: two shapes arrive on this socket -- statusPayload sends the whole install, publishStatus sends one programme -- and both label themselves with a single source.id, so the install-wide one kept the other programmes' rows and re-added them. 13 destinations rendered as 17, which is how it was caught. UPTIME is left as "—" for SRT. It has the same cause and no fix available here: relay.Stats carries byte and loss counters and no notion of when bytes first arrived, so it needs a server-side field and a decision about whether a reconnect gap resets it. Claude-Session: https://claude.ai/code/session_01A8N3W5ct9SZtHK9sCDD9cL
…mme install The committed set photographed an install with one source, no destinations and no recordings, because that is what every box available to point the capture at looked like. capture.spec.ts drives a real browser and waits for real state before each shot, which is the right design and is worth nothing aimed at an empty product. These are taken against scripts/demo-seed.sh: three programmes on one SRT port, a 1080p/720p/480p ladder with destinations on different rungs, thirteen destinations across six kinds, live multitrack audio and a recording library. Every URL is under .invalid and every key is obviously fake. The install is left streaming for several minutes before the shutter rather than the 25 seconds the seed needs to settle, so destination uptimes and speeds read as a broadcast in progress instead of one that started moments ago. 18-destinations.png is new: the whole destination area, grouped by programme, which is the only shot that shows what this product is for -- six platforms, each on its own rendition rung, each taking a different subset of the audio tracks. Retaking these found three defects, none of which any test caught and all of which needed more than one programme to be visible at all: destinations compiled against the wrong programme's track layout, a dashboard reporting OFFLINE over three live streams, and a destination list that flipped between programmes every two seconds. The captures are the reason the product is now right, not just the record of it. Claude-Session: https://claude.ai/code/session_01A8N3W5ct9SZtHK9sCDD9cL
|
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.


All nine style directions, plus the demo-seed harness they needed and a full screenshot recapture.
App
Nothing that matters is signalled by colour alone.
toneMarkgives every state a silhouette that survives greyscale, and the guard asserts the real property rather than a literal: strip every colour-only class and no two tones may render the same mark. The meters gained three non-colour channels — one of which was a plain bug, the zone-break ticks were drawn under the bar, so a hot signal covered the very mark saying where hot starts.Density is one line of mechanism: Tailwind v4 compiles spacing to
calc(var(--spacing)*n), so rescaling that variable rescales everything. Scoped tomainso chrome and Radix portals stay comfortable; controls floored in literal rem so a 21px Stop button can't happen.An OnAirBar answers "what is on air, and what is wrong" with a fault list that is empty when nothing is wrong, rather than a row of reassurances. Across-the-room mode reuses the same silhouettes at 32px.
Website
The hero's
docker runhad drifted from README — no--name, no:latest, and no-p 1935:1935, so a visitor pasting it got a container with no RTMP ingest and nothing saying why. Now byte-identical, keeping the/udpnote that explains the classic silent-ingest failure.make buildappeared nowhere on the site at all.The primary CTA is now "Read the quickstart", not "Install it" — for self-hosted infrastructure the documentation is the conversion path. The comparison page gains the loss tables verbatim, including that Restreamer is more mature and you should probably use it if you don't need per-destination audio. That candour is the asset and is not softened.
No new colour on either surface; the token spine is untouched.
Tokens
Twelve values were numerically identical across the two surfaces. Six are one brand fact spelled twice and are marked SPINE on both sides. One pair was a coincidence — the app's
--warnand the website's--color-cross— and folding them would move an operator's degraded-stream colour because a designer retuned a crosspoint. Marked NOT SPINE.What the screenshots found
Retaking them surfaced three defects, none caught by any test, all needing more than one programme to be visible: destinations compiled against the wrong programme's layout (#515), a dashboard reading OFFLINE over three live streams (#514), and a destination list flipping between programmes every two seconds (#524). The captures are why the product is now right, not just the record of it.
Depends on #524 for the last of those.
Verified:
tsc -bclean, vitest 625/625,npm run buildon both.https://claude.ai/code/session_01A8N3W5ct9SZtHK9sCDD9cL