WebSocket Telemetry Stream Frame Desync in Canvas Real-Time Resource Chart
Problem Statement
The real-time resource consumption chart in src/components/dashboard/ResourceTelemetryChart.tsx renders streaming telemetry as a multi-line SVG/Canvas chart using Chart.js with a streaming plugin. The WebSocket handler at src/hooks/useTelemetryStream.ts:55 receives binary-framed telemetry messages, parses them, and pushes data points to the chart's data store via chartRef.current.data.datasets[i].data.push(). The WebSocket connection uses a custom binary framing protocol (sequence number + payload) but the sequence number validation at src/utils/telemetry/frameValidator.ts:30 only checks for gaps > 1 (i.e., dropped frames), not for frame reordering. When the WebSocket reconnects after a brief network interruption (common in mobile DePIN deployments), the new stream's sequence counter resets to 0. The validator sees lastSequence = 5201 followed by newSequence = 3 and treats it as a drop of 5198 frames, not recognizing the counter reset. The chart data store receives 5198 synthetic "gap-fill" null data points, causing the canvas to render a flat horizontal line across the chart for 86 seconds at 60fps (5198 / 60 ≈ 86s), during which all real telemetry is visually suppressed.
State Invariants & Parameters
- Sequence number:
u16 (wraps at 65535)
- Max sequence gap before reset detection: 1000 frames
- Reconnect behavior: new TCP connection, counter resets to 0
- Chart data store:
Chart.js reactive array (max 10,000 points)
- Gap-fill behavior: insert
null for each missing sequence
- Frame rate: 60 updates/s (WebSocket batch)
- Invariant:
chart_data.length == elapsed_time * 60 (no synthetic insertion)
Affected Code Paths
src/hooks/useTelemetryStream.ts:50-100 — WebSocket message handler pushing to chart
src/utils/telemetry/frameValidator.ts:25-55 — Sequence checking with no reset detection
src/components/dashboard/ResourceTelemetryChart.tsx:60-110 — Chart component rendering null data points
src/hooks/tests/useTelemetryStream.test.ts — No reconnection-with-reset test
Resolution Blueprint
- Implement stream epoch detection in
frameValidator.ts: newConnection flag is set on WebSocket onopen. On the first message after reconnection, the validator accepts any sequence number (skipping gap check for the first frame of a new connection epoch). The epoch is identified by a monotonic connectionId (UUID per onopen event).
- Replace
null gap-fill with decimated interpolation: instead of inserting nulls, compute (prevValue + nextValue) / 2 for each missing point and fill with interpolated values. Mark interpolated points with a synthetic: true flag so they can be rendered with dashed lines or lower opacity.
- Cap the maximum gap-fill window to 300 frames (5 seconds at 60fps). If the gap exceeds 300, insert a
NaN sentinel that the chart renderer treats as a gap separator (disconnected line segments). The 300-frame cap prevents the chart from freezing for 86s.
- Add a
SequenceTracker class that maintains a sliding window of the last 100 sequence numbers. On reconnect, it learns the new baseline within 100 frames and adapts. Gap detection uses median-absolute-deviation (MAD) from the window rather than raw subtraction.
- Add a benchmark test in
tests/benchmarks/telemetry_chart_reconnect.ts that simulates 10 reconnection cycles with counter resets, measuring the time until the chart displays live data again (target: < 500ms).
Labels
Complexity: Hardcore
Layer: Core-Engine
Type: Race-Condition
WebSocket Telemetry Stream Frame Desync in Canvas Real-Time Resource Chart
Problem Statement
The real-time resource consumption chart in
src/components/dashboard/ResourceTelemetryChart.tsxrenders streaming telemetry as a multi-line SVG/Canvas chart usingChart.jswith a streaming plugin. The WebSocket handler atsrc/hooks/useTelemetryStream.ts:55receives binary-framed telemetry messages, parses them, and pushes data points to the chart's data store viachartRef.current.data.datasets[i].data.push(). The WebSocket connection uses a custom binary framing protocol (sequence number + payload) but the sequence number validation atsrc/utils/telemetry/frameValidator.ts:30only checks for gaps > 1 (i.e., dropped frames), not for frame reordering. When the WebSocket reconnects after a brief network interruption (common in mobile DePIN deployments), the new stream's sequence counter resets to 0. The validator seeslastSequence = 5201followed bynewSequence = 3and treats it as a drop of 5198 frames, not recognizing the counter reset. The chart data store receives 5198 synthetic "gap-fill" null data points, causing the canvas to render a flat horizontal line across the chart for 86 seconds at 60fps (5198 / 60 ≈ 86s), during which all real telemetry is visually suppressed.State Invariants & Parameters
u16(wraps at 65535)Chart.jsreactive array (max 10,000 points)nullfor each missing sequencechart_data.length == elapsed_time * 60(no synthetic insertion)Affected Code Paths
src/hooks/useTelemetryStream.ts:50-100— WebSocket message handler pushing to chartsrc/utils/telemetry/frameValidator.ts:25-55— Sequence checking with no reset detectionsrc/components/dashboard/ResourceTelemetryChart.tsx:60-110— Chart component rendering null data pointssrc/hooks/tests/useTelemetryStream.test.ts— No reconnection-with-reset testResolution Blueprint
frameValidator.ts:newConnectionflag is set on WebSocketonopen. On the first message after reconnection, the validator accepts any sequence number (skipping gap check for the first frame of a new connection epoch). The epoch is identified by a monotonicconnectionId(UUID peronopenevent).nullgap-fill with decimated interpolation: instead of inserting nulls, compute(prevValue + nextValue) / 2for each missing point and fill with interpolated values. Mark interpolated points with asynthetic: trueflag so they can be rendered with dashed lines or lower opacity.NaNsentinel that the chart renderer treats as a gap separator (disconnected line segments). The 300-frame cap prevents the chart from freezing for 86s.SequenceTrackerclass that maintains a sliding window of the last 100 sequence numbers. On reconnect, it learns the new baseline within 100 frames and adapts. Gap detection uses median-absolute-deviation (MAD) from the window rather than raw subtraction.tests/benchmarks/telemetry_chart_reconnect.tsthat simulates 10 reconnection cycles with counter resets, measuring the time until the chart displays live data again (target: < 500ms).Labels
Complexity: HardcoreLayer: Core-EngineType: Race-Condition