Requires exclusive pending, success, error, and empty states for async screens, forbids an unbounded spinner, announces completion, and preserves user input when a request fails.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
An async screen occupies exactly one of pending, success, error, or empty. Pending is time-bounded: after timeout_ms the screen must enter error with retry, never an infinite spinner. Success may show stale data during background revalidation, with a visible stale flag and no stacked blocking spinner. Completion of a user-initiated load is announced once. Typed filters and drafts must survive error. Transitions live in state-table.yaml.
Scope
- Screens that fetch or mutate then render a result region.
- Timeout, stale-while-revalidate, empty versus error, announcements.
- Preservation of in-progress input across failed reloads.
- Focus: this region does not steal focus unless a form submit is in play.
Outside this block
- Field-level validation codes and first-invalid focus.
- Destructive confirmation copy.
- Streaming token cancellation.
Contract
- The result region exposes one exclusive
statusfrom{pending, success, error, empty}. - Pending past
timeout_mswithout a success payload becomeserrorwithrequest_timeoutand an enabled retry control. - Empty is legal only when the request succeeded and the result set is zero; 5xx or network failure is
error. - Stale-while-revalidate keeps
status: successwithstale: trueand must not cover the region with a blocking spinner. - A user-initiated fetch that reaches success or error is announced once in a polite live region; revalidation ticks are silent.
- User-entered filter and draft strings survive error; they clear only via an explicit reset control.
Implementation
Model status as a state machine, not overlapping booleans. After timeout, cancel the fetch so a late body cannot flip error to success without retry. Keep the previous success view while revalidating. Mount the live region once.
Failure handling
Retry moves to pending and disables duplicate retries until settle or timeout. A failed revalidation must not become empty. If stale age exceeds max_stale_ms, show error instead of a current-looking view.
Verification
Unit-test exclusive status from the state table. Capture timeout, empty-versus-error, stale overlay, announcement count, and input preservation. Measure spinner duration against timeout_ms. Execute the packaged scenarios.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.