Fixtures and statesThe data nobody demos with

Every screen works
until the data arrives.

Demo data is polite. Names fit the column, everyone has a salary, and every record is in a state somebody thought about. A real roster is none of those things, and the difference is where an interface stops being a picture.

The demo setEverything fits
Amina Berrada$42,000.00Offer sent
Tomás Novak$51,500.00Interviewing
Lena Fischer$38,900.00Offer sent
Committed salary$132,400.00
The fixtureNothing fits
Maria-Alejandra Fernández-Villalobos$96,750.00Notice served
withheld by requestno salary setRescinded
Joy Okonkwo-$2,400.00Clawback
Committed salarynot derivable
Hostile valuesChosen to break something specific

A fixture is not sample data. It is an argument.

Sample data exists to make a screen look finished. A fixture exists to find out whether it is. Each value below is in the set because it broke something, and it stays because it would break it again.

01How shells are handled →
The valueWhat it exposesWhat the rule says
Maria-Alejandra Fernández-Villalobos
A 38-character name in a column measured against three short ones. Either it is cut without warning, or it shoves the salary off the row.
A cell states its own overflow. Never a silent cut.
name: null
A record withheld by request. The avatar has no photo and no initials to fall back to, so most components render an empty circle and move on.
Absent is a value with an appearance. Not a blank.
salary: null
The figure that has genuinely not been set yet, rendered as 0.00 — which is a different fact, and a worse one.
Unset and zero are drawn differently. Always.
-$2,400.00
A clawback that reads as a payment, because the only thing separating them is a minus sign eight pixels wide.
Direction is legible without the sign. Colour is not enough.
status: "rescinded"
A state the mockup never had, arriving from the applicant tracking system on a Tuesday.
Every value in the enum has an appearance. The set is closed.
0 rows
The roster nobody has seen empty, because the demo always had three candidates in it.
Empty is a designed screen with a way out of it.
Six of them here, more in the set. A fixture is added the first time a real roster embarrasses a screen, and removed only when the rule that catches it is compiled rather than stated.
The four statesDrawn, not described

The four that never make the mockup.

Every component ships with them, because they are not edge cases — they are most of a working day. A screen that only has its happy path is a screen that is a quarter finished.

02How they travel →
RosterLoading

Loading

Shaped like the roster that is arriving, down to the avatar and the two figures. Nothing jumps when the data lands.

Roster0 of 19
No candidates match this filterNineteen people are on the roster. None of them are on notice.Clear filters

Empty

Nothing to show is not the same as nothing. It says which of the two, and how many are behind the filter. Every empty state has a way out of it.

RosterFailed
Could not reach the HR systemRetried twice. Nothing was submitted, so nothing was changed.Try againWork offlineref 4c81f0 · 14:22

Error

What failed, whether it already retried, and whether anything was changed on the way. A reference the support desk can actually use.

CandidateRestricted
This record exists. You cannot open it.Salary detail is limited to the hiring manager and the HR partner.Request access

Permission denied

A designed screen, not a blank one and not a lie. The record exists, this reader cannot see it, and there is a route to asking.

Four states, one component, and the audit reports any component missing one of them. That check is the difference between a rule and a hope.
How they travelWith the product, not beside it

Fixtures ship with the thing they test.

A fixture kept in a separate repository is a fixture nobody runs. Each worked product carries its own, and each component carries the four states it must render.

03Back to the top →
Per product
Every worked product carries a fixture set built from its own domain — the claims that are time-barred, the roles nobody fits, the payouts that reverse.
Named in its notes file, so a reader knows what the screens were tested against.
Per component
Loading, empty, error and denied are part of the component, not a story written beside it.
Missing one is reported by the audit, by name.
In the rules
The reason each fixture exists is written next to the rule it defends, so the set does not outlive its argument.
Remove the rule, remove the fixture.
The point is not to have hostile data. It is to have already seen what the screen does when it arrives.

Reckon

Ship the screen.
Not the guesswork.

Components are the easy part. What you are missing is the layer that decides what gets computed, what gets shown, and what gets refused.

Built for Claude Design · plain markdown · nothing to run