Problem Statement / Feature Objective
The real-time dashboard relies on a persistent WebSocket connection to a load-balanced backend that streams telemetry and event data. Network partitions, server rolls, or DNS hiccups cause disconnections. A naive reconnect floods the server with retry storms and may land on a different backend node, losing in-memory subscription state. An adaptive reconnection layer must implement capped exponential backoff with full-jitter randomization, sticky session cookies, and a graceful subscription-recovery handshake so that the operator experiences at most 3 seconds of data loss during a failure cascade.
Technical Invariants & Bounds
- Initial backoff: 500 ms; cap: 30 seconds; multiplier: 2.0; full-jitter: random(0, min(cap, base * 2^attempt)).
- Maximum attempts before declaring terminal failure: 12 (covers ~3.5 minutes of reconnection). After 12, surface a persistent error banner.
- Subscription recovery: the client must buffer the last 500 telemetry frames (each ~200 bytes to 100 KB) and replay them as a batch in the "recovery" field of the reconnection handshake.
- WebSocket URL: wss://api.utilityprotocol.com/ws?nodeId={sticky} where "sticky" is a server-assigned node ID stored in sessionStorage.
- Heartbeat: server sends "ping" every 15 seconds; client must respond with "pong" within 5 seconds or the connection is considered dead. Missed heartbeat count is exposed to the reconnection state machine.
Codebase Navigation Guide
- src/hooks/useWebSocket.ts - Current WebSocket hook; needs reconnection logic extracted into a state machine.
- src/services/reconnectMachine.ts - XState or custom finite state machine for reconnect phases: Connected, Disconnecting, Waiting, Connecting, Recovering, Failed.
- src/utils/backoff.ts - Exponential backoff with full-jitter calculation.
- src/store/slices/connectionSlice.ts - Redux slice tracking connection status, latency, node ID, missed heartbeats.
- src/components/panels/ConnectionStatusBar.tsx - Fixed top bar showing connection quality (green/yellow/red).
- src/utils/frameBuffer.ts - Circular buffer for telemetry frame replay during recovery.
Implementation Blueprint
- Implement fullJitterBackoff(attempt, base, cap) in src/utils/backoff.ts.
- Build the state machine src/services/reconnectMachine.ts with states: idle, connecting, connected, reconnecting(attempt), recovering, failed. Transitions: DISCONNECTED, HEARTBEAT_TIMEOUT, RECOVERY_SUCCESS, MAX_ATTEMPTS.
- Refactor useWebSocket to delegate connection lifecycle to the machine. On entering reconnecting, compute delay via full-jitter, schedule a reconnect with setTimeout.
- On successful reconnection (state recovering), send a JSON recovery frame: { type: 'recovery', lastSequenceId, frames: frameBuffer.drain() }. Wait for server to acknowledge with { type: 'recovery_ack', missedCount }.
- Backfill missed frames by requesting a REST endpoint GET /ws/replay?from={sequenceId}&to={serverCurrentSeq} if missedCount > 0.
- Maintain the frame buffer in frameBuffer.ts as a ring buffer of 500 TelemetryFrame objects. Push on every incoming frame; drain on recovery send.
- Dispatch connectionSlice actions for status transitions so UI components can react (e.g., dim charts during reconnect).
Problem Statement / Feature Objective
The real-time dashboard relies on a persistent WebSocket connection to a load-balanced backend that streams telemetry and event data. Network partitions, server rolls, or DNS hiccups cause disconnections. A naive reconnect floods the server with retry storms and may land on a different backend node, losing in-memory subscription state. An adaptive reconnection layer must implement capped exponential backoff with full-jitter randomization, sticky session cookies, and a graceful subscription-recovery handshake so that the operator experiences at most 3 seconds of data loss during a failure cascade.
Technical Invariants & Bounds
Codebase Navigation Guide
Implementation Blueprint