Eleven components, graded by how loudly the product should say them rather than by what caused them. Almost nothing blocks. Nothing blames the reader. Nothing names the system. And every state above the quietest rung says what to do now — a state that only describes itself is unfinished.
/splash is a real route.Not a spinner on it, not a progress bar under it, and no message. The product holds its own identity until there is something to replace it with. An earlier draft of the spec said the shell is always there and was contradicted by the first screen of every session.
Nothing is wrong with your house is doing the work. Sam is anxious and high-stake, and the first thing every one of Sam's states answers is am I alright. A sentence naming a network or a server answers a question nobody asked.
A human moment and what it means for you. “As of Tuesday evening”, never “Last updated 14:32” — the second is the system talking about itself, and it is the single most likely regression in this file.
At the top of the thing that is stale, never per card. A stale screen that says so on every card is a screen of warnings. And stale content is never hidden: the alternative to showing old data is showing none, which is strictly worse in a house with no signal.
Looking at an old allowance costs nothing. Deciding against one does. This is the general rule that an action on the surface raises the rung, and it is why the ladder is a test rather than a lookup table.
A shimmer is an animation applied to a delay to make it feel shorter. It makes a slow screen look busy and a fast one flicker. Anything wearing this class is already known to be slow, and a slow screen does not need to sparkle.
3 of 12 loaded is a denominator and is banned for the same reason a money fill is. A loading state is the likeliest place for a progress readout to come back, which is why the ban is restated here rather than only where the money is.
An observation has no counterparty and cannot conflict — two people photographing the same wall both produce valid records, and a late-syncing photo is never wrong, only late. So it appears immediately, marked, rather than sitting in a queue with a status on it.
hc.css already spends .pending on the diagonal hatch meaning nobody has chosen this color, which is absence rather than waiting. Two states one word apart on the same screen is how a reader learns the wrong one.
If they do, the good news reads as an unfinished screen for the rest of the product's life — and on a surface built for anxious people, an unfinished screen is read as something wrong with the house.
Because it is a shape rather than a message: the section header stays, the rows do not, and nothing writes an encouraging sentence into the hole. 09-first-run settled this and it stands unchanged. Year two is the one that needed inventing.
An empty container is removed. An empty container that will fill is kept and dated. It says when, not what. Writing nothing here yet in it collapses it into day one and throws away the one useful fact.
Because nothing here is an object. A hairline says this slot exists and will fill without claiming there is already something in it, which is exactly what a not-yet state means. The date line is the only amber thing on it, and it is a mark naming the kind rather than a panel the reader has to decode.
Absence teaches nothing and reads as broken — an admin who cannot find the role control concludes the app has lost it. Inert, with the reason, teaches the permission model once and never needs a settings page to explain it.
An alpha over a whole control drops its label below AA and reads as a rendering fault rather than a decision. The ink moves to the muted tone and the control keeps its shape. Drawn here in its reached state; at rest the sentence is closed.
Dave is not anxious, he is interrupted, and he is coordinating five people in the physical world. He needs the clock. Sam mostly does not. Everything else about the two registers is the same components saying the same things.
Send it now, not retry. An error that can retry itself already has; this one is waiting on him because it left his phone and never landed, and the sentence names who has not seen it rather than what failed.
Telling a homeowner with no signal that their house does not exist. That is what merging these two produces, and it is why not found is a state of its own rather than a variant of something broke — nothing broke.
Not you typed the wrong address, not you should have opened this earlier. Never blame the user, never name the system: the two rules that stop the ladder collapsing back into a cause taxonomy the first time something is urgent.
30-the-link-states draws the whole set; this frame is here so the template is met beside its siblings.Superseded says this page is wrong — do not act on it, and there may be nothing to go to. Replaced says this page is out of date and always points forward. A reader who acts on a superseded page has done damage; one who acts on a replaced page has wasted a trip.
Before anything that has to be fetched. A stranger must be able to tell whose message this is while the rest is still arriving — they know Dave, they do not know us, and a page opening on our logo alone reads as smishing.
One line, where it is true, never again. That escalation-then-silence is how no nag survives a state that persists indefinitely — a polite rung-2 banner on every screen for the rest of the account's life is a nag by duration.
The same move the offline bar makes. Everything is still here is the first sentence because it is the one the reader is afraid of, and because a lapsed builder's clients are holding live links he did not withdraw.
A lapsed account is a permission model with one rule in it, so it reuses the component rather than inventing a disabled state. The button is where it always was, quiet, and says why when he reaches for it.
There is no .error class, at any rung, and there is no way to spell a status code with this vocabulary. Cause never organizes anything and never surfaces — no sync failed, no exception text, no system voice. That is the rule that stops the ladder degenerating back into the cause taxonomy the first time something feels urgent.
There is no red rung. Volume is carried by layout — flat, versus interrupting, versus alone on the screen. --terracotta means a genuine clash and it is spent exactly once here, on superseded, which is the one state where the page in front of the reader contradicts the record. Coloring no signal as an error blames the reader for the building.
There is no spinner and no modal. Under about a second the screen arrives; above it, the shape of what is coming. A dialog stops the reader to say something they could have read while still looking at their house.
Rung 0 has no class and must not get one. The undecided kitchen renders gray because --r-wall is gray; an absent section is absent; a live link is just the page. If a page finds itself reaching for a .rung0, it has found a rung-1 state and mis-rated it.
Three questions, in order, and the first yes wins. Would proceeding without knowing this cause harm? → rung 3, and almost nothing qualifies. Does the reader need this to decide what to do next? → rung 2. Can the reader act on it at all? → if no, rung 1 — telling someone loudly about something they cannot change is anxiety with no payload. If none apply it is rung 0 and the data says it by itself.
Two corollaries the frames above draw. The same state takes different rungs by approach — a removed decision is rung 1 noticed in a list and rung 2 followed to directly. An action on the surface raises the rung, which is frames 3 and 4.
And a test for whether a candidate state is real at all: it is legitimate if it changes what the reader should do next. If it only changes what an engineer would investigate, it is a cause and it does not reach a screen. Connection reset, timeout and 500 all fail that test — the reader does the same thing in every case, so they are one state with three causes behind it.