---
alwaysApply: false
globs: ["**/*"]
description: "Enumerate empty, loading, partial, error and success states before the happy path. Use when building or reviewing a screen's behaviour."
---

# UI Surface States And Transitions

**Scope.** How one surface behaves over time. Visual values belong to
`ui-visual-values`. String wording belongs to `ui-text`.

**State table first.** Produce, before any happy-path markup, a table for
the surface with one row per state: state name, the condition that
triggers it, what the person sees, and the action that leaves it. A
reviewer checks the table exists and that the markup implements every row.

**The closed state set.** Produce a row for each state below, or the
written sentence "not reachable because X": empty-first-run,
empty-after-filter, empty-no-permission, loading-initial, loading-refresh,
pending-optimistic, partial-paginated, partial-some-failed, partial-stale,
error-retryable, error-terminal, offline, success-transient,
success-persistent, blocked, read-only. A reviewer counts sixteen rows or
sixteen written exclusions.

**Loading thresholds.** Produce no indicator under 200ms of wait, a
skeleton or indicator from 200ms, and a determinate progress bar plus a
cancel control past 5s `[ASSUMPTION]`. A reviewer throttles the network
and observes each threshold.

**No layout shift.** Produce a skeleton that occupies the loaded
content's box. A reviewer measures Cumulative Layout Shift at 0.1 or
below across the load.

**Refresh keeps content.** Produce the previous content plus an inline
indicator on refetch. Never blank a populated surface. A reviewer
refreshes a loaded list and sees the rows stay.

**Empty states carry a cause and an exit.** Produce one sentence naming
why the surface is empty and one control that changes it. A reviewer
finds both, and finds the text differing per variant: a filter with no
matches never shows the first-run text.

**Partial results are counted.** Produce the number that loaded, the
number that failed, and a retry scoped to the failures. A reviewer fails
one of several parallel requests and sees the loaded part still rendered.

**Stale data is dated.** Produce the time the shown data was fetched
whenever it is served from cache after a failed refresh. A reviewer
blocks the network on a loaded surface and reads that timestamp.

**Errors state their next step.** Produce the operation that failed, a
retry control when the operation is idempotent, and the correlation
identifier when one exists. A reviewer checks a terminal error offers a
path that is not retry: a link, a contact, or a different input.

**Optimistic updates declare rollback.** Produce the pre-change value,
the rollback trigger, and the message shown after rollback. A reviewer
rejects the request server-side and sees the surface return to the
pre-change value with that message.

**Success is recorded, not only flashed.** Produce a confirmation that
stays at least 4s `[ASSUMPTION]`, plus a durable result the person can
find afterwards. A reviewer dismisses the confirmation and still locates
the outcome.

**Blocked controls explain themselves.** Produce, for every blocked
control, the condition that unblocks it, readable without hovering. A
reviewer finds no blocked control whose reason is unstated.
