From ff61366543ee51355fe6712769b68ea3e7839271 Mon Sep 17 00:00:00 2001 From: csysp Date: Sat, 14 Mar 2026 10:16:04 -0600 Subject: [PATCH] fix: replace array-index entity IDs with stable keys for GDELT and news popups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit selectedEntity.id was stored as a numeric array index into data.gdelt[] and data.news[]. After any data refresh those arrays rebuild, so the stored index pointed to a different item — showing wrong popup content. GDELT features now use g.properties?.name || String(g.geometry.coordinates) as a stable id; popups resolve via find(). News popups resolve via find() matching alertKey. ThreatMarkers emits alertKey string instead of originalIdx. ThreatMarkerProps updated: id: number → id: string | number. Co-Authored-By: Claude Sonnet 4.6 Former-commit-id: c2bfd0897a9ebd27e7c905ea3ac848a89883f140 --- frontend/src/components/MaplibreViewer.tsx | 26 +++++++++++++--------- frontend/src/components/map/MapMarkers.tsx | 10 ++++----- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/MaplibreViewer.tsx b/frontend/src/components/MaplibreViewer.tsx index 8e92d46..fee9181 100644 --- a/frontend/src/components/MaplibreViewer.tsx +++ b/frontend/src/components/MaplibreViewer.tsx @@ -1037,7 +1037,7 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele if (!inView(gLat, gLng)) return null; return { type: 'Feature', - properties: { id: i, type: 'gdelt', title: g.title }, + properties: { id: g.properties?.name || String(g.geometry.coordinates), type: 'gdelt', title: g.title }, geometry: g.geometry }; }).filter(Boolean) @@ -2232,10 +2232,13 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele })()} { - selectedEntity?.type === 'gdelt' && data?.gdelt?.[selectedEntity.id as number] && ( + selectedEntity?.type === 'gdelt' && (() => { + const item = data?.gdelt?.find((g: any) => (g.properties?.name || String(g.geometry?.coordinates)) === selectedEntity.id); + if (!item) return null; + return ( onEntityClick?.(null)} @@ -2252,14 +2255,14 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele
LOCATION - {data.gdelt[selectedEntity.id as number].properties?.name || 'UNKNOWN REGION'} + {item.properties?.name || 'UNKNOWN REGION'}
- LATEST REPORTS: ({data.gdelt[selectedEntity.id as number].properties?.count || 1}) + LATEST REPORTS: ({item.properties?.count || 1})
{(() => { - const urls: string[] = data.gdelt[selectedEntity.id as number].properties?._urls_list || []; - const headlines: string[] = data.gdelt[selectedEntity.id as number].properties?._headlines_list || []; + const urls: string[] = item.properties?._urls_list || []; + const headlines: string[] = item.properties?._headlines_list || []; if (urls.length === 0) return No articles available.; return urls.map((url: string, idx: number) => { const headline = headlines[idx] || ''; @@ -2290,7 +2293,8 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele
- ) + ); + })() } { @@ -2336,8 +2340,8 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele } { - selectedEntity?.type === 'news' && data?.news?.[selectedEntity.id as number] && (() => { - const item = data.news[selectedEntity.id as number]; + selectedEntity?.type === 'news' && (() => { + const item = data?.news?.find((n: any) => (n.alertKey || `${n.title}|${n.coords?.[0]},${n.coords?.[1]}`) === selectedEntity.id); let threatColor = "text-yellow-400"; let borderColor = "border-yellow-800"; let bgHeaderColor = "bg-yellow-950/40"; diff --git a/frontend/src/components/map/MapMarkers.tsx b/frontend/src/components/map/MapMarkers.tsx index a2b9f1d..0e40ae0 100644 --- a/frontend/src/components/map/MapMarkers.tsx +++ b/frontend/src/components/map/MapMarkers.tsx @@ -168,7 +168,7 @@ interface ThreatMarkerProps { spreadAlerts: any[]; viewState: ViewState; selectedEntity: any; - onEntityClick?: (entity: { id: number; type: string } | null) => void; + onEntityClick?: (entity: { id: string | number; type: string } | null) => void; onDismiss?: (alertKey: string) => void; } @@ -176,22 +176,20 @@ export function ThreatMarkers({ spreadAlerts, viewState, selectedEntity, onEntit return ( <> {spreadAlerts.map((n: any) => { - const idx = n.originalIdx; const count = n.cluster_count || 1; const score = n.risk_score || 0; const riskColor = getRiskColor(score); + const alertKey = n.alertKey || `${n.title}|${n.coords?.[0]},${n.coords?.[1]}`; let isVisible = viewState.zoom >= 1; if (selectedEntity) { if (selectedEntity.type === 'news') { - if (selectedEntity.id !== idx) isVisible = false; + if (selectedEntity.id !== alertKey) isVisible = false; } else { isVisible = false; } } - const alertKey = n.alertKey || `${n.title}|${n.coords?.[0]},${n.coords?.[1]}`; - return ( { e.originalEvent.stopPropagation(); - onEntityClick?.({ id: idx, type: 'news' }); + onEntityClick?.({ id: alertKey, type: 'news' }); }} >