Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

06 — Screen Specifications (WS / Realtime Module)

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.


§1 Connection Status Indicator

1.1 Placement & anatomy

  • 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).
  • Contains: status dot (8 dp) + optional label "Live" / "Offline — retrying…".

1.2 State machine (client)

StateTriggerVisualBehavior
idleapp start, before first socket opendot greynothing
connectingsocket open initiateddot amber, pulsingno banner
connectedhandshake OK — server joined tenant room (ws.gateway.ts:50)dot greentooltip "Live"; banner hidden
reconnectingsocket.io reconnect attemptdot amber pulsebanner only after 1st failed attempt
offlineheartbeat loss / transport error, retries exhausteddot red + banner "Live updates paused"manual "Retry now" affordance + auto backoff continues
error-authhandshake rejected → server client.disconnect() (ws.gateway.ts:54-56)dot red + banner "Session expired"client refreshes access token via REST, reopens socket; if refresh fails → sign-out flow

1.3 Rules

  • 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).

1.4 Source grounding

  • Auth failure path: missing token → UnauthorizedException('Missing token'), catch → client.disconnect() (ws.gateway.ts:37-40,54-56).
  • No server session resumption: reconnect = full re-handshake (see 03 J4).

§2 Notification Bell + Toast

2.1 Bell badge

  • 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).

2.2 Toast

  • Shown for notification types marked high-priority (module config, (planned) — the notifications module owns priority semantics, IMPLEMENTATION_PLAN.md:231).
  • One toast at a time; queue others; auto-dismiss 5 s; tap → navigate to target route.
  • While the user is typing/editing: toast never steals focus (see 10 §2).

2.3 Edge cases

CaseBehavior
Duplicate event (multi-tab, 02 §cross-persona)dedupe by payload.{entityId}+eventType within a short window; single toast
Event for soft-deleted entitytoast still shows; tap → REST 404 → snackbar "No longer available"
Arrived while offlinenot delivered (no replay); bell reconciled on REST fetch
Malformed envelopeignore + log client-side (defensive; see 14 §Q8)

§3 Live-updating Lists & Dashboard Tiles

3.1 The AppLiveList contract

Applies to: Dashboard overview, Fees, Attendance, Homework, Results, CRM lists.

  1. Screen subscribes (via RealtimeClient stream, 13) to the event families it renders.
  2. On event: match payload.entityId against loaded rows.
    • Hit → update that row in place (m-fast highlight flash).
    • Miss (not loaded or filtered out) → decide by screen rule: if the entity belongs to the current filter, insert; else ignore.
  3. If a screen cannot map the payload to a row (schema drift), it schedules one REST refetch of the current page (bounded: 1 refetch / 5 s / screen).
  4. Sorting/aggregation (e.g. dashboard totals) → recompute locally, do not refetch.
  5. Offline state: keep last known data, banner on; on reconnect → one refetch per visible live screen (eventual consistency, 03 J4 step 5).

3.2 Per-screen event mapping (proposed defaults; align with each module doc)

ScreenEvents to renderAction
Dashboard — fees tilepayment.completedincrement collected total
Dashboard — attendance tileattendance.updatedrefresh today's %
Fees — invoice listpayment.*row status update
Attendance — today listattendance.updatedupdate rows by studentId
Homework — listhomework.publishedinsert row at top
CRM — leadscrm.lead.*row/status update

3.3 Rules

  • Live events never replace the primary load; they mutate an existing snapshot.
  • Never write user-visible "confetti" or full-screen overlays on events — 09 keeps the layer passive.
  • Pull-to-refresh stays available and forces a REST refetch regardless of socket state.

§4 Connection Status Detail Sheet (proposed)

FieldSpec
Entrytap status dot (desktop) / long-press dot (mobile)
Contentstate chip; "last event: {eventType} at {occurredAt}" (from last received envelope, ws-bridge.service.ts:17-21); reconnect attempt count; server clock delta
Actions"Retry now" (if offline), "Open debug view" (admin only, §5)
Empty
Motionsheet slides m-base; content fades in m-fast
a11ysheet is a dialog region; focus first action

§5 Realtime Admin Debug View /admin/realtime (proposed)

5.1 Layout

  • Header: connection state + socket count for this tenant (server-provided — see note), pause/resume toggle, eventType filter chips.
  • Body: AppEventLog — monospace rows: time | eventType | payload.size | entityId.
  • Footer: session token expiry countdown (client-side check against JWT_ACCESS_SECRET expiry from the auth store).

5.2 Behavior

  • Log buffers last 500 events client-side; pause stops render (not capture).
  • Filter by eventType substring; clear button.
  • Row tap → payload pretty-printed in a bottom sheet.

5.3 Notes

  • 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).

§6 Cross-cutting specifications

TopicSpec
Event orderingsocket.io preserves per-connection order; across reconnects there is no ordering guarantee — reconcile with REST
Envelope validationclient must validate {eventType: string, occurredAt: string, payload: object}; reject anything else (ws-bridge.service.ts:17-21)
Token refresh mid-sessionbefore 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 reconnecttenant room is automatic (ws.gateway.ts:50); extra rooms must be re-subscribed by client (ws.gateway.ts:63-68)
Multi-tabone socket per tab; dedupe events by envelope (eventType + correlationId — note correlationId is NOT forwarded; use entityId until server forwards it)
Background tabsocket stays; if OS suspends, reconnect flow handles it; badge reconciled on resume
Debug/datanever log payload contents; log eventType + occurredAt only (10 §8)