Detailed specification of every realtime surface from 05. All behavior is tied to
source facts where they exist; client-side behavior not in source is marked
"(proposed)" or "(planned)". This is the largest spec of the module — it is the
contract the Flutter/web client must implement.
Top app bar trailing slot on mobile; app-bar region on desktop; offline state promotes
to a full-width AppConnectionBanner under the app bar (see 00-shared/03 for primitives).
Status is global (one app-level state), never per-screen.
No modal ever blocks on connection state; the layer is passive (see 09).
Stale-data rule: while offline/reconnecting, any screen showing live-fed values
renders the offline banner; values are not cleared (graceful degradation).
a11y: every state change announces via SemanticsService.liveRegion; dot never
color-only (00-shared/09).
Badge counts notification.created events since last list open (client-side count).
Cap display at 99+.
On bell open → fetch list via REST (authoritative) and reset count — events are
ephemeral; the list is source of truth (bridge is fire-and-forget,
ws-bridge.service.ts:16-22).
tap status dot (desktop) / long-press dot (mobile)
Content
state chip; "last event: {eventType} at {occurredAt}" (from last received envelope, ws-bridge.service.ts:17-21); reconnect attempt count; server clock delta
Client-side only today: it renders what the socket receives. Server-side metrics
(per-connection views, room members) are forward-looking — the gateway logs
connect/disconnect only (ws.gateway.ts:51-53,59-61).
Gate with admin role: reuse existing RBAC admin permission (rbac module); no ws.*
permission exists yet (permissions.constants.ts:1-97 — verified absent, see 12).
before reconnect, check access-token expiry; refresh via REST (/auth/refresh) then open socket (source: handshake requires valid token, ws.gateway.ts:42-44)
Rooms to re-join on reconnect
tenant room is automatic (ws.gateway.ts:50); extra rooms must be re-subscribed by client (ws.gateway.ts:63-68)
Multi-tab
one socket per tab; dedupe events by envelope (eventType + correlationId — note correlationId is NOT forwarded; use entityId until server forwards it)
Background tab
socket stays; if OS suspends, reconnect flow handles it; badge reconciled on resume
Debug/data
never log payload contents; log eventType + occurredAt only (10 §8)