feat(spa): retrofit remaining data-driven pages onto explicit server-down UX #415

Merged
Claude-Run merged 1 commit from feat/MAPPS-357-retrofit-remote-resource-pages into main 2026-07-03 17:50:59 +02:00
Member

Completes the MAPPS-351 AC1 invariant across the whole app: every data-driven page now renders an explicit "server unreachable" state during an outage instead of empty tables / zero counts (the "content itself lies" failure mode), and disables its write controls while the server is down. Previously only dashboard/approvals/admin were retrofitted; the remaining pages still used the use_resource(...) + .ok().unwrap_or_default() swallow pattern.

Two patterns applied per page, mirroring the existing references: read-only primaries use use_remote_resource + ContentUnavailable gated on is_unavailable() (like dashboard.rs); primaries that are .restart()ed after a mutation keep use_resource but preserve the failure (.ok() / raw Result, never .unwrap_or_default()), subscribe to use_server_reachable() inside the fetcher for auto-refetch on reconnect, and gate ContentUnavailable on fetch_failed && !reachable (like approvals.rs). Mutating controls are disabled via use_can_mutate() with an explanatory tooltip. Secondary/lookup resources keep degrading to defaults, documented inline per page.

Pages retrofitted: billing, time, tickets, contracts, portal, sla, reports, projects, team, contacts, calendar, assets, audit_log, knowledge_base, settings, profile, big_view, dashboards, dashboards_view.

Special cases: system_status is marked N/A inline (it is the diagnostics page that reports connectivity, so it must render while the server is down and already preserves each probe as an explicit Result). portal pages use a new portal-native PortalUnavailable panel inside PortalLayout instead of the shared ContentUnavailable, which mounts the internal-agent AppLayout and would leak internal chrome to client-portal users. big_view kiosk pages render an inline unavailable panel inside BigLayout for the same reason. sla moved its AppLayout + tab-bar chrome into a new SlaShell wrapper so a tab can return the full-page ContentUnavailable without nesting a second AppLayout.

AC5 coverage: added a down -> reconnect classify_remote test on a concretely-retrofitted page (dashboards.rs saved-dashboards list), extending the coverage pattern from hooks/remote_data.rs so a regression that reintroduced the swallow would fail the suite.

Known follow-up (not a data-loss bug; the page swaps to the unavailable state before these modals can open during a full outage): shared modal/picker components (SettingFormModal, AssetPicker, CompanyPicker, ContactPicker) expose no disabled/can_mutate prop, so their in-modal Save/Delete cannot be gated from a single page file; hardening them needs a prop added to those shared components, which is broader than this page-scoped change.

#MAPPS-357

