feat(ui): explicit server-unavailable state + auto-refetch on reconnect (MAPPS-351) #412

Merged
vas2000-work merged 1 commit from feat/MAPPS-351-server-down-ux into main 2026-07-03 06:57:24 +02:00
Owner

What

Fixes the "everything vanishes when the server is down" UX (MAPPS-351), building on MAPPS-333 (reachability flag + non-blocking banner + /ready recovery poll).

Why

Data-driven pages loaded with use_resource(...).ok().unwrap_or_default(), which swallows a failed fetch into an empty default. During an outage a page rendered empty tables / zero counts that looked identical to a brand-new tenant with no data. The banner was the only signal; the content lied.

How

New primitives

  • RemoteData<T> + use_remote_resource (src/hooks/remote_data.rs): preserve a failed fetch as an explicit Unavailable state, and subscribe to SERVER_REACHABLE so resources auto-refetch the instant the server returns. The (outcome, reachable) -> RemoteData mapping is the pure, unit-tested classify_remote.
  • ContentUnavailable (src/components/content_unavailable.rs): the honest page body swapped in when the primary resource is Unavailable. Keeps AppLayout chrome (sidebar nav, top bar, MAPPS-333 banner stay visible) and offers "Go to dashboard". Mirrors the PermissionRequired empty-state idiom.
  • use_can_mutate (src/hooks/server_status.rs): disables writes while down so a click cannot silently fail.

Retrofits

  • Dashboard: report is the primary resource; all four loads auto-refetch on reconnect.
  • My Approvals: unavailable state on down; approve/reject buttons disabled with an explanatory tooltip while down.
  • Tenant Management: shows the unavailable state instead of masquerading demo rows as real data during an outage, while preserving the intentional demo fallback for reachable errors.
  • Other data pages adopt the primitives incrementally. Settings home is a static card menu (no fetch) so it already stays visible. Nav gates on the in-memory auth user, not a server fetch, so tabs do not vanish.

Deferred (option 3, scaffold only)
src/hooks/edit_queue.rs lays down PendingEdit, EDIT_QUEUE, enqueue, and a replay_pending_edits whose backoff/conflict body is commented out, plus a commented reconnect call in use_server_status_monitor. Compiles and is inert; hold-and-replay (the local-first slice) becomes a small, well-located change later. Today writes are blocked/discarded while down.

Acceptance criteria

  • Server down shows an explicit unavailable state rather than missing sections.
  • User is kept on their page / offered the dashboard with a cannot-connect indication (no auto-redirect).
  • Reconnect behavior defined: resources auto-refetch; edits are blocked/discarded (replay scaffolded).
  • Tests cover the server-down and reconnect transitions (classify_remote, incl. the down -> reconnect sequence).

Verification

just pre-commit (dockerized): fmt, clippy -D warnings, wasm cargo check, test --lib = 200 passed.

Refs #MAPPS-351.

