Elie Habib 73cd8a9c92 feat(energy-atlas): EnergyDisruptionsPanel standalone timeline (§L #4) (#3378)
* 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.
2026-04-24 19:09:05 +04:00

World Monitor

Real-time global intelligence dashboard — AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface.

GitHub stars GitHub forks Discord License: AGPL v3 TypeScript Last commit Latest release

Web App  Tech Variant  Finance Variant  Commodity Variant  Happy Variant

Download Windows  Download macOS ARM  Download macOS Intel  Download Linux

Documentation  ·  Releases  ·  Contributing

World Monitor Dashboard


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 HabibGitHub

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

Star History

Star History Chart
Description
Mirrored from GitHub
Readme AGPL-3.0 382 MiB
Languages
TypeScript 49.1%
JavaScript 47%
CSS 2.9%
HTML 0.4%
Rust 0.3%
Other 0.1%