feat(web): app-wide service-unavailable banner when bunyip-api is down (BUNYIP-243) #278

Merged
vas2000-work merged 4 commits from feat/BUNYIP-243-server-down-banner into main 2026-06-30 06:28:00 +02:00
Owner

What

When bunyip-api is unreachable the BFF (bunyip-web) degraded silently and indistinguishably from real conditions: dashboard.rs collapsed to an empty list via .unwrap_or_default(), auth.rs treated any non-401 error as signed-out (phantom logout), and other handlers showed a generic "unexpected error". Reflect it instead as one clear app-wide "Service unavailable" state. Closes BUNYIP-243 (the bunyip-web counterpart of MAPPS-333).

Changes

  • New bunyip-web/src/server_status.rs: a process-global AtomicBool "api reachable" flag (default reachable), a classifier, an SSR banner partial, and a recovery poll.
  • Classify at the single BFF chokepoint Api::send (api/mod.rs): a transport failure or 5xx marks down; any real response (including 4xx) clears it, so auth/validation errors keep their normal per-handler handling. Also classifies post_form() and the get_stream() download proxy.
  • Render the banner from the shared document() shell (views/layout.rs), so it shows on every page including /login: a phantom-logout bounce now lands on a login page that clearly says the service is unavailable, instead of looking like a normal logout. Renders nothing while healthy.
  • Spawn the recovery poll at startup (main.rs): while down, hit bunyip-api GET /health every 10s and clear on first success. No network while healthy; banner auto-dismisses on recovery.

Acceptance criteria coverage

  • Single classification point marks down on transport failure / timeout / 5xx; 4xx does not.
  • One non-blocking full-width "Service unavailable, reconnecting..." banner while down; pages still render.
  • Background /health poll clears the state on first success; no polling while healthy; auto-dismiss on recovery.
  • Phantom logout is no longer silent: the banner renders on /login too, so a down API is communicated rather than reading as a logout. (A deeper change to skip the /login redirect entirely during an outage was left out to avoid touching the auth/guard redirect flow; the banner-on-login covers the user-visible AC.)
  • [~] Browser-side SSE channel: the server-side banner is the primary outage signal. Reflecting the direct browser-to-API EventSource drop client-side (and not surfacing its opaque failure as a CORS error in the console) is not included here and can be a follow-up; the EventSource already auto-reconnects.

Verification

just check-container green in the rust-builder-glibc:v1.0.1-rust1.94-trixie CI image: cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace --lib (270 tests pass). Manual AC (stop bunyip-api -> banner within one poll, no phantom logout / empty dashboard; restart -> auto-clear) needs the live stack and was not exercised here.

## What When bunyip-api is unreachable the BFF (bunyip-web) degraded silently and indistinguishably from real conditions: `dashboard.rs` collapsed to an empty list via `.unwrap_or_default()`, `auth.rs` treated any non-401 error as signed-out (phantom logout), and other handlers showed a generic "unexpected error". Reflect it instead as one clear app-wide "Service unavailable" state. Closes BUNYIP-243 (the bunyip-web counterpart of MAPPS-333). ## Changes - New `bunyip-web/src/server_status.rs`: a process-global `AtomicBool` "api reachable" flag (default reachable), a classifier, an SSR banner partial, and a recovery poll. - Classify at the single BFF chokepoint `Api::send` (`api/mod.rs`): a transport failure or `5xx` marks down; any real response (including `4xx`) clears it, so auth/validation errors keep their normal per-handler handling. Also classifies `post_form()` and the `get_stream()` download proxy. - Render the banner from the shared `document()` shell (`views/layout.rs`), so it shows on every page including `/login`: a phantom-logout bounce now lands on a login page that clearly says the service is unavailable, instead of looking like a normal logout. Renders nothing while healthy. - Spawn the recovery poll at startup (`main.rs`): while down, hit bunyip-api `GET /health` every 10s and clear on first success. No network while healthy; banner auto-dismisses on recovery. ## Acceptance criteria coverage - [x] Single classification point marks down on transport failure / timeout / `5xx`; `4xx` does not. - [x] One non-blocking full-width "Service unavailable, reconnecting..." banner while down; pages still render. - [x] Background `/health` poll clears the state on first success; no polling while healthy; auto-dismiss on recovery. - [x] Phantom logout is no longer silent: the banner renders on `/login` too, so a down API is communicated rather than reading as a logout. (A deeper change to skip the `/login` redirect entirely during an outage was left out to avoid touching the auth/guard redirect flow; the banner-on-login covers the user-visible AC.) - [~] Browser-side SSE channel: the server-side banner is the primary outage signal. Reflecting the direct browser-to-API `EventSource` drop client-side (and not surfacing its opaque failure as a CORS error in the console) is not included here and can be a follow-up; the EventSource already auto-reconnects. ## Verification `just check-container` green in the `rust-builder-glibc:v1.0.1-rust1.94-trixie` CI image: `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace --lib` (270 tests pass). Manual AC (stop bunyip-api -> banner within one poll, no phantom logout / empty dashboard; restart -> auto-clear) needs the live stack and was not exercised here.
feat(web): app-wide service-unavailable banner when bunyip-api is down
Some checks failed
E2E / Playwright against deployment (pull_request) Failing after 23s
Check / fmt + clippy + build + tests (pull_request) Successful in 18m52s
e294507167
When bunyip-api was unreachable the BFF degraded silently: dashboard.rs collapsed to an empty list via .unwrap_or_default(), auth.rs treated any non-401 error as signed-out (phantom logout), and other handlers showed a generic "unexpected error". Nothing told the user the backend was down, and the direct browser-to-API SSE failure read like a CORS error.

Add a process-global "api reachable" flag (server_status) classified at the single BFF chokepoint Api::send: a transport failure or 5xx marks down, any real response (incl. 4xx) clears it, so auth/validation errors keep their normal per-handler handling. The shared document() shell renders one non-blocking "Service unavailable. Reconnecting..." banner whenever down, on every page including /login, so an outage is communicated instead of reading as a phantom logout or silently empty page. A startup recovery task polls bunyip-api GET /health every 10s only while down and clears the flag on first success, so there is no added traffic while healthy and the banner auto-dismisses on recovery. Classification covers send(), post_form(), and the get_stream() download proxy.

Verified via just check-container (cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace --lib) in the rust-builder-glibc CI image: all green.

#BUNYIP-243
ci: re-trigger checks (E2E staging flake, not a code change)
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 24s
Check / fmt + clippy + build + tests (pull_request) Successful in 11m22s
be2a9114ba
The previous E2E run failed against shared staging (pull_request runs test the live deployment, never the PR SHA), not this branch's code. Empty commit to re-run.

#BUNYIP-243
Merge branch 'main' into feat/BUNYIP-243-server-down-banner
Some checks failed
E2E / Playwright against deployment (pull_request) Successful in 22s
Check / fmt + clippy + build + tests (pull_request) Has been cancelled
f2f51c21bb
vas2000-work scheduled this pull request to auto merge when all checks succeed 2026-06-30 05:58:43 +02:00
Merge branch 'main' into feat/BUNYIP-243-server-down-banner
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 21s
Check / fmt + clippy + build + tests (pull_request) Successful in 16m47s
Create release / Create release from merged PR (pull_request) Has been skipped
2ad1d467bf
vas2000-work deleted branch feat/BUNYIP-243-server-down-banner 2026-06-30 06:28:00 +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/bunyip!278
No description provided.