From ff45193ba2f2ebfe3e26005e2f3c6ed6b18ce388 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Fri, 2 Jan 2026 13:06:52 +0100 Subject: [PATCH] feat: Grey out stale locations, remove indoor map, dedupe pings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Stale locations (>5 min old) are now greyed out on the map with reduced opacity and hover tooltip showing time since last seen 2. Removed indoor map button and related UI elements 3. Location request pings now deduplicate by participant name to avoid pinging the same user multiple times 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/components/map/DualMapView.tsx | 29 +--------------------------- src/components/map/MapView.tsx | 31 ++++++++++++++++++++++++++++-- sync-server/server.js | 23 ++++++++++++++++++++-- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/components/map/DualMapView.tsx b/src/components/map/DualMapView.tsx index 205b9a1..89be594 100644 --- a/src/components/map/DualMapView.tsx +++ b/src/components/map/DualMapView.tsx @@ -209,38 +209,11 @@ export default function DualMapView({ )} - {/* Indoor Map button - switch to indoor view */} - {activeView === 'outdoor' && ( - - )} - - {/* Auto-mode indicator */} - {mode === 'auto' && ( -
- Auto-detecting location -
- )} {/* Venue proximity indicator */} {currentLocation && isInC3NavArea(currentLocation.latitude, currentLocation.longitude) && activeView === 'outdoor' && ( -
+
You're at the venue! -
)}
diff --git a/src/components/map/MapView.tsx b/src/components/map/MapView.tsx index f16c4ce..b1b7c5a 100644 --- a/src/components/map/MapView.tsx +++ b/src/components/map/MapView.tsx @@ -57,6 +57,12 @@ function isValidCoordinate(lat: number, lng: number): boolean { ); } +// Check if a participant's location is stale (older than 5 minutes) +const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes +function isLocationStale(lastSeen: number): boolean { + return Date.now() - lastSeen > STALE_THRESHOLD_MS; +} + export default function MapView({ participants, waypoints = [], @@ -202,15 +208,36 @@ export default function MapView({ let marker = currentMarkers.get(participant.id); if (marker) { - // Update existing marker position + // Update existing marker position and stale status marker.setLngLat([longitude, latitude]); + const el = marker.getElement(); + const isStale = isLocationStale(participant.lastSeen.getTime()); + if (isStale) { + el.style.backgroundColor = '#6b7280'; + el.style.opacity = '0.6'; + el.title = `${participant.name} - last seen ${Math.round((Date.now() - participant.lastSeen.getTime()) / 60000)} min ago`; + } else { + el.style.backgroundColor = participant.color; + el.style.opacity = '1'; + el.title = participant.name; + } } else { // Create new marker const el = document.createElement('div'); el.className = 'friend-marker'; - el.style.backgroundColor = participant.color; el.innerHTML = participant.emoji; + const isStale = isLocationStale(participant.lastSeen.getTime()); + + if (isStale) { + // Grey out stale locations + el.style.backgroundColor = '#6b7280'; // gray-500 + el.style.opacity = '0.6'; + el.title = `${participant.name} - last seen ${Math.round((Date.now() - participant.lastSeen.getTime()) / 60000)} min ago`; + } else { + el.style.backgroundColor = participant.color; + } + if (participant.id === currentUserId) { el.classList.add('sharing'); } diff --git a/sync-server/server.js b/sync-server/server.js index 3112be2..cc6cd88 100644 --- a/sync-server/server.js +++ b/sync-server/server.js @@ -485,15 +485,34 @@ const server = createServer(async (req, res) => { let pushSent = 0; let pushFailed = 0; - // First, send WebSocket message to all connected clients in the room + // Get room to access participant names for deduplication + const room = rooms.get(roomSlug); + + // First, send WebSocket message to connected clients, deduplicated by name const locationRequestMsg = JSON.stringify({ type: 'request_location' }); + const pingedNames = new Set(); + for (const [ws, clientInfo] of clients.entries()) { if (clientInfo.roomSlug === roomSlug && ws.readyState === 1) { + // Get participant name for this client + const participant = room?.participants?.[clientInfo.participantId]; + const name = participant?.name; + + // Skip if we've already pinged this name + if (name && pingedNames.has(name)) { + console.log(`[${roomSlug}] Skipping duplicate ping for: ${name}`); + continue; + } + ws.send(locationRequestMsg); wsSent++; + + if (name) { + pingedNames.add(name); + } } } - console.log(`[${roomSlug}] Location request via WebSocket: ${wsSent} clients`); + console.log(`[${roomSlug}] Location request via WebSocket: ${wsSent} clients (deduped by name)`); // Then, send push notifications to offline subscribers const subs = pushSubscriptions.get(roomSlug);