8dcaf1c6c6
- lib/error/: classify any caught error into a stable ErrorKind from the transport signal (HTTP status / Connect Code / fetch TypeError / navigator.onLine); map to translated error.* messages via reportError (sticky Retry toast for retryable kinds) or errorMessageKey (inline). Mail compose now surfaces the translated 403/error inline. - lib/ui/view-state.svelte: shared loading/empty/error placeholder with the right live-region role + optional action; entity tables (races/sciences/ship-classes) migrated, rest adopt incrementally. - map/selection-ring.ts: accent ring around the selected planet, fed into the map buildExtras alongside the reach circles. - lib/ui/sheet-dismiss.ts: tap-outside + drag-handle swipe-down dismissal for the planet/ship-group bottom-sheets (hand-rolled pointer events). Tests: error, view-state, selection-ring, sheet-dismiss (761 total). Docs: ui/docs/error-state-ux.md (+ index); F4 marked done. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52 lines
2.4 KiB
Markdown
52 lines
2.4 KiB
Markdown
# Error & state UX
|
|
|
|
How the client gives consistent, actionable feedback for failures and for
|
|
loading / empty / error states.
|
|
|
|
## Error surface (`src/lib/error/`)
|
|
|
|
Server `result_code`s are downstream-opaque (only `"ok"` is contractual —
|
|
`docs/ARCHITECTURE.md`), so the client keys UX off the reliable
|
|
transport-level signal instead.
|
|
|
|
- [`classify.ts`](../frontend/src/lib/error/classify.ts) collapses any
|
|
caught value into a stable `ErrorKind` (`offline`, `network`, `auth`,
|
|
`forbidden`, `conflict`, `notFound`, `rateLimit`, `server`, `unknown`)
|
|
from `navigator.onLine`, an `AuthError` HTTP status, a Connect `Code`,
|
|
or a fetch `TypeError`. `isRetryable` marks the transient kinds.
|
|
- [`report.ts`](../frontend/src/lib/error/report.ts) maps each kind to a
|
|
translated `error.*` message and either:
|
|
- `reportError(err, { onRetry? })` — raises a toast (a sticky toast
|
|
with a **Retry** action for a retryable kind that supplies `onRetry`,
|
|
otherwise auto-dismiss); or
|
|
- `errorMessageKey(err)` — returns the translated key for a view to
|
|
render the message inline (e.g. the mail compose 403).
|
|
|
|
Messages live under `error.*` (en + ru); the action label is
|
|
`common.retry`. New call sites adopt this surface incrementally; the mail
|
|
compose dialog is the worked inline example.
|
|
|
|
## View states (`src/lib/ui/view-state.svelte`)
|
|
|
|
`<ViewState kind="loading|empty|error" message=… [actionLabel onAction] />`
|
|
is the one placeholder for a view or panel: a spinner for loading, an
|
|
accent action button when given, and the right live-region role
|
|
(`status` for loading/empty, `alert` for error). The entity tables
|
|
(races / sciences / ship-classes) use it; remaining views adopt it
|
|
incrementally.
|
|
|
|
## Selected-planet marker (`src/map/selection-ring.ts`)
|
|
|
|
When the `SelectionStore` holds a planet, the map draws one accent ring
|
|
tight around it (`computeSelectionRing`, fed into the map's `buildExtras`
|
|
alongside the reach circles). Ship-group selection is not ringed — groups
|
|
are addressed by report index and have no single stable map coordinate.
|
|
|
|
## Mobile bottom-sheet dismissal (`src/lib/ui/sheet-dismiss.ts`)
|
|
|
|
`use:sheetDismiss={{ onDismiss }}` adds two hand-rolled (no-dependency)
|
|
gestures to the planet / ship-group bottom-sheets: tap anywhere outside
|
|
the sheet, or drag its `[data-sheet-handle]` grabber down past a
|
|
threshold. The swipe starts only from the grabber so it never fights the
|
|
sheet's own content scrolling.
|