-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Problem
The map and sidebar panels exist in total isolation. There is zero communication between them.
- Clicking "Ukraine" in the Entity Tracker panel → nothing happens on the map
- Clicking a country on the map → nothing happens in the panels
- The Instability Index panel shows risk scores as a flat text list, but the map shows no corresponding choropleth coloring
- There is no URL state sharing — if you zoom to the Middle East and enable specific layers, sharing the URL shows the recipient the default view
The App.ts wiring (lines 66-68) sends source data to panels via sourceManager.onItems(), but the map receives data only through its own layer system — no bridge connects the two. The SourceItem interface already has entities?: string[] and metadata?: Record<string, unknown> — fields designed for exactly this kind of cross-referencing but never used for map-panel linking.
This isolation misses the core value of having a map AND panels side-by-side: cross-referencing. The whole point of a "mission control" dashboard is that selecting something in one view highlights it everywhere else. This is what creates the "wow" experience that makes users share the project.
Visual Mockup
Choropleth Heatmap for Instability Index
When the instability-index panel has data, the map automatically colors every country polygon based on its risk score. A continuous color scale maps scores 0-10:
- 0-3 (Low): Cool blue
#1a5276 - 4-6 (Moderate): Amber
#f39c12 - 7-10 (High): Hot red
#e74c3c
Countries without data remain at the default dark-matter style. Country boundaries visible with 1px strokes. A small color scale legend in the bottom-right map corner shows the gradient with labels "Low Risk" / "Moderate" / "High Risk".
Panel → Map Linking
Each country name in InstabilityIndexPanel becomes clickable. Clicking "Ukraine" triggers a flyTo animation centering the map on Ukraine at zoom 5. The country polygon highlights with a bright accent border (2px var(--accent) stroke) for 5 seconds, then fades back.
Each entity name in EntityTrackerPanel is also clickable. If the entity has known coordinates (via a bundled geocoding table of ~200 major countries/cities), clicking pans the map there.
Map → Panel Linking
Clicking a country on the map does two things:
- Highlights the country's entry in the Instability Index panel (bright left border + subtle background glow)
- Filters the News Feed to show only items whose
entitiesarray contains that country name, with a "Showing: Ukraine" indicator and an "✕ clear filter" button to restore the full feed
AI-Annotated News Items
- Each news item gains a small inline sentiment tag using the existing
item.sentimentfield (green/neutral/red pill badges) - Items flagged by AI focal point detection get a "FOCAL" badge — a small red pill that draws the eye to the most important stories
URL State Sharing
The current view state is encoded in the URL hash:
#center=30.5,31.2&zoom=5&layers=instability,conflicts&filter=Ukraine&panel=instability-index
- Map center, zoom, pitch, bearing
- Enabled layer names
- Active panel filters and highlighted entity
- Uses
history.replaceState(notpushState) to avoid polluting browser history - On page load, URL hash is parsed and the entire view is restored
- Copy URL → share → recipient sees your exact view
Implementation Approach
Phase 1: Event bus for cross-component communication
- Create
src/core/events/EventBus.ts— typed pub/sub - Events:
entity:select,entity:deselect,map:click,map:flyTo,panel:filter,panel:highlight,state:change - Wire into
App.ts, pass toMapEngine,PanelManager, and all panels
Phase 2: Country geocoding table
- Create
data/geo/country-centroids.json— ~200 country/territory names →[lng, lat]centroids + ISO codes - Static ~15KB file from public domain Natural Earth data
Phase 3: World countries GeoJSON for choropleth
- Add
data/geo/countries-110m.geojson— Natural Earth 110m simplified world polygons (~500KB) with ISO codes - Required for choropleth to have polygons to color
Phase 4: Choropleth layer type
- Create
src/core/map/layers/ChoroplethLayer.tsimplementingLayerPlugin - Dynamic polygon coloring via MapLibre's
data-driven stylingwithmatch/interpolateexpressions - Colors based on
feature.properties.iso_a3matched against instability scores - Register as
'choropleth'in layer registry - Add
'choropleth'toLayerSchema.typeenum inforge/src/config/schema.ts
Phase 5: MapEngine flyTo and highlight methods
Add to MapEngine:
flyTo(center: [number, number], zoom?: number): voidhighlightFeature(featureId: string, duration?: number): voidsetCountryHighlight(countryCode: string): void— temporary polygon style changeonClick(handler: (features, lngLat) => void): void— expose map clicks
Phase 6: Make panel items interactive
- InstabilityIndexPanel: clickable country rows → emit
entity:selectwith name + coordinates (from centroid lookup) - EntityTrackerPanel: clickable entity rows → emit
entity:select - NewsFeedPanel: add
filter(entityName)method that shows only matching items; add sentiment/focal badges; add clear-filter button
Phase 7: App.ts wiring
- Listen
entity:select→ callmapEngine.flyTo()+mapEngine.setCountryHighlight() - Listen
map:click→ callpanelManager.highlightEntity()+newsFeedPanel.filter() - On instability score updates → pass scores to choropleth layer
Phase 8: URL state manager
- Create
src/core/state/URLStateManager.ts - Parse
window.location.hashon init:center,zoom,pitch,bearing,layers,filter,highlight setState(partial)→ merge +history.replaceState+ update hash- Subscribe to map
moveendand layer toggle events to keep URL in sync
Acceptance Criteria
- Instability Index scores render as a choropleth heatmap on the map (countries colored by risk)
- Color scale legend visible in the map corner
- Clicking a country name in Instability Index panel → map flies to that country with highlight
- Clicking a country on the map → highlights entry in sidebar panels
- Clicking a country on the map → filters news feed to related items
- "Clear filter" control restores the full news feed
- News items show inline sentiment tags and "FOCAL" badges for AI-flagged items
- Map state (center, zoom, layers) encoded in URL hash
- Opening URL with hash parameters restores the encoded view state
-
choroplethadded as valid layer type in config schema - Country centroid data covers at least 193 UN member states
- Event bus architecture allows future panels/plugins to subscribe to cross-component events
WorldMonitor Reference
Directly inspired by WorldMonitor's Country Instability Index choropleth heatmap and URL state sharing (encoding map center, zoom, and active layers in URL parameters). This goes further by adding bidirectional map-panel linking — clicking something in a panel highlights it on the map AND clicking on the map filters panels. WorldMonitor does not fully implement this bidirectional cross-referencing, making this a differentiator for monitor-forge rather than just catch-up.