docs/Docs_To_Review/PANELS.md
World Monitor — Config-driven panel architecture powering three site variants.
Source of truth:
src/config/panels.ts· Panel base class:src/components/Panel.ts· App wiring:src/App.ts
World Monitor uses a config-driven panel system where every dashboard tile — from live news feeds to AI insights to market data — is declared as a PanelConfig entry inside a variant-specific configuration object. The system is designed around three principles:
full, tech, finance) declares its own panel set with variant-appropriate display names and priorities. The build-time environment variable VITE_VARIANT selects which set is exported.localStorage.Panel base class. There is no React/Vue/Angular; DOM construction and updates are imperative.| Variant | Domain | Focus | Panel Count |
|---|---|---|---|
full | worldmonitor.io | Geopolitical intelligence, OSINT, defense | 37 |
tech | tech.worldmonitor.io | Technology, AI/ML, startups, VC | 34 |
finance | finance.worldmonitor.io | Markets, trading, macro, commodities | 29 |
| File | Purpose |
|---|---|
src/config/panels.ts | Central panel & map-layer definitions for all three variants |
src/config/variants/base.ts | Shared VariantConfig interface, STORAGE_KEYS, MONITOR_COLORS, API URLs |
src/config/variants/full.ts | Full variant config with panels, layers, and feeds |
src/config/variants/tech.ts | Tech variant config |
src/config/variants/finance.ts | Finance variant config |
src/components/Panel.ts | Base Panel class (440 lines) — DOM, resize, badges, lifecycle |
src/components/NewsPanel.ts | NewsPanel extending Panel — RSS-driven news tiles |
src/App.ts | Application shell — panel instantiation, data loading, settings modal |
src/types/index.ts | PanelConfig, MapLayers, AppState type definitions |
Every panel is described by a PanelConfig object defined in src/types/index.ts:
export interface PanelConfig {
name: string; // Display name shown in panel header and settings modal
enabled: boolean; // Whether the panel is visible (toggled by user)
priority?: number; // 1 = core (shown early), 2 = supplementary (shown later)
}
name — Variant-specific. The same panel ID (e.g. map) can have different display names across variants: "Global Map" (full), "Global Tech Map" (tech), "Global Markets Map" (finance).enabled — Defaults to true for all shipped panels. Users can disable panels via the Settings modal; the toggled state is persisted.priority — Informational grouping. Priority 1 panels are considered core to the variant's mission; priority 2 panels are supplementary. This does not affect rendering order—order is determined by declaration order in the config object and user drag-and-drop overrides.Panel sets are selected at build time via the SITE_VARIANT constant (derived from VITE_VARIANT env var):
// src/config/panels.ts — variant-aware exports
export const DEFAULT_PANELS =
SITE_VARIANT === 'tech' ? TECH_PANELS :
SITE_VARIANT === 'finance' ? FINANCE_PANELS :
FULL_PANELS;
export const DEFAULT_MAP_LAYERS =
SITE_VARIANT === 'tech' ? TECH_MAP_LAYERS :
SITE_VARIANT === 'finance' ? FINANCE_MAP_LAYERS :
FULL_MAP_LAYERS;
export const MOBILE_DEFAULT_MAP_LAYERS =
SITE_VARIANT === 'tech' ? TECH_MOBILE_MAP_LAYERS :
SITE_VARIANT === 'finance' ? FINANCE_MOBILE_MAP_LAYERS :
FULL_MOBILE_MAP_LAYERS;
Vite tree-shakes the unused variant objects from the production bundle.
Each variant file exports a full VariantConfig object:
// src/config/variants/base.ts
export interface VariantConfig {
name: string; // 'full' | 'tech' | 'finance'
description: string; // Human-readable variant description
panels: Record<string, PanelConfig>; // Panel ID → config
mapLayers: MapLayers; // Desktop default layer toggles
mobileMapLayers: MapLayers; // Mobile default layer toggles
}
The variant config also defines its own FEEDS object (Record<string, Feed[]>) mapping panel IDs to their RSS feed sources. Feeds that don't have a registered panel ID result in auto-generated NewsPanel instances (see Panel Lifecycle).
All persistence keys are centralized in STORAGE_KEYS:
// src/config/variants/base.ts (also re-exported from src/config/panels.ts)
export const STORAGE_KEYS = {
panels: 'worldmonitor-panels', // Panel visibility toggles
monitors: 'worldmonitor-monitors', // Monitor keyword configs
mapLayers: 'worldmonitor-layers', // Map layer toggles
disabledFeeds: 'worldmonitor-disabled-feeds', // Per-source feed disabling
} as const;
Additional keys used outside STORAGE_KEYS:
| Key | Purpose | Managed By |
|---|---|---|
worldmonitor-panel-spans | Panel height/span sizes (1–4) | Panel.ts |
panel-order | Drag-and-drop panel ordering | App.ts |
worldmonitor-variant | Last-active variant (triggers reset on change) | App.ts |
worldmonitor-panel-order-v1.9 | Migration flag for v1.9 panel layout | App.ts |
The monitor palette provides 10 fixed category colors used for user-defined keyword monitors:
export const MONITOR_COLORS = [
'#44ff88', '#ff8844', '#4488ff', '#ff44ff', '#ffff44',
'#ff4444', '#44ffff', '#88ff44', '#ff88ff', '#88ffff',
];
These colors are theme-independent and persist alongside monitor definitions in localStorage.
All panels extend the Panel class defined in src/components/Panel.ts (440 lines). This base class provides the shared DOM structure, interaction patterns, and lifecycle methods.
export interface PanelOptions {
id: string; // Unique panel identifier (matches config key)
title: string; // Display name rendered in header
showCount?: boolean; // Show item count badge in header
className?: string; // Additional CSS class on root element
trackActivity?: boolean; // Enable "new items" badge (default: true)
infoTooltip?: string; // HTML content for methodology tooltip (ℹ️ button)
}
Every panel renders the following DOM tree:
div.panel[data-panel="{id}"]
├── div.panel-header
│ ├── div.panel-header-left
│ │ ├── span.panel-title ← Display name
│ │ ├── div.panel-info-wrapper ← (optional) ℹ️ tooltip
│ │ └── span.panel-new-badge ← (optional) "N new" badge
│ ├── span.panel-data-badge ← live/cached/unavailable indicator
│ └── span.panel-count ← (optional) item count
├── div.panel-content#${id}Content ← Main content area
└── div.panel-resize-handle ← Drag-to-resize handle
| Feature | Description |
|---|---|
| Drag-to-resize | Bottom handle supports mouse + touch. Height maps to span classes (span-1 through span-4). Double-click resets to default. |
| Collapsible | Click header to toggle hidden class on content (handled by CSS). |
| Loading state | showLoading(message?) renders a radar sweep animation with text. Shown by default on construction. |
| Error state | showError(message?) renders error text. showConfigError(message) adds a "Open Settings" button (Tauri desktop). |
| Data badge | setDataBadge(state, detail?) shows live, cached, or unavailable with optional detail text. |
| New badge | setNewBadge(count, pulse?) shows a blue dot with count in the header. Pulses for important updates. |
| Count badge | setCount(n) updates the numeric count in the header (when showCount is enabled). |
| Info tooltip | Hover/click on ℹ️ icon shows methodology explanation. Dismissed on outside click. |
| Throttled content | setContentThrottled(html) buffers DOM writes to one per animation frame (PERF-009). |
| Header error state | setErrorState(hasError, tooltip?) toggles a red header accent for degraded panels. |
Panel height is quantized into 4 span levels:
| Span | Min Height | CSS Class | Description |
|---|---|---|---|
| 1 | default | span-1 | Standard single-row height |
| 2 | 250px | span-2 | Medium — 50px drag triggers |
| 3 | 350px | span-3 | Large — 150px drag triggers |
| 4 | 500px | span-4 | Extra-large — 300px drag triggers |
Span values are persisted per-panel in the worldmonitor-panel-spans localStorage key as a JSON object { [panelId]: spanNumber }.
| Method | Signature | Description |
|---|---|---|
getElement() | (): HTMLElement | Returns the root DOM element |
show() | (): void | Remove hidden class |
hide() | (): void | Add hidden class |
toggle(visible) | (boolean): void | Show or hide |
showLoading(msg?) | (string?): void | Render loading spinner |
showError(msg?) | (string?): void | Render error message |
showConfigError(msg) | (string): void | Render config error with settings button |
setContent(html) | (string): void | Set content innerHTML directly |
setContentThrottled(html) | (string): void | Buffered content update (rAF) |
setCount(n) | (number): void | Update count badge |
setNewBadge(count, pulse?) | (number, boolean?): void | Update new-items badge |
clearNewBadge() | (): void | Hide new badge |
setDataBadge(state, detail?) | (string, string?): void | Update data freshness badge |
clearDataBadge() | (): void | Hide data badge |
setErrorState(err, tip?) | (boolean, string?): void | Toggle header error styling |
getId() | (): string | Return panel ID |
resetHeight() | (): void | Clear saved span, remove span classes |
destroy() | (): void | Remove all event listeners |
The full (geopolitical) variant ships 37 panels focused on OSINT, defense intelligence, geopolitical risk, and global situational awareness.
| # | Panel ID | Display Name | Priority | Component Class | Data Source |
|---|---|---|---|---|---|
| 1 | map | Global Map | 1 | MapContainer | MapLibre + deck.gl |
| 2 | live-news | Live News | 1 | LiveNewsPanel | Multi-source RSS aggregation |
| 3 | live-webcams | Live Webcams | 1 | LiveWebcamsPanel | Curated webcam streams |
| 4 | insights | AI Insights | 1 | InsightsPanel | Groq/OpenRouter LLM summarization |
| 5 | strategic-posture | AI Strategic Posture | 1 | StrategicPosturePanel | Theater posture API |
| 6 | cii | Country Instability | 1 | CIIPanel | Composite instability index |
| 7 | strategic-risk | Strategic Risk Overview | 1 | StrategicRiskPanel | Risk scores API |
| 8 | intel | Intel Feed | 1 | NewsPanel | Intelligence RSS feeds |
| 9 | gdelt-intel | Live Intelligence | 1 | GdeltIntelPanel | GDELT event database |
| 10 | cascade | Infrastructure Cascade | 1 | CascadePanel | Multi-domain cascade analysis |
| 11 | politics | World News | 1 | NewsPanel | Political RSS feeds |
| 12 | middleeast | Middle East | 1 | NewsPanel | Regional RSS feeds |
| 13 | africa | Africa | 1 | NewsPanel | Regional RSS feeds |
| 14 | latam | Latin America | 1 | NewsPanel | Regional RSS feeds |
| 15 | asia | Asia-Pacific | 1 | NewsPanel | Regional RSS feeds |
| 16 | energy | Energy & Resources | 1 | NewsPanel | Energy RSS feeds |
| 17 | gov | Government | 1 | NewsPanel | Government RSS feeds |
| 18 | thinktanks | Think Tanks | 1 | NewsPanel | Think tank RSS feeds |
| 19 | polymarket | Predictions | 1 | PredictionPanel | Polymarket API |
| 20 | commodities | Commodities | 1 | CommoditiesPanel | Yahoo Finance / commodity APIs |
| 21 | markets | Markets | 1 | MarketPanel | Finnhub / Yahoo Finance |
| 22 | economic | Economic Indicators | 1 | EconomicPanel | FRED API |
| 23 | finance | Financial | 1 | NewsPanel | Financial RSS feeds |
| 24 | tech | Technology | 2 | NewsPanel | Technology RSS feeds |
| 25 | crypto | Crypto | 2 | CryptoPanel | CoinGecko API |
| 26 | heatmap | Sector Heatmap | 2 | HeatmapPanel | Market sector data |
| 27 | ai | AI/ML | 2 | NewsPanel | AI/ML RSS feeds |
| 28 | layoffs | Layoffs Tracker | 2 | NewsPanel | Layoffs RSS feeds |
| 29 | monitors | My Monitors | 2 | MonitorPanel | User-defined keyword monitors |
| 30 | satellite-fires | Fires | 2 | SatelliteFiresPanel | NASA FIRMS API |
| 31 | macro-signals | Market Radar | 2 | MacroSignalsPanel | Macro signals API |
| 32 | etf-flows | BTC ETF Tracker | 2 | ETFFlowsPanel | ETF flows API |
| 33 | stablecoins | Stablecoins | 2 | StablecoinPanel | Stablecoin markets API |
| 34 | ucdp-events | UCDP Conflict Events | 2 | UcdpEventsPanel | UCDP API |
| 35 | displacement | UNHCR Displacement | 2 | DisplacementPanel | UNHCR population API |
| 36 | climate | Climate Anomalies | 2 | ClimateAnomalyPanel | Climate anomalies API |
| 37 | population-exposure | Population Exposure | 2 | PopulationExposurePanel | WorldPop exposure API |
Full variant exclusive panels: strategic-posture, cii, strategic-risk, gdelt-intel, cascade, satellite-fires, ucdp-events, displacement, climate, population-exposure, and the regional panels (middleeast, africa, latam, asia).
The tech variant ships 34 panels focused on technology news, AI/ML, startup ecosystems, and developer tooling.
| # | Panel ID | Display Name | Priority | Component Class | Data Source |
|---|---|---|---|---|---|
| 1 | map | Global Tech Map | 1 | MapContainer | MapLibre + deck.gl |
| 2 | live-news | Tech Headlines | 1 | LiveNewsPanel | Tech RSS aggregation |
| 3 | live-webcams | Live Webcams | 2 | LiveWebcamsPanel | Curated webcam streams |
| 4 | insights | AI Insights | 1 | InsightsPanel | Groq/OpenRouter LLM summarization |
| 5 | ai | AI/ML News | 1 | NewsPanel | AI/ML RSS feeds |
| 6 | tech | Technology | 1 | NewsPanel | Technology RSS feeds |
| 7 | startups | Startups & VC | 1 | NewsPanel | Startup RSS feeds |
| 8 | vcblogs | VC Insights & Essays | 1 | NewsPanel | VC blog RSS feeds |
| 9 | regionalStartups | Global Startup News | 1 | NewsPanel | Regional startup RSS feeds |
| 10 | unicorns | Unicorn Tracker | 1 | NewsPanel | Unicorn RSS feeds |
| 11 | accelerators | Accelerators & Demo Days | 1 | NewsPanel | Accelerator RSS feeds |
| 12 | security | Cybersecurity | 1 | NewsPanel | Cybersecurity RSS feeds |
| 13 | policy | AI Policy & Regulation | 1 | NewsPanel | AI policy RSS feeds |
| 14 | regulation | AI Regulation Dashboard | 1 | RegulationPanel | Regulation data |
| 15 | layoffs | Layoffs Tracker | 1 | NewsPanel | Layoffs RSS feeds |
| 16 | markets | Tech Stocks | 2 | MarketPanel | Finnhub / Yahoo Finance |
| 17 | finance | Financial News | 2 | NewsPanel | Financial RSS feeds |
| 18 | crypto | Crypto | 2 | CryptoPanel | CoinGecko API |
| 19 | hardware | Semiconductors & Hardware | 2 | NewsPanel | Hardware RSS feeds |
| 20 | cloud | Cloud & Infrastructure | 2 | NewsPanel | Cloud RSS feeds |
| 21 | dev | Developer Community | 2 | NewsPanel | Developer RSS feeds |
| 22 | github | GitHub Trending | 1 | NewsPanel | GitHub trending API |
| 23 | ipo | IPO & SPAC | 2 | NewsPanel | IPO RSS feeds |
| 24 | polymarket | Tech Predictions | 2 | PredictionPanel | Polymarket API |
| 25 | funding | Funding & VC | 1 | NewsPanel | Funding RSS feeds |
| 26 | producthunt | Product Hunt | 1 | NewsPanel | Product Hunt RSS |
| 27 | events | Tech Events | 1 | TechEventsPanel | Tech events API |
| 28 | service-status | Service Status | 2 | ServiceStatusPanel | Service status API |
| 29 | economic | Economic Indicators | 2 | EconomicPanel | FRED API |
| 30 | tech-readiness | Tech Readiness Index | 1 | TechReadinessPanel | World Bank API |
| 31 | macro-signals | Market Radar | 2 | MacroSignalsPanel | Macro signals API |
| 32 | etf-flows | BTC ETF Tracker | 2 | ETFFlowsPanel | ETF flows API |
| 33 | stablecoins | Stablecoins | 2 | StablecoinPanel | Stablecoin markets API |
| 34 | monitors | My Monitors | 2 | MonitorPanel | User-defined keyword monitors |
Tech variant exclusive panels: startups, vcblogs, regionalStartups, unicorns, accelerators, security, policy, regulation, hardware, cloud, dev, github, funding, producthunt, events, service-status, tech-readiness.
The finance variant ships 29 panels focused on markets, trading, macro indicators, and financial data.
| # | Panel ID | Display Name | Priority | Component Class | Data Source |
|---|---|---|---|---|---|
| 1 | map | Global Markets Map | 1 | MapContainer | MapLibre + deck.gl |
| 2 | live-news | Market Headlines | 1 | LiveNewsPanel | Financial RSS aggregation |
| 3 | live-webcams | Live Webcams | 2 | LiveWebcamsPanel | Curated webcam streams |
| 4 | insights | AI Market Insights | 1 | InsightsPanel | Groq/OpenRouter LLM summarization |
| 5 | markets | Live Markets | 1 | MarketPanel | Finnhub / Yahoo Finance |
| 6 | markets-news | Markets News | 2 | NewsPanel | Markets RSS feeds |
| 7 | forex | Forex & Currencies | 1 | NewsPanel | Forex RSS feeds |
| 8 | bonds | Fixed Income | 1 | NewsPanel | Fixed income RSS feeds |
| 9 | commodities | Commodities & Futures | 1 | CommoditiesPanel | Yahoo Finance / commodity APIs |
| 10 | commodities-news | Commodities News | 2 | NewsPanel | Commodities RSS feeds |
| 11 | crypto | Crypto & Digital Assets | 1 | CryptoPanel | CoinGecko API |
| 12 | crypto-news | Crypto News | 2 | NewsPanel | Crypto RSS feeds |
| 13 | centralbanks | Central Bank Watch | 1 | NewsPanel | Central bank RSS feeds |
| 14 | economic | Economic Data | 1 | EconomicPanel | FRED API |
| 15 | economic-news | Economic News | 2 | NewsPanel | Economic RSS feeds |
| 16 | ipo | IPOs, Earnings & M&A | 1 | NewsPanel | IPO/M&A RSS feeds |
| 17 | heatmap | Sector Heatmap | 1 | HeatmapPanel | Market sector data |
| 18 | macro-signals | Market Radar | 1 | MacroSignalsPanel | Macro signals API |
| 19 | derivatives | Derivatives & Options | 2 | NewsPanel | Derivatives RSS feeds |
| 20 | fintech | Fintech & Trading Tech | 2 | NewsPanel | Fintech RSS feeds |
| 21 | regulation | Financial Regulation | 2 | NewsPanel | Regulation RSS feeds |
| 22 | institutional | Hedge Funds & PE | 2 | NewsPanel | Institutional RSS feeds |
| 23 | analysis | Market Analysis | 2 | NewsPanel | Analysis RSS feeds |
| 24 | etf-flows | BTC ETF Tracker | 2 | ETFFlowsPanel | ETF flows API |
| 25 | stablecoins | Stablecoins | 2 | StablecoinPanel | Stablecoin markets API |
| 26 | gcc-investments | GCC Investments | 2 | InvestmentsPanel | GCC investment data |
| 27 | gccNews | GCC Business News | 2 | NewsPanel | GCC news RSS feeds |
| 28 | polymarket | Predictions | 2 | PredictionPanel | Polymarket API |
| 29 | monitors | My Monitors | 2 | MonitorPanel | User-defined keyword monitors |
Finance variant exclusive panels: markets-news, forex, bonds, commodities-news, crypto-news, centralbanks, economic-news, derivatives, fintech, institutional, analysis, gcc-investments, gccNews.
This matrix shows which panel IDs are available in each variant. Panels that appear in multiple variants may have different display names (see individual variant sections above).
| Panel ID | Full | Tech | Finance | Notes |
|---|---|---|---|---|
map | ✅ | ✅ | ✅ | Different names per variant |
live-news | ✅ | ✅ | ✅ | Different names per variant |
live-webcams | ✅ | ✅ | ✅ | Priority 1 in full, priority 2 in tech/finance |
insights | ✅ | ✅ | ✅ | Different names per variant |
markets | ✅ | ✅ | ✅ | "Markets" / "Tech Stocks" / "Live Markets" |
economic | ✅ | ✅ | ✅ | "Economic Indicators" / "Economic Indicators" / "Economic Data" |
crypto | ✅ | ✅ | ✅ | "Crypto" / "Crypto" / "Crypto & Digital Assets" |
polymarket | ✅ | ✅ | ✅ | "Predictions" / "Tech Predictions" / "Predictions" |
monitors | ✅ | ✅ | ✅ | Identical across all variants |
macro-signals | ✅ | ✅ | ✅ | P2 in full/tech, P1 in finance |
etf-flows | ✅ | ✅ | ✅ | P2 in all variants |
stablecoins | ✅ | ✅ | ✅ | P2 in all variants |
layoffs | ✅ | ✅ | — | P2 in full, P1 in tech |
finance | ✅ | ✅ | — | "Financial" / "Financial News" |
tech | ✅ | ✅ | — | P2 in full, P1 in tech |
ai | ✅ | ✅ | — | "AI/ML" / "AI/ML News" |
heatmap | ✅ | — | ✅ | P2 in full, P1 in finance |
commodities | ✅ | — | ✅ | "Commodities" / "Commodities & Futures" |
ipo | — | ✅ | ✅ | "IPO & SPAC" / "IPOs, Earnings & M&A" |
regulation | — | ✅ | ✅ | "AI Regulation Dashboard" / "Financial Regulation" |
politics | ✅ | — | — | Full only — World News |
middleeast | ✅ | — | — | Full only |
africa | ✅ | — | — | Full only |
latam | ✅ | — | — | Full only |
asia | ✅ | — | — | Full only |
energy | ✅ | — | — | Full only |
gov | ✅ | — | — | Full only |
thinktanks | ✅ | — | — | Full only |
intel | ✅ | — | — | Full only |
gdelt-intel | ✅ | — | — | Full only |
cascade | ✅ | — | — | Full only |
strategic-posture | ✅ | — | — | Full only |
cii | ✅ | — | — | Full only |
strategic-risk | ✅ | — | — | Full only |
satellite-fires | ✅ | — | — | Full only |
ucdp-events | ✅ | — | — | Full only |
displacement | ✅ | — | — | Full only |
climate | ✅ | — | — | Full only |
population-exposure | ✅ | — | — | Full only |
startups | — | ✅ | — | Tech only |
vcblogs | — | ✅ | — | Tech only |
regionalStartups | — | ✅ | — | Tech only |
unicorns | — | ✅ | — | Tech only |
accelerators | — | ✅ | — | Tech only |
security | — | ✅ | — | Tech only |
policy | — | ✅ | — | Tech only |
hardware | — | ✅ | — | Tech only |
cloud | — | ✅ | — | Tech only |
dev | — | ✅ | — | Tech only |
github | — | ✅ | — | Tech only |
funding | — | ✅ | — | Tech only |
producthunt | — | ✅ | — | Tech only |
events | — | ✅ | — | Tech only |
service-status | — | ✅ | — | Tech only |
tech-readiness | — | ✅ | — | Tech only |
markets-news | — | — | ✅ | Finance only |
forex | — | — | ✅ | Finance only |
bonds | — | — | ✅ | Finance only |
commodities-news | — | — | ✅ | Finance only |
crypto-news | — | — | ✅ | Finance only |
centralbanks | — | — | ✅ | Finance only |
economic-news | — | — | ✅ | Finance only |
derivatives | — | — | ✅ | Finance only |
fintech | — | — | ✅ | Finance only |
institutional | — | — | ✅ | Finance only |
analysis | — | — | ✅ | Finance only |
gcc-investments | — | — | ✅ | Finance only |
gccNews | — | — | ✅ | Finance only |
The map is a specialized panel (map ID) rendered in its own #mapSection container rather than the #panelsGrid. Each variant defines both desktop and mobile default layer sets.
// src/types/index.ts
export interface MapLayers {
// Geopolitical layers
conflicts: boolean; bases: boolean; hotspots: boolean;
nuclear: boolean; sanctions: boolean; military: boolean;
// Infrastructure layers
cables: boolean; pipelines: boolean; waterways: boolean;
outages: boolean; datacenters: boolean; spaceports: boolean;
// Threat layers
cyberThreats: boolean; protests: boolean; fires: boolean;
// Environmental layers
weather: boolean; economic: boolean; natural: boolean;
minerals: boolean; irradiators: boolean;
// Transport layers
ais: boolean; flights: boolean;
// Data source layers
ucdpEvents: boolean; displacement: boolean; climate: boolean;
// Tech variant layers
startupHubs: boolean; cloudRegions: boolean; accelerators: boolean;
techHQs: boolean; techEvents: boolean;
// Finance variant layers
stockExchanges: boolean; financialCenters: boolean; centralBanks: boolean;
commodityHubs: boolean; gulfInvestments: boolean;
}
| Layer | Desktop Default | Mobile Default | Description |
|---|---|---|---|
conflicts | ✅ ON | ✅ ON | Armed conflict zones |
bases | ✅ ON | OFF | Military bases |
hotspots | ✅ ON | ✅ ON | Geopolitical hotspots |
nuclear | ✅ ON | OFF | Nuclear facilities |
sanctions | ✅ ON | ✅ ON | Sanctioned entities/regions |
weather | ✅ ON | ✅ ON | Weather alerts |
economic | ✅ ON | OFF | Economic indicators overlay |
waterways | ✅ ON | OFF | Strategic waterways |
outages | ✅ ON | ✅ ON | Internet/infrastructure outages |
military | ✅ ON | OFF | Military deployments |
natural | ✅ ON | ✅ ON | Natural disasters |
cables | OFF | OFF | Undersea fiber cables |
pipelines | OFF | OFF | Oil/gas pipelines |
ais | OFF | OFF | AIS vessel tracking |
irradiators | OFF | OFF | Gamma irradiators |
cyberThreats | OFF | OFF | Cyber threat indicators |
datacenters | OFF | OFF | AI data centers |
protests | OFF | OFF | Social unrest events |
flights | OFF | OFF | Military flights |
spaceports | OFF | OFF | Space launch facilities |
minerals | OFF | OFF | Critical mineral deposits |
fires | OFF | OFF | Active fires (FIRMS) |
ucdpEvents | OFF | OFF | UCDP conflict data |
displacement | OFF | OFF | UNHCR displacement data |
climate | OFF | OFF | Climate anomalies |
| All tech layers | OFF | OFF | — |
| All finance layers | OFF | OFF | — |
Desktop default ON: 11 layers · Mobile default ON: 5 layers
| Layer | Desktop Default | Mobile Default | Description |
|---|---|---|---|
cables | ✅ ON | OFF | Undersea fiber cables |
weather | ✅ ON | OFF | Weather alerts |
economic | ✅ ON | OFF | Economic indicators overlay |
outages | ✅ ON | ✅ ON | Internet outages |
datacenters | ✅ ON | ✅ ON | AI data centers |
natural | ✅ ON | ✅ ON | Natural disasters |
startupHubs | ✅ ON | ✅ ON | Startup ecosystem hubs |
cloudRegions | ✅ ON | OFF | Cloud provider regions |
techHQs | ✅ ON | OFF | Major tech company HQs |
techEvents | ✅ ON | ✅ ON | Tech conferences and events |
| All geopolitical | OFF | OFF | — |
| All military | OFF | OFF | — |
| All finance layers | OFF | OFF | — |
Desktop default ON: 10 layers · Mobile default ON: 5 layers
| Layer | Desktop Default | Mobile Default | Description |
|---|---|---|---|
cables | ✅ ON | OFF | Undersea fiber cables |
pipelines | ✅ ON | OFF | Oil/gas pipelines |
sanctions | ✅ ON | OFF | Sanctioned entities |
weather | ✅ ON | OFF | Weather alerts |
economic | ✅ ON | ✅ ON | Economic indicators overlay |
waterways | ✅ ON | OFF | Strategic waterways |
outages | ✅ ON | ✅ ON | Internet outages |
natural | ✅ ON | ✅ ON | Natural disasters |
stockExchanges | ✅ ON | ✅ ON | Global stock exchanges |
financialCenters | ✅ ON | OFF | Financial centers |
centralBanks | ✅ ON | ✅ ON | Central bank locations |
| All geopolitical | OFF | OFF | — |
| All military | OFF | OFF | — |
| All tech layers | OFF | OFF | — |
Desktop default ON: 11 layers · Mobile default ON: 5 layers
Mobile defaults enable fewer layers to preserve performance on constrained devices. The pattern:
natural, outages) are always onstartupHubs for tech, stockExchanges for finance)cables, pipelines, weather) are off by default on mobileAll user preferences survive page reload via localStorage. The following table enumerates every persisted setting:
| Setting | localStorage Key | Format | Default Source | Survives Reload |
|---|---|---|---|---|
| Panel visibility | worldmonitor-panels | Record<string, PanelConfig> JSON | DEFAULT_PANELS | ✅ |
| Panel ordering | panel-order | string[] JSON | Config declaration order | ✅ |
| Panel sizes/spans | worldmonitor-panel-spans | Record<string, number> JSON | All span-1 | ✅ |
| Map layer toggles | worldmonitor-layers | MapLayers JSON | DEFAULT_MAP_LAYERS | ✅ |
| Monitor keywords | worldmonitor-monitors | Monitor[] JSON | [] | ✅ |
| Disabled sources | worldmonitor-disabled-feeds | string[] JSON | [] | ✅ |
| Active variant | worldmonitor-variant | Plain string | SITE_VARIANT | ✅ |
| Banner dismissal | banner-dismissed (sessionStorage) | Timestamp string | — | Session only |
When the stored variant (worldmonitor-variant) differs from the current SITE_VARIANT, the App constructor performs a full reset:
if (storedVariant !== currentVariant) {
localStorage.setItem('worldmonitor-variant', currentVariant);
localStorage.removeItem(STORAGE_KEYS.mapLayers);
localStorage.removeItem(STORAGE_KEYS.panels);
localStorage.removeItem(this.PANEL_ORDER_KEY);
localStorage.removeItem(this.PANEL_SPANS_KEY);
this.mapLayers = { ...defaultLayers };
this.panelSettings = { ...DEFAULT_PANELS };
}
This ensures users switching between variant domains (e.g. from worldmonitor.io to tech.worldmonitor.io) get a clean default experience for the new variant.
Users can clear all panel preferences by clearing localStorage for the domain. There is no in-app "reset to defaults" button — the variant change mechanism serves as an implicit reset.
When the application adds new panels (in a code update), the saved panel order is merged with the current defaults:
panel-order key)DEFAULT_PANELSDEFAULT_PANELS but missing from saved orderpolitics panel position (or at position 0)monitors panel lastlive-news panel first (CSS grid constraint — it spans 2 columns)live-webcams immediately after live-newsThe panel lifecycle begins in App.ts constructor and flows through these phases:
panelSettings loaded from localStorage (or DEFAULT_PANELS on first visit / variant change)render() builds the page shell including #panelsGrid container and settings modalcreatePanels() constructs all panel class instances and registers them in this.panels and this.newsPanels#panelsGrid in the resolved order (saved + merge)makeDraggable() handlers for reorderingapplyPanelSettings() calls panel.toggle(config.enabled) for every panelloadAllData() fires parallel API calls; each handler updates its panel via setContent() / setContentThrottled()Panel (base)
├── NewsPanel ← RSS-driven news feeds (most panels)
├── MarketPanel ← Live market tickers
├── HeatmapPanel ← Sector heatmap grid
├── CryptoPanel ← Cryptocurrency prices
├── CommoditiesPanel ← Commodity prices
├── PredictionPanel ← Polymarket predictions
├── EconomicPanel ← FRED economic indicators
├── MonitorPanel ← User keyword monitors
├── CIIPanel ← Country Instability Index
├── CascadePanel ← Infrastructure cascade analysis
├── GdeltIntelPanel ← GDELT intelligence feed
├── SatelliteFiresPanel← NASA FIRMS fire data
├── StrategicRiskPanel ← Risk score overview
├── StrategicPosturePanel ← Theater posture
├── UcdpEventsPanel ← UCDP conflict events
├── DisplacementPanel ← UNHCR displacement
├── ClimateAnomalyPanel← Climate anomaly data
├── PopulationExposurePanel ← Population exposure
├── InvestmentsPanel ← GCC investment tracker
├── LiveNewsPanel ← Breaking news aggregation
├── LiveWebcamsPanel ← Multi-stream webcam view
├── TechEventsPanel ← Technology events
├── ServiceStatusPanel ← Service uptime monitor
├── TechReadinessPanel ← World Bank tech index
├── MacroSignalsPanel ← Macro market signals
├── ETFFlowsPanel ← BTC ETF flow tracker
├── StablecoinPanel ← Stablecoin market data
├── InsightsPanel ← AI-generated insights
├── RegulationPanel ← Regulation dashboard
├── RuntimeConfigPanel ← Desktop runtime config (Tauri only)
├── OrefSirensPanel ← Israel alert sirens (full only)
└── StatusPanel ← System status (not in panel grid)
App.ts iterates over all keys in the variant's FEEDS object. For any feed category that does not already have a manually instantiated NewsPanel, a new NewsPanel is created automatically:
for (const key of Object.keys(FEEDS)) {
if (this.newsPanels[key]) continue; // Skip if already created
if (!Array.isArray((FEEDS as Record<string, unknown>)[key])) continue;
// If a data panel exists with this key, create a separate news panel with "-news" suffix
const panelKey = this.panels[key] && !this.newsPanels[key] ? `${key}-news` : key;
if (this.panels[panelKey]) continue;
const panelConfig = DEFAULT_PANELS[panelKey] ?? DEFAULT_PANELS[key];
const label = panelConfig?.name ?? key.charAt(0).toUpperCase() + key.slice(1);
const panel = new NewsPanel(panelKey, label);
this.attachRelatedAssetHandlers(panel);
this.newsPanels[key] = panel;
this.panels[panelKey] = panel;
}
This allows the finance variant to have paired panels like markets (data panel) + markets-news (RSS panel) without manual duplication.
When a user clicks a panel toggle in the Settings modal:
config.enabled is flipped on the in-memory panelSettingssaveToStorage(STORAGE_KEYS.panels, this.panelSettings) persists to localStoragerenderPanelToggles() re-renders the toggle UI with updated checkmarksapplyPanelSettings() iterates all panels, calling panel.toggle(config.enabled)Panel.toggle() method adds/removes the hidden CSS classPanels are destroyed when the App instance is torn down. The Panel.destroy() method removes global event listeners (tooltip close handlers, touch/mouse handlers for resize). Panel DOM elements are removed when their parent grid is cleared.
Step-by-step guide to adding a new panel to World Monitor.
Add the panel entry to the appropriate variant config in src/config/panels.ts:
// In the FULL_PANELS (or TECH_PANELS, FINANCE_PANELS) object:
'my-panel': { name: 'My Panel', enabled: true, priority: 2 },
Panel order matters — panels are rendered in declaration order by default.
Create a new file in src/components/:
// src/components/MyPanel.ts
import { Panel } from './Panel';
export class MyPanel extends Panel {
constructor() {
super({
id: 'my-panel',
title: 'My Panel',
showCount: true,
infoTooltip: 'Methodology: ...',
});
}
public async refresh(): Promise<void> {
this.showLoading();
try {
const data = await fetch('/api/my-data').then(r => r.json());
this.setContent(this.renderData(data));
this.setCount(data.length);
this.setDataBadge('live');
} catch (e) {
this.showError('Failed to load data');
this.setDataBadge('unavailable');
}
}
private renderData(data: unknown[]): string {
return `<div class="my-panel-content">...</div>`;
}
}
For RSS-based panels, use NewsPanel directly instead of creating a new class — just add feeds to the variant's FEEDS object and the panel will be auto-generated.
Import and instantiate the panel in the createPanels() method of src/App.ts:
import { MyPanel } from '@/components/MyPanel';
// Inside createPanels():
const myPanel = new MyPanel();
this.panels['my-panel'] = myPanel;
Add a data loading task in loadAllData():
tasks.push({
name: 'myPanel',
task: runGuarded('myPanel', () => (this.panels['my-panel'] as MyPanel).refresh()),
});
If the panel needs periodic refresh, add a timer in setupRefreshTimers() referencing REFRESH_INTERVALS.
If the panel has an associated map layer:
MapLayers in src/types/index.tssrc/config/panels.tsMapContainerAdd a translation key in src/locales/en.json (and other locale files):
{
"panels": {
"myPanel": "My Panel"
}
}
If using the variant config system (src/config/variants/), add the panel to the appropriate variant file(s) alongside feeds if applicable.
flowchart TD
A[App Constructor] --> B{Variant Changed?}
B -->|Yes| C[Reset localStorage
Use DEFAULT_PANELS]
B -->|No| D[Load panelSettings
from localStorage]
C --> E[render: Build DOM Shell]
D --> E
E --> F[createPanels]
F --> G[Instantiate Panel Classes
Register in this.panels]
G --> H[Auto-generate NewsPanels
from FEEDS keys]
H --> I[Resolve Panel Order
saved + merge missing]
I --> J[Append to #panelsGrid
in resolved order]
J --> K[makeDraggable
on each panel element]
K --> L[applyPanelSettings
show/hide per config]
L --> M[renderPanelToggles
build settings modal UI]
M --> N[loadAllData
parallel API calls]
flowchart LR
ENV["VITE_VARIANT
env variable"] --> VAR["SITE_VARIANT
constant"]
VAR --> SW{Switch}
SW -->|"'tech'"| TP["TECH_PANELS
TECH_MAP_LAYERS
TECH_MOBILE_MAP_LAYERS"]
SW -->|"'finance'"| FP["FINANCE_PANELS
FINANCE_MAP_LAYERS
FINANCE_MOBILE_MAP_LAYERS"]
SW -->|default| GP["FULL_PANELS
FULL_MAP_LAYERS
FULL_MOBILE_MAP_LAYERS"]
TP --> EX["DEFAULT_PANELS
DEFAULT_MAP_LAYERS
MOBILE_DEFAULT_MAP_LAYERS"]
FP --> EX
GP --> EX
EX --> APP["App.ts
imports DEFAULT_PANELS"]
APP --> TREE["Vite tree-shakes
unused variants"]
sequenceDiagram
participant User
participant SettingsModal
participant App
participant localStorage
participant Panel
User->>SettingsModal: Click panel toggle
SettingsModal->>App: panelKey identified
App->>App: config.enabled = !config.enabled
App->>localStorage: saveToStorage('worldmonitor-panels', panelSettings)
App->>SettingsModal: renderPanelToggles() — update checkmarks
App->>App: applyPanelSettings()
loop For each panel
App->>Panel: panel.toggle(config.enabled)
Panel->>Panel: add/remove 'hidden' CSS class
end
Note over localStorage: Survives page reload
User->>User: Refreshes page
App->>localStorage: loadFromStorage('worldmonitor-panels')
App->>App: applyPanelSettings() with restored state
Quick reference for locating panel component source files:
| Component | File |
|---|---|
Panel (base) | src/components/Panel.ts |
NewsPanel | src/components/NewsPanel.ts |
MarketPanel / HeatmapPanel | src/components/MarketPanel.ts |
CryptoPanel | src/components/CryptoPanel.ts |
CommoditiesPanel | src/components/CommoditiesPanel.ts |
PredictionPanel | src/components/PredictionPanel.ts |
EconomicPanel | src/components/EconomicPanel.ts |
MonitorPanel | src/components/MonitorPanel.ts |
CIIPanel | src/components/CIIPanel.ts |
CascadePanel | src/components/CascadePanel.ts |
GdeltIntelPanel | src/components/GdeltIntelPanel.ts |
SatelliteFiresPanel | src/components/SatelliteFiresPanel.ts |
StrategicRiskPanel | src/components/StrategicRiskPanel.ts |
StrategicPosturePanel | src/components/StrategicPosturePanel.ts |
UcdpEventsPanel | src/components/UcdpEventsPanel.ts |
DisplacementPanel | src/components/DisplacementPanel.ts |
ClimateAnomalyPanel | src/components/ClimateAnomalyPanel.ts |
PopulationExposurePanel | src/components/PopulationExposurePanel.ts |
InvestmentsPanel | src/components/InvestmentsPanel.ts |
LiveNewsPanel | src/components/LiveNewsPanel.ts |
LiveWebcamsPanel | src/components/LiveWebcamsPanel.ts |
TechEventsPanel | src/components/TechEventsPanel.ts |
ServiceStatusPanel | src/components/ServiceStatusPanel.ts |
TechReadinessPanel | src/components/TechReadinessPanel.ts |
MacroSignalsPanel | src/components/MacroSignalsPanel.ts |
ETFFlowsPanel | src/components/ETFFlowsPanel.ts |
StablecoinPanel | src/components/StablecoinPanel.ts |
InsightsPanel | src/components/InsightsPanel.ts |
RegulationPanel | src/components/RegulationPanel.ts |
RuntimeConfigPanel | src/components/RuntimeConfigPanel.ts |
OrefSirensPanel | src/components/OrefSirensPanel.ts |
StatusPanel | src/components/StatusPanel.ts |