* feat(energy-atlas): EnergyDisruptionsPanel standalone timeline (§L #4) Closes gap #4 from docs/internal/energy-atlas-registry-expansion.md §L. Before this PR, the 52 disruption events in `energy:disruptions:v1` were only reachable by drilling into a specific pipeline or storage facility — PipelineStatusPanel and StorageFacilityMapPanel each render an asset-scoped slice of the log inside their drawers, but no surface listed the global event log. This panel makes the full log first-class. Shape: - Reverse-chronological table (newest first) of every event. - Filter chips: event type (sabotage, sanction, maintenance, mechanical, weather, war, commercial, other) + "ongoing only" toggle. - Row click dispatches the existing `energy:open-pipeline-detail` or `energy:open-storage-facility-detail` CustomEvent with `{assetId, highlightEventId}` — no new open-panel protocol introduced. Mirrors the CountryDeepDivePanel disruption row contract from PR #3377. - Uses `src/shared/disruption-timeline.ts` formatters (formatEventWindow, formatCapacityOffline, statusForEvent) that PipelineStatus/StorageFacilityMap already use — consistent UI across all three disruption surfaces. Wiring: - `src/components/EnergyDisruptionsPanel.ts` — new (~230 lines). - `src/components/index.ts` — export. - `src/app/panel-layout.ts` — `this.createPanel('energy-disruptions', () => new EnergyDisruptionsPanel())` alongside the other three atlas panels at :892. - `src/config/panels.ts` — add to `FULL_PANELS` (priority 2, next to fuel-shortages) + `ENERGY_PANELS` (priority 1, top tier) + `PANEL_CATEGORY_MAP.marketsFinance` list alongside the other atlas panels. - `src/config/commands.ts` — CMD+K entry `panel:energy-disruptions` with keywords matching the user vocabulary (sabotage, sanctions events, force majeure, drone strike, nord stream sabotage). Not done in this PR: - No new map pin layer — per plan §Q (Codex approved), disruptions stay a tabular/timeline surface; map assets (pipelines + storage) already show disruption markers on click. - No direct globe-mode or SVG-fallback rendering needs — panel is pure DOM, not a map layer. Test plan: - [x] npm run typecheck (clean) - [x] npm run test:data (6694/6694 pass) - [ ] Manual: CMD+K "disruption log" → panel opens with 52 events, newest first. Click "Sabotage" chip → narrows to sabotage events only. Click a Nord Stream row → PipelineStatusPanel opens with that event highlighted. * fix(energy-atlas): drop highlightEventId emission + respect empty-state (review P2) Two Codex P2 findings on this PR: 1. Row click dispatched `highlightEventId` but neither PipelineStatusPanel nor StorageFacilityMapPanel consumes it. The UI's implicit promise (event-specific highlighting) wasn't delivered — clickthrough was asset-generic, and the extra field on the wire was a misleading API surface. Fix: drop `highlightEventId` from the dispatched detail. Row click now opens the asset drawer with just {pipelineId, facilityId}, the fields the receivers actually consume. User sees the full disruption timeline for that asset and locates the event visually. A future PR can add real highlight support by: - drawers accept `highlightEventId` in their openDetailHandler - loadDetail stores it and renderDisruptionTimeline scrolls + emphasises the matching event - re-add `highlightEventId` to the dispatch here, symmetrically in CountryDeepDivePanel (which has the same wire emission) The internal `_eventId` parameter is kept as a plumb-through so that future work is a drawer-side change, not a re-plumb. 2. `events.length === 0` was conflated with `upstreamUnavailable` and triggered the error UI. The server contract (list-energy-disruptions handler) returns `upstreamUnavailable: false` with an empty events array when Redis is up but has no entries matching the filter — a legitimate empty state, not a fetch failure. Fix: gate `showError` on `upstreamUnavailable` alone. Empty results fall through to the normal render, where the table's `No events match the current filter` row already handles the case. Typecheck clean, test:data 6694/6694 pass. * fix(energy-atlas): event delegation on persistent content (review P1) Codex P1: Panel.setContent() debounces the DOM write by 150ms (see Panel.ts:1025), so attaching listeners in render() via `this.element.querySelector(...)` targets the STALE DOM — chips, rows, and the ongoing-toggle button are silently non-interactive. Visually the panel renders correctly after the debounce fires, but every click is permanently dead. Fix: register a single delegated click handler on `this.content` (persistent element) in the constructor. The handler uses `closest('[data-filter-type]')`, `closest('[data-toggle-ongoing]')`, and `closest('tr.ed-row')` to route by data-attribute. Works regardless of when setContent flushes or how many times render() re-rewrites the inner HTML. Also fixes Codex P2 on the same PR: filterEvents() was called twice per render (once for row HTML, again for filteredCount). Now computed once, reused. Trivial for 52 events but eliminates the redundant sort. Typecheck clean. * fix(energy-atlas): remap orphan disruption assetIds to real pipelines Two events referenced pipeline ids that do not exist in scripts/data/pipelines-oil.json: - cpc-force-majeure-2022: assetId "cpc-pipeline" → "cpc" - pdvsa-designation-2019: assetId "ve-petrol-2026-q1" → "venezuela-anzoategui-puerto-la-cruz" Without this, clicking those rows in EnergyDisruptionsPanel dead-ends at "Pipeline detail unavailable", so the panel shipped with broken navigation on real data. Mirrors the same fix on PR #3377 (gap #5a registry); applying it on this branch as well so PR #3378 is independently correct regardless of merge order. The two changes will dedupe cleanly on rebase since the edits are byte-identical.
World Monitor
Real-time global intelligence dashboard — AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface.
Documentation · Releases · Contributing
What It Does
- 500+ curated news feeds across 15 categories, AI-synthesized into briefs
- Dual map engine — 3D globe (globe.gl) and WebGL flat map (deck.gl) with 45 data layers
- Cross-stream correlation — military, economic, disaster, and escalation signal convergence
- Country Intelligence Index — composite risk scoring across 12 signal categories
- Finance radar — 92 stock exchanges, commodities, crypto, and 7-signal market composite
- Local AI — run everything with Ollama, no API keys required
- 5 site variants from a single codebase (world, tech, finance, commodity, happy)
- Native desktop app (Tauri 2) for macOS, Windows, and Linux
- 21 languages with native-language feeds and RTL support
For the full feature list, architecture, data sources, and algorithms, see the documentation.
Quick Start
git clone https://github.com/koala73/worldmonitor.git
cd worldmonitor
npm install
npm run dev
Open localhost:5173. No environment variables required for basic operation.
For variant-specific development:
npm run dev:tech # tech.worldmonitor.app
npm run dev:finance # finance.worldmonitor.app
npm run dev:commodity # commodity.worldmonitor.app
npm run dev:happy # happy.worldmonitor.app
See the self-hosting guide for deployment options (Vercel, Docker, static).
Tech Stack
| Category | Technologies |
|---|---|
| Frontend | Vanilla TypeScript, Vite, globe.gl + Three.js, deck.gl + MapLibre GL |
| Desktop | Tauri 2 (Rust) with Node.js sidecar |
| AI/ML | Ollama / Groq / OpenRouter, Transformers.js (browser-side) |
| API Contracts | Protocol Buffers (92 protos, 22 services), sebuf HTTP annotations |
| Deployment | Vercel Edge Functions (60+), Railway relay, Tauri, PWA |
| Caching | Redis (Upstash), 3-tier cache, CDN, service worker |
Full stack details in the architecture docs.
Flight Data
Flight data provided gracefully by Wingbits, the most advanced ADS-B flight data solution.
Data Sources
WorldMonitor aggregates 65+ external data sources across geopolitics, finance, energy, climate, aviation, cyber, military, infrastructure, and news intelligence. See the full data sources catalog for providers, feed tiers, and collection methods.
Contributing
Contributions welcome! See CONTRIBUTING.md for guidelines.
npm run typecheck # Type checking
npm run build:full # Production build
License
AGPL-3.0 for non-commercial use. Commercial license required for any commercial use.
| Use Case | Allowed? |
|---|---|
| Personal / research / educational | Yes |
| Self-hosted (non-commercial) | Yes, with attribution |
| Fork and modify (non-commercial) | Yes, share source under AGPL-3.0 |
| Commercial use / SaaS / rebranding | Requires commercial license |
See LICENSE for full terms. For commercial licensing, contact the maintainer.
Copyright (C) 2024-2026 Elie Habib. All rights reserved.
Author
Elie Habib — GitHub
Contributors
Security Acknowledgments
We thank the following researchers for responsibly disclosing security issues:
- Cody Richard — Disclosed three security findings covering IPC command exposure, renderer-to-sidecar trust boundary analysis, and fetch patch credential injection architecture (2026)
See our Security Policy for responsible disclosure guidelines.
worldmonitor.app · docs.worldmonitor.app · finance.worldmonitor.app · commodity.worldmonitor.app
