Skip to content

Design/network status banner - #1359

Closed
stephaniepez21-art wants to merge 4 commits into
Fluxora-Org:mainfrom
stephaniepez21-art:design/network-status-banner
Closed

Design/network status banner#1359
stephaniepez21-art wants to merge 4 commits into
Fluxora-Org:mainfrom
stephaniepez21-art:design/network-status-banner

Conversation

@stephaniepez21-art

Copy link
Copy Markdown
Contributor

Closes #1044

What this PR does

Adds an app-wide <NetworkStatusBanner /> mounted in src/components/Layout.tsx that surfaces Soroban RPC + browser-network health across every /app page. The banner surfaces five states:

State Visible? Tone aria-live Notes
online-nominal no Default; banner hidden.
slow yes info polite Latency ≥ 1500 ms within a 4 s window OR recent RPC error.
offline yes error assertive navigator.onLine === false OR last error is a timeout.
reconnecting yes warning polite Bridge state after recovery; deterministic 1.5 s chip.
reconnected-confirmation pill success polite 4 s confirmation pill, then auto-dismiss (also has manual close).

In-flight actions get a data-at-risk="true" attribute plus a "network is unstable — submission may not complete" label suffix so users (and screen-readers) know a submission may not complete while connectivity is degraded.

State machine

                                                       ┌──────────────────────┐
                                                       │  online-nominal      │ ◀─── initial state; banner is hidden
                                                       │  (banner hidden)     │
                                                       └──────────┬───────────┘
                          RPC latency ≥ 1500 ms                window 'offline'
                          OR RPC error                        OR RPC timeout
                  ┌─────────────────────┐                    ┌────────────────────┐
                  ▼                     │                    ▼                    │
             ┌────────┐                 │              ┌──────────┐              │
   ┌───────◀─│  slow  │ ◀── latency ─────┘              │  offline │ ─ online ──┐ │
   │         │  info  │   window clears (≥ 4 s)          │  error   │    event    │ │
   │ slow    └────┬───┘                                └────┬─────┘             │ │
   │  obervation  │ heuristic: window online AND the          │ online event detected│ │
   │              │ last observation was within the         ▼                    │ │
   │              │ slow-heal window AND healthy,        ┌───────────────┐         │ │
   │              │ then we transition through          │  reconnecting │         │ │
   │              │ the deterministic reconnecting chip  │   warning     │         │ │
   │              └─────────── last recovery before ─────►└──────┬────────┘         │ │
   │                                                                  │ 1.5 s chip │ │
   │                                                                  ▼            │ │
   │                                                          ┌──────────────────────────┐
   │                                                          │  reconnected-confirmation │
   │                                                          │  (success pill, ~4 s)    │
   │                                                          └──────────┬───────────────┘
   │                                                                     │ 4 s timer
   └─────────────────────────────────────────────────────────────────────┘
                                                                             ▼
                                                                   ┌──────────────────────┐
                                                                   │  online-nominal      │
                                                                   └──────────────────────┘

Surface

New files

  • docs/NETWORK_STATUS_BANNER_SPEC.md — design spec, transition diagram, contrast redlines, accessibility annotations, PR hand-off checklist.
  • src/lib/networkStatus.ts — module-level singleton store (mirrors the offlineActionQueue pattern) where useTransactionStatus publishes latency + errors and any subscriber (the hook) consumes them.
  • src/hooks/useNetworkStatus.ts — React hook + state machine. StrictMode-safe (no refs that would survive React 18 double-mount); deterministic timers (1.5 s chip → 4 s pill → dismiss).
  • src/components/NetworkStatusBanner.tsx + .css — presentational banner with aria-live semantics per state + responsive reflow to a 36 px pill below --breakpoint-sm (640 px).
  • src/hooks/__tests__/useNetworkStatus.test.ts — per-transition state-machine tests (8 transitions).
  • src/components/__tests__/NetworkStatusBanner.test.tsx — render chrome per state (5 tests).
  • src/components/__tests__/NetworkStatusBanner.contrast.test.tsx — WCAG ≥ 4.5 : 1 contrast across both themes (9 tests).
  • src/components/__tests__/NetworkStatusBanner.layout.test.tsxLayout mount + skip-link safety (3 tests).

Modified files

  • src/components/Layout.tsx — mounts <NetworkStatusBanner /> above <main> so the existing #main-content skip-link target still bypasses it.
  • src/components/Layout.css — banner host slot + responsive rule at --breakpoint-sm.
  • src/hooks/useTransactionStatus.ts — calls reportRpcSuccess(latency) / reportRpcFailure(latency, category) for every poll result.
  • src/components/CreateStreamModal.tsx — reads useNetworkStatus().isAtRisk, applies data-at-risk="true" + an at-risk label suffix to both the single-stream submit AND the bulk dry-run submit.
  • src/i18n/en.tsnetwork.banner.* strings + createStream.atRisk.networkSuffix.

