Wristkit / Documentation
Component states
Every wristkit component handles a five state contract. Here is why.
The contract
Every component in the registry has to handle these states:
This is not optional. The state contract is what makes wristkit feel polished instead of janky. Real data is messy. The Shortcut automation pauses, the Supabase connection drops, an env var is missing. Components that only handle the ok state break in silence.
Design principles
One layout for every state. Each state keeps the same pieces in the same places: a status pill in the header, the rings, the three metrics and a note in the footer. The card does not change shape when data is missing.
Generic error copy. The error card shows a short, generic message. It never echoes the underlying error or status code — that would leak details about your auth or database to whoever is looking at the page.
Numbers or nothing. The stale state keeps the last numbers and dims the arcs to say "old", instead of hiding them. The empty and error states use — rather than 0 or a blank, because a zero would suggest the user burned zero calories, which is not true.
Next steps in the footer. Recovery copy lives in the footer, where users already look for context. The empty state asks you to run the Shortcut on your iPhone. The stale state says how long ago the last sync was. No modals, no full screen takeovers.
State resolution
loadTodayActivity() (and every load function we ship) figures out the state for you:
The component is a pure renderer. It gets state as a prop and renders the matching UI. It never fetches. This split makes testing easy and lets the component live on both the server and the client side.
lastSyncLabel and hoursSinceSync are pre-computed on the server using the timezone you pass to loadTodayActivity. That keeps the rendered string identical between server and client (no hydration mismatch) and respects the user's local "today" instead of the server's timezone.