Completes the MAPPS-351 AC1 invariant across the whole app: every data-driven page now renders an explicit "server unreachable" state during an outage instead of empty tables / zero counts (the "content itself lies" failure mode), and disables its write controls while the server is down. Previously only dashboard/approvals/admin were retrofitted; the remaining pages still used the `use_resource(...) + .ok().unwrap_or_default()` swallow pattern. Two patterns applied per page, mirroring the existing references: read-only primaries use `use_remote_resource` + `ContentUnavailable` gated on `is_unavailable()` (like dashboard.rs); primaries that are `.restart()`ed after a mutation keep `use_resource` but preserve the failure (`.ok()` / raw `Result`, never `.unwrap_or_default()`), subscribe to `use_server_reachable()` inside the fetcher for auto-refetch on reconnect, and gate `ContentUnavailable` on `fetch_failed && !reachable` (like approvals.rs). Mutating controls are disabled via `use_can_mutate()` with an explanatory tooltip. Secondary/lookup resources keep degrading to defaults, documented inline per page. Pages retrofitted: billing, time, tickets, contracts, portal, sla, reports, projects, team, contacts, calendar, assets, audit_log, knowledge_base, settings, profile, big_view, dashboards, dashboards_view. Special cases: system_status is marked N/A inline (it is the diagnostics page that reports connectivity, so it must render while the server is down and already preserves each probe as an explicit Result). portal pages use a new portal-native `PortalUnavailable` panel inside `PortalLayout` instead of the shared `ContentUnavailable`, which mounts the internal-agent `AppLayout` and would leak internal chrome to client-portal users. big_view kiosk pages render an inline unavailable panel inside `BigLayout` for the same reason. sla moved its AppLayout + tab-bar chrome into a new `SlaShell` wrapper so a tab can return the full-page `ContentUnavailable` without nesting a second AppLayout. AC5 coverage: added a down -> reconnect `classify_remote` test on a concretely-retrofitted page (dashboards.rs saved-dashboards list), extending the coverage pattern from hooks/remote_data.rs so a regression that reintroduced the swallow would fail the suite. Known follow-up (not a data-loss bug; the page swaps to the unavailable state before these modals can open during a full outage): shared modal/picker components (SettingFormModal, AssetPicker, CompanyPicker, ContactPicker) expose no `disabled`/`can_mutate` prop, so their in-modal Save/Delete cannot be gated from a single page file; hardening them needs a prop added to those shared components, which is broader than this page-scoped change. #MAPPS-357
feat(spa): retrofit remaining data-driven pages onto explicit server-down UX
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m22s
Create release / Create release from merged PR (pull_request) Has been skipped
d212d32a5b
Completes the MAPPS-351 AC1 invariant across the whole app: every data-driven page now renders an explicit "server unreachable" state during an outage instead of empty tables / zero counts (the "content itself lies" failure mode), and disables its write controls while the server is down. Previously only dashboard/approvals/admin were retrofitted; the remaining pages still used the `use_resource(...) + .ok().unwrap_or_default()` swallow pattern.

Two patterns applied per page, mirroring the existing references: read-only primaries use `use_remote_resource` + `ContentUnavailable` gated on `is_unavailable()` (like dashboard.rs); primaries that are `.restart()`ed after a mutation keep `use_resource` but preserve the failure (`.ok()` / raw `Result`, never `.unwrap_or_default()`), subscribe to `use_server_reachable()` inside the fetcher for auto-refetch on reconnect, and gate `ContentUnavailable` on `fetch_failed && !reachable` (like approvals.rs). Mutating controls are disabled via `use_can_mutate()` with an explanatory tooltip. Secondary/lookup resources keep degrading to defaults, documented inline per page.

Pages retrofitted: billing, time, tickets, contracts, portal, sla, reports, projects, team, contacts, calendar, assets, audit_log, knowledge_base, settings, profile, big_view, dashboards, dashboards_view.

Special cases: system_status is marked N/A inline (it is the diagnostics page that reports connectivity, so it must render while the server is down and already preserves each probe as an explicit Result). portal pages use a new portal-native `PortalUnavailable` panel inside `PortalLayout` instead of the shared `ContentUnavailable`, which mounts the internal-agent `AppLayout` and would leak internal chrome to client-portal users. big_view kiosk pages render an inline unavailable panel inside `BigLayout` for the same reason. sla moved its AppLayout + tab-bar chrome into a new `SlaShell` wrapper so a tab can return the full-page `ContentUnavailable` without nesting a second AppLayout.

AC5 coverage: added a down -> reconnect `classify_remote` test on a concretely-retrofitted page (dashboards.rs saved-dashboards list), extending the coverage pattern from hooks/remote_data.rs so a regression that reintroduced the swallow would fail the suite.

Known follow-up (not a data-loss bug; the page swaps to the unavailable state before these modals can open during a full outage): shared modal/picker components (SettingFormModal, AssetPicker, CompanyPicker, ContactPicker) expose no `disabled`/`can_mutate` prop, so their in-modal Save/Delete cannot be gated from a single page file; hardening them needs a prop added to those shared components, which is broader than this page-scoped change.

#MAPPS-357
Claude-Run deleted branch feat/MAPPS-357-retrofit-remote-resource-pages 2026-07-03 17:50:59 +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!415
No description provided.