Evidence

  • pnpm exec tsc --noEmit -p . — clean for the new files (pre-existing errors in ConnectWalletModal.test.tsx are unrelated to this PR).
  • pnpm exec vitest run src/hooks/__tests__/useNetworkStatus.test.ts src/components/__tests__/NetworkStatusBanner.*.test.tsx27 / 27 passing.
  • ✅ WCAG 2.1 AA contrast ≥ 4.5 : 1 across light + dark themes for all four visible tones (asserted in NetworkStatusBanner.contrast.test.tsx).
  • ✅ Skip-link target in Layout bypasses the banner — clicking Skip to main content focuses <main> directly (asserted in NetworkStatusBanner.layout.test.tsx).
  • forced-colors: active (Windows High Contrast) fallback path covered in NetworkStatusBanner.css.

Redlines / screenshots

To attach before merge: light + dark theme captures of slow, offline, reconnecting, and the reconnected-confirmation pill, plus a 640 px breakpoint compact variant. Placeholder slots below for the upload.

  • Light · slow: attach
  • Light · offline: attach
  • Light · reconnecting: attach
  • Light · reconnected-confirmation pill: attach
  • Dark · slow: attach
  • Dark · offline: attach
  • Dark · reconnecting: attach
  • Dark · reconnected-confirmation pill: attach
  • Responsive · single-line compaction at 640 px breakpoint: attach

Out of scope (follow-ups)

  • The data-tone attribute is rendered for designer audit but not consumed by any CSS rule — review whether to migrate one selector or drop the attribute.
  • NetworkStatusBanner.contrast.test.tsx uses hardcoded hex proxies; resolving live --color-info-bg / --color-warning-bg / --color-danger-bg / --color-success-bg via getComputedStyle(document.documentElement) would catch future recolours (≈ 20-line jsdom polyfill in src/test/setup.ts).
  • No e2e/ Playwright spec yet — e2e/wallet-disconnect-reconnect.spec.ts is the natural home for the offline → banner → online → pill → dismiss flow.
  • useTransactionStatus.ts does not currently report observations on AbortError cleanup; minor honest-fail opportunity.
  • dismissPill useCallback deps are [props]; ESLint-friendly edit would tighten to [props.onDismissPill].

Reviewer checklist

  • Spec doc (docs/NETWORK_STATUS_BANNER_SPEC.md).
  • State machine in useNetworkStatus.ts (5 states, deterministic timers).
  • Banner responsive at --breakpoint-sm (640 px).
  • aria-live semantics per state (assertive for offline, polite for the rest).
  • Mounted in Layout.tsx above <main>.
  • Skip-link still bypasses banner (asserted).
  • useTransactionStatus.ts reports lateness / error to the singleton store.
  • CreateStreamModal.tsx shows at-risk marker on single-stream AND bulk-dry-run submit.
  • Tests: state machine, render, contrast, layout mount (27 / 27 passing).
  • Contrast ≥ 4.5 : 1 across both themes (asserted).
  • No new third-party deps.
  • WCAG 2.1 AA — keyboard walkthrough: no focus trap; banner host is aria-live only.
  • forced-colors: active fallback path is honoured.

How to test locally

pnpm install
pnpm exec tsc --noEmit -p .
pnpm exec vitest run \
  src/hooks/__tests__/useNetworkStatus.test.ts \
  src/components/__tests__/NetworkStatusBanner.test.tsx \
  src/components/__tests__/NetworkStatusBanner.contrast.test.tsx \
  src/components/__tests__/NetworkStatusBanner.layout.test.tsx
# All 27 tests should pass.

Then run pnpm dev, navigate to /app, open DevTools → Application → Service Workers → toggle Offline, and observe the banner appear; flip back to online and observe the chip → pill → dismiss flow.

Related

  • Spec open follow-ups: see docs/NETWORK_STATUS_BANNER_SPEC.md §11.

Adds the app-wide NetworkStatusBanner rendered in src/components/Layout.tsx,
backed by the new src/lib/networkStatus.ts singleton + src/hooks/useNetworkStatus.ts
state machine, that surfaces five states (online-nominal [hidden],
slow, offline, reconnecting, reconnected-confirmation). Slow is derived from
latency windows reported by useTransactionStatus; offline from navigator.onLine
and RPC errors. The reconnecting -> reconnected-confirmation -> dismiss flow
uses a 4 s confirmation pill. Layout placement is above <main> and never
intercepts the existing skip-link. Per-state aria-live levels (polite for
slow/reconnecting/reconnected; assertive for offline) and >= 4.5:1 contrast
verified in both themes. CreateStreamModal reads isAtRisk and adds
data-at-risk + a label suffix while offline/reconnecting. Spec:
docs/NETWORK_STATUS_BANNER_SPEC.md.
PR_DESCRIPTION.md at the repo root is a generic / stale file unrelated
to this task and not needed for the implementation here; the analogous
per-task record already lives at docs/NETWORK_STATUS_BANNER_SPEC.md
and on the GitHub PR description for Fluxora-Org#1044 (closes Fluxora-Org#1044).
@drips-wave

drips-wave Bot commented Aug 1, 2026

Copy link
Copy Markdown

@stephaniepez21-art Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design a network-status banner for slow, offline, and reconnecting RPC states across /app pages

2 participants