From 97f1eafc6f988c5b3b713039eef16dee21a5eeb4 Mon Sep 17 00:00:00 2001 From: jedarden Date: Tue, 7 Apr 2026 11:03:52 -0400 Subject: [PATCH] feat: process buffered events from delta WebSocket updates Events (zone entries/exits, portal crossings, presence transitions) were already broadcast immediately via BroadcastEvent, but the buffered copies included in the 10 Hz delta tick were silently dropped by handleIncrementalUpdate. Now delta events are processed through the same handleEventMessage path, with dedup to avoid double-processing when both immediate and delta copies arrive. Co-Authored-By: Claude Opus 4.6 --- dashboard/js/app.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/dashboard/js/app.js b/dashboard/js/app.js index 9dcb65d..9fcfc93 100644 --- a/dashboard/js/app.js +++ b/dashboard/js/app.js @@ -58,7 +58,11 @@ bleDevices: new Map(), // MAC -> { mac, name, rssi, last_seen, label, blob_id } // Alert tracking alerts: new Map(), // id -> { id, ts, severity, description, acknowledged } - unacknowledgedCount: 0 + unacknowledgedCount: 0, + // Event dedup: set of recently processed event IDs to avoid double-processing + // from immediate broadcast + delta buffering + recentEventIDs: new Set(), + recentEventIDsPruneAt: 0 }; // ============================================ @@ -431,6 +435,19 @@ if (!msg.event) return; const event = msg.event; + + // Dedup: skip if we already processed this event (from immediate broadcast or prior delta) + const eid = String(event.id); + if (state.recentEventIDs.has(eid)) return; + state.recentEventIDs.add(eid); + + // Prune old IDs every 30 seconds to prevent unbounded growth + const now = Date.now(); + if (now > state.recentEventIDsPruneAt) { + state.recentEventIDs.clear(); + state.recentEventIDsPruneAt = now + 30000; + } + console.log('[Spaxel] Event:', event.kind, 'in', event.zone, 'by', event.person_name || 'blob #' + event.blob_id); // Log to timeline @@ -900,6 +917,13 @@ Viz3D.handleZoneUpdate(msg.zones); } } + + // Events buffered since last tick (presence transitions, zone entries/exits, portal crossings) + if (msg.events && Array.isArray(msg.events)) { + msg.events.forEach(function (evt) { + handleEventMessage({ type: 'event', event: evt }); + }); + } } // applyMotionState updates a link's motion fields; returns true if it changed.