# 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`) `` 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.