## What Fixes the "everything vanishes when the server is down" UX (MAPPS-351), building on MAPPS-333 (reachability flag + non-blocking banner + `/ready` recovery poll). ## Why Data-driven pages loaded with `use_resource(...).ok().unwrap_or_default()`, which swallows a failed fetch into an empty default. During an outage a page rendered empty tables / zero counts that looked identical to a brand-new tenant with no data. The banner was the only signal; the content lied. ## How **New primitives** - `RemoteData<T>` + `use_remote_resource` (`src/hooks/remote_data.rs`): preserve a failed fetch as an explicit `Unavailable` state, and subscribe to `SERVER_REACHABLE` so resources **auto-refetch the instant the server returns**. The `(outcome, reachable) -> RemoteData` mapping is the pure, unit-tested `classify_remote`. - `ContentUnavailable` (`src/components/content_unavailable.rs`): the honest page body swapped in when the primary resource is `Unavailable`. Keeps `AppLayout` chrome (sidebar nav, top bar, MAPPS-333 banner stay visible) and offers "Go to dashboard". Mirrors the `PermissionRequired` empty-state idiom. - `use_can_mutate` (`src/hooks/server_status.rs`): disables writes while down so a click cannot silently fail. **Retrofits** - Dashboard: report is the primary resource; all four loads auto-refetch on reconnect. - My Approvals: unavailable state on down; approve/reject buttons disabled with an explanatory tooltip while down. - Tenant Management: shows the unavailable state instead of masquerading demo rows as real data during an outage, while preserving the intentional demo fallback for *reachable* errors. - Other data pages adopt the primitives incrementally. Settings home is a static card menu (no fetch) so it already stays visible. Nav gates on the in-memory auth user, not a server fetch, so tabs do not vanish. **Deferred (option 3, scaffold only)** `src/hooks/edit_queue.rs` lays down `PendingEdit`, `EDIT_QUEUE`, `enqueue`, and a `replay_pending_edits` whose backoff/conflict body is commented out, plus a commented reconnect call in `use_server_status_monitor`. Compiles and is inert; hold-and-replay (the local-first slice) becomes a small, well-located change later. Today writes are blocked/discarded while down. ## Acceptance criteria - [x] Server down shows an explicit unavailable state rather than missing sections. - [x] User is kept on their page / offered the dashboard with a cannot-connect indication (no auto-redirect). - [x] Reconnect behavior defined: resources auto-refetch; edits are blocked/discarded (replay scaffolded). - [x] Tests cover the server-down and reconnect transitions (`classify_remote`, incl. the down -> reconnect sequence). ## Verification `just pre-commit` (dockerized): fmt, clippy `-D warnings`, wasm `cargo check`, `test --lib` = 200 passed. Refs #MAPPS-351.
feat(ui): explicit server-unavailable state + auto-refetch on reconnect
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m18s
Create release / Create release from merged PR (pull_request) Has been skipped
9e4b49a286
Builds on MAPPS-333 (the app-wide reachability flag, non-blocking banner, and /ready recovery poll) to fix the layer above it: data-driven pages loaded with `use_resource(...).ok().unwrap_or_default()`, which swallows a failed fetch into an empty default, so during an outage a page rendered empty tables and zero counts that looked exactly like a brand-new tenant with no data. The banner was the only signal; the content itself lied.

New primitives. `RemoteData<T>` + `use_remote_resource` (src/hooks/remote_data.rs) replace the swallowing pattern: they preserve a failed fetch as an explicit `Unavailable` state and subscribe to `SERVER_REACHABLE` so every resource auto-refetches the instant the recovery poll flips the server back. The `(outcome, reachable) -> RemoteData` mapping is factored into the pure `classify_remote` and unit-tested (including the full down -> reconnect transition). `ContentUnavailable` (src/components/content_unavailable.rs) is the honest page body a page swaps in when its primary resource is `Unavailable`; it keeps the AppLayout chrome (sidebar nav, top bar, MAPPS-333 banner all stay visible) and offers a "Go to dashboard" affordance, mirroring the PermissionRequired empty-state idiom. `use_can_mutate` (src/hooks/server_status.rs) disables writes while the server is down so a click cannot silently fail.

Retrofits. Dashboard (report is the primary resource; all four loads auto-refetch on reconnect), My Approvals (unavailable state on down + decision buttons disabled with an explanatory tooltip while down), and Tenant Management (shows the unavailable state instead of masquerading demo rows as real data during an outage, while preserving the intentional demo fallback for reachable errors). Other data pages adopt the primitives incrementally; the Settings home is a static card menu with no server fetch, so it already stays visible during an outage. Nav is unchanged: it gates on the in-memory auth user, not a server fetch, so tabs do not vanish while down.

Deferred (option 3, scaffold only). src/hooks/edit_queue.rs lays down PendingEdit, the EDIT_QUEUE signal, enqueue, and a replay_pending_edits whose backoff/conflict-resolution body is commented out, plus a commented reconnect call in use_server_status_monitor. It compiles and is inert, so hold-and-replay (the local-first slice) is a small, well-located change later. Today writes are blocked/discarded while down, not queued.

Verified with `just pre-commit` (dockerized fmt, clippy -D warnings, wasm cargo check, test --lib: 200 passed).

#MAPPS-351

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vas2000-work deleted branch feat/MAPPS-351-server-down-ux 2026-07-03 06:57:24 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!412
No description provided.