semantic state surface
situation
- route showed a remote process; success view clear, surrounding states grew independently
- spinner in one branch, raw error in another, empty-looking panel on denied access, plausible placeholder values while the authoritative response was absent
- two facts looked identical: "nothing exists" vs "the screen does not know"
- a transient request failure could look like an operation completed
why
- an interface that fills gaps with plausible content is lying with good visuals: absence, denial, failure all look like ready data
- invariant: every visible claim comes from the authoritative response or is labeled as uncertainty
- mapping typed remote states to a small set of semantic primitives makes each condition a distinct, auditable surface instead of per-branch improvisation
invariants
- every visible claim must come from the authoritative response or be clearly identified as uncertainty
- each failure surface must name the next meaningful action: retry, correct, request access, wait, or stop
- a screen must not manufacture content to preserve visual polish
- exact time and state remain available even when a compact relative label is displayed
decision process
| candidate | reasoning | decision |
|---|---|---|
| keep one success component and add conditional text around it | fast initially, but each new failure branch duplicates layout and loses semantic consistency. | rejected |
| fill unavailable sections with plausible sample values | looks complete in a screenshot but falsely asserts that the system has data or progress. | rejected |
| show the raw transport exception | preserves a cause but hands provider vocabulary to the person without telling them what action is valid. | rejected |
| map typed remote states to a small set of semantic state primitives | separates authority from presentation, keeps actions bounded, and gives every route the same audit surface. | selected |
resulting shape
- the query hook returns the remote state and typed failure category
- the route or display component maps it once:
| remote fact | semantic surface | person can conclude |
|---|---|---|
| request pending | loading | the system is still determining the answer. |
| successful empty response | empty | there is currently no matching record. |
| authorization failure | blocked | access or identity must be corrected. |
| retryable dependency failure | recoverable failure | the request did not complete; retry may be useful. |
| invalid input or configuration | correction required | a specific prerequisite must change. |
| successful populated response | ready | the displayed facts are authoritative for the response timestamp. |
- the state primitive owns wording, iconography, action placement
- the domain component supplies the semantic category and recovery action; never passes raw class strings or transport labels through its API
what this prevented
- earlier shape allowed two regressions: fresh mount briefly showed old-looking sample content; denied response rendered as a blank section
- selected design makes both impossible to hide: absence is explicit, denial distinct from emptiness, retryable failure cannot masquerade as ready data
verification matrix
use a controlled response or fixture for each state. for every render, verify the visible claim, action, and metadata together:
- pending preserves enough structure to avoid layout deception but claims no result
- empty states explain absence without an error treatment
- blocked states give an access-oriented action and no retry promise
- recoverable failures preserve a safe diagnostic category and expose retry only when it is valid
- ready states render only response-backed values and expose their exact timestamp in accessible metadata
good and bad
bad
return <WorkflowList items={query.data ?? sampleWorkflows} />;
- the page turns missing authority into fabricated ready content
- absence, denial, transient failure all look identical to success
- visitor acts on facts nobody established; a screenshot review can never catch the lie because the screen looks complete
good
return query.isPending ? <WorkflowListSkeleton /> : <WorkflowListState query={query} />;
- each remote condition renders its own truthful semantic state
- pending, empty, blocked, recoverable failure become distinct auditable surfaces with bounded actions
- the same primitive set is reused across routes instead of re-improvised per branch