Tauri resource_dir() on Windows returns \\?\ extended-length paths that
Node.js module resolution cannot handle, causing EISDIR: lstat 'C:'.
Strip the prefix before passing to Node.js, set current_dir to the
sidecar directory, and add package.json with "type": "module" to prevent
ESM scope walk-up to drive root.
keyring v3 ships with NO default platform backends — API keys were
stored in-memory only, lost on every app restart. Add apple-native
and windows-native features to use real OS credential stores.
MD012 / no-multiple-blanks Multiple consecutive blank lines
MD022 / blanks-around-headings Headings should be surrounded by blank
lines
MD032 / blanks-around-lists Lists should be surrounded by blank lines
MD012 / no-multiple-blanks Multiple consecutive blank lines
MD022 / blanks-around-headings Headings should be surrounded by blank lines
MD032 / blanks-around-lists Lists should be surrounded by blank lines
- Save keys that pass verification even when others fail (was all-or-nothing)
- Capture un-blurred input values before render to prevent loss on checkbox toggle
- Fix missing isDisallowedOrigin import in PIZZINT endpoints
- Show "Staged" status/pill for buffered secrets instead of "Missing"
- Add macOS Edit menu (Cmd+C/V/X/Z) for WKWebView clipboard support
- Raise settings window when main gains focus (prevent hide-behind)
- Fix Cloudflare verification to probe Radar API (not token/verify)
- Fix EIA verification URL to valid v2 endpoint
- Force IPv4 globally: monkey-patch fetch() to avoid IPv6 ETIMEDOUT
on government APIs (EIA, NASA FIRMS) with broken AAAA records
- Soft-pass on network errors during secret verification (don't block save)
- Add desktopRequiredSecrets to skip relay URLs on desktop
- Cross-window sync for secrets and feature toggles via localStorage events
- Add @tauri-apps/cli devDependency
Common English verbs like "say" pass frequency thresholds and generate
false intelligence findings. Now runs browser NER (BERT) on spike
headlines before alerting — terms not recognized as named entities
(PER/ORG/LOC/MISC) are suppressed. Falls back gracefully when NER
is unavailable. Also adds ~55 base verb forms to the suppressed list
as hardened fallback for low-spec devices.
Major security hardening: CORS enforcement on all API endpoints,
sidecar auth bypass fix, postMessage origin validation, CSP
tightening, and service worker stale cache fix.
Add skipWaiting, clientsClaim, and cleanupOutdatedCaches to workbox
config. Fixes NS_ERROR_CORRUPTED_CONTENT / MIME type errors when
users have a cached service worker serving old HTML that references
asset hashes no longer on the CDN after redeployment.
- Expand SUPPRESSED_TRENDING_TERMS from 13 to ~170 entries to filter
common English words (department, state, news, etc.) from intelligence
findings
- Move sidecar admin endpoints (debug-toggle, traffic-log, env-update,
local-status) before LOCAL_API_TOKEN auth gate — settings window sends
bare fetch without token, causing silent 401 failures
- Restore Market Radar and Economic Indicators panels to tech variant
- Remove stale Documentation section from README
- Clean up .env.example cyber threat keys (handled internally)
- Bump v2.2.6
Replace Access-Control-Allow-Origin: * with shared getCorsHeaders()
across 20 API edge functions to restrict access to worldmonitor.app,
tech.worldmonitor.app, and authorized Vercel preview URLs.
Version bump to 2.2.5 across package.json, tauri.conf.json, Cargo.toml.
Replace Access-Control-Allow-Origin: * with an allowlist of our domains
(worldmonitor.app, tech.worldmonitor.app, localhost dev ports, Tauri,
and *.vercel.app previews). Prevents third parties from using the relay
as a free proxy.
Desktop Configuration panel was leaking to web due to being in
FULL_PANELS/TECH_PANELS configs. Removed from static configs (injected
programmatically for Tauri) and filtered from toggle UI on web.
World Bank API returns 403 to Vercel edge IPs. Added /worldbank proxy
route to Railway relay with full response transformation and 30-min cache.
Client tries Railway first, falls back to Vercel.
Polymarket gamma-api suffers Cloudflare JA3 blocking from Vercel.
Added /polymarket proxy route to Railway relay with 2-min cache.
Client fallback chain: browser-direct → Tauri → Railway → Vercel → production.
Update README with 5 new How It Works sections and 10 inline updates
reflecting implemented features. Data layer count 29→30+, data sources
14→22, new Tech Stack and Roadmap entries.
Bring Climate Anomalies, Population Exposure, UCDP Conflict Events,
and UNHCR Displacement panels up to dashboard design standards
matching Fires/Markets/Stablecoins panels.
- Replace div-based layouts with proper <table> structures
- Add scoped <style> blocks (no inline style= attributes)
- CSS class-based severity badges, death colors, displacement badges
- tabular-nums on all numeric columns, right-aligned
- Row hover effects, text-overflow ellipsis for long text
- Stat grid for UNHCR global totals, total deaths summary for UCDP
- Custom tab styling for UCDP and Displacement panels
- Tooltip: show "Cyber Threat" + severity + country instead of raw source names
- Popup header: "Cyber Threat" title instead of raw IP address
- Popup: show all 5 source labels and add malware family field
- Increase dot size for better visibility at global zoom level
ipwho.is returns 403 from Node.js/Edge ("CORS not supported on Free plan")
and ipapi.co rate-limits aggressively (429). Both providers only work from
browser-side requests.
Switch to ipinfo.io (HTTPS, 50K/mo free, works from Edge runtime) as primary
with freeipapi.com as fallback.
Validated end-to-end: 20 C2 IPs geo-enriched in <1s with real coordinates.
Expands from 2 to 5 threat intel sources:
- C2IntelFeeds (free, no auth): ~500 active C2 server IPs (CobaltStrike, Metasploit)
- AlienVault OTX (optional API key): community-sourced IOCs with tags
- AbuseIPDB (optional API key): high-confidence abuse reports with geo
- Feodo: now includes recently-offline entries (still threat-relevant)
All sources fetched in parallel. Graceful degradation when API keys missing.
Plot live botnet C2 servers, malware distribution nodes, and malicious IPs
on the globe using free abuse.ch APIs (Feodo Tracker + URLhaus).
- Vercel edge API with triple-layer caching (Redis → memory → stale fallback)
- IP geolocation via ipwho.is + ipapi.co (HTTPS-compatible with Edge runtime)
- Severity-based color coding (critical=red, high=orange, medium=amber, low=yellow)
- Feature-gated behind VITE_ENABLE_CYBER_LAYER=true env var
- Frontend circuit breaker, data sanitization, 10min auto-refresh
- Tauri desktop support: 3 new secret keys (URLHAUS, OTX, AbuseIPDB)
- Full test suite (6 unit tests), e2e harness updates, popup + tooltip rendering
- Add 60s startup cooldown to suppress pulse during initial data hydration
- Consolidate pulse lifecycle into syncPulseAnimation() across all setters
- Replace static predicates (h.level=high, c.maxSeverity=high) with dynamic-only
checks: recent news, recent ACLED riots (<2h), breaking hotspots
- Exclude GDELT riots from pulse gating (noisy, not actionable)
- Reduce pulse interval from 250ms to 500ms (halves layer rebuild rate)
- Pause pulse when render is paused (country brief overlay)
- Add latestRiotEventTimeMs to MapProtestCluster with raw protest fallback
- Add Playwright tests for startup cooldown and lifecycle start/stop/GDELT exclusion
The runtime fetch patch previously returned local 4xx/5xx responses
as-is. Now falls back to worldmonitor.app cloud API on any non-OK
local response, matching the existing network-error fallback behavior.
- Full-page Country Brief replacing modal overlay
- Tightened headline relevance with negative country filter
- Top News section replacing redundant Evidence/Sources
- Shared country-geometry service for local-first detection
- BR/AE added to tier-1, deep-link name resolution
- Prediction Markets moved to right column for balanced layout
GeoJSON uses "United States of America" but COUNTRY_TAG_MAP and
variant matching expect "United States". Normalize at entry point
so polymarket, search terms, and all downstream lookups work.
- Remove Evidence/Sources section (duplicate of Top News)
- Top News now shows 8 items with citation anchor IDs
- AI brief [N] citations link to Top News cards with scroll+highlight
- Move Prediction Markets to right column for balanced layout
- Left: Risk + Brief + Top News | Right: Signals + Timeline + Markets + Infra
- Add Brazil and UAE to TIER1_COUNTRIES, BASELINE_RISK, EVENT_MULTIPLIER,
COUNTRY_KEYWORDS, COUNTRY_BOUNDS, COUNTRY_ALIASES, and Polymarket maps
- Use Intl.DisplayNames to resolve non-tier ISO codes in deep-link URLs
- Guard raw 2-letter codes from becoming overly broad search terms
- Fix military timeline to use same either/or logic as getCountrySignals
- Add negative country filter: headlines where another country is mentioned
first in the title are excluded (fixes Venezuela brief showing for US)
- Filtered headlines used for both evidence section AND LLM context
- Add Top News section (5 items) between Intelligence Brief and Markets
- Limit prediction markets to max 3, tighter padding
- RuntimeConfigPanel only created when isDesktopApp is true (was leaking
full desktop config UI including secret key inputs to web visitors)
- Fix polymarket sub-market selection: when event title doesn't match
country but a sub-market does, restrict candidates to matching
sub-markets instead of picking highest-volume globally (was showing
Xi Jinping market for France because Macron sub-market triggered match)
Shield.io badges for Windows, macOS ARM, and macOS Intel desktop downloads
pointing to the new /api/download redirect. Web app links restyled as badges.
Edge function that redirects /api/download?platform=macos-arm64 to
the matching asset from the latest GitHub release. Falls back to
the releases page when no match is found.
- Rename variant default from 'world' to 'full' across config files
- Replace all startups.worldmonitor.app references with tech.worldmonitor.app
- Add CARTO basemap tile caching to Workbox runtime config (basemaps.cartocdn.com)
- Make open_settings_window_command async to prevent WebView2 deadlock on Windows
- Create settings window with visible(false) to avoid white flash before content loads
- Remove menu bar from settings window on Windows/Linux (macOS uses screen-level menu)
- Frontend calls plugin:window|show + set_focus after init completes
Switch ARM64 build from cross-compiling on macos-latest (Intel) to
native compilation on macos-14 (M1). Updates platform checks to
use contains() so signing works on both macOS runner types.
Gracefully handle missing/invalid Apple Developer certificates instead
of failing the entire build. Detects secret availability, validates
certificate file size, makes import failures non-fatal, and splits
each variant into signed/unsigned conditional paths.
Builds macOS (ARM64 + x64) and Windows via tauri-apps/tauri-action.
Supports full/tech variants, optional code signing, and automatic
GitHub Release creation on tag push.
- Add worldmonitor.app to frame-src CSP in index.html (was only in
tauri.conf.json, causing iframe block)
- Add devtools feature and Help > Toggle Developer Tools menu item
- Try native YouTube JS API first, fall back to cloud bridge on Error 153
- Add pause-then-play workaround for WKWebView channel switching
Switch bridge page from raw iframe to YouTube IFrame Player API with
postMessage protocol for play/pause/mute commands. Use youtube-nocookie.com
host, add click-to-play overlay for WKWebView autoplay restrictions, and
route desktop embeds through cloud URL to avoid sidecar auth issues.
Prevents unauthorized local processes from accessing the sidecar on
localhost:46123. Token is generated at Tauri startup using RandomState
hasher, injected into sidecar env, and lazy-loaded by the frontend
fetch patch via get_local_api_token command.
Service-status endpoint remains public for health checks.
Co-authored-by: RinZ27 <RinZ27@users.noreply.github.com>
GeoJSON had France, Norway, and Kosovo sharing ISO code -99, causing hover
on any one to highlight all three. Fix ISO codes and switch hover filter
to unique name property. Add data validation tests.
- Add RUSI, Wilson Center, GMF, Stimson, CNAS, Lowy Institute feeds
- Add Arms Control Association, Bulletin of Atomic Scientists feeds
- Add FAO GIEWS food security alerts, EU ISS feeds
- Add www.iss.europa.eu to RSS proxy allowlist
- Add SOURCE_TIERS and SOURCE_TYPES entries for new feeds
Please review modified files (2) to ensure we are properly aligned on
goals/expectations. Cheers!
Jas
- Add RUSI, Wilson Center, GMF, Stimson, CNAS, Lowy Institute feeds
- Add Arms Control Association, Bulletin of Atomic Scientists feeds
- Add FAO GIEWS food security alerts, EU ISS feeds
- Add www.iss.europa.eu to RSS proxy allowlist
- Add SOURCE_TIERS and SOURCE_TYPES entries for new feeds
- Fix YouTube Error 153 by serving embed bridge from cloud URL (origin match)
- Fix channel switching when playerContainer detached from DOM
- Fix Fires panel infinite spinner when API returns 0 or fails
- Make TECH variant button open web URL instead of being disabled
- Fix circuit breaker caching empty results as success in 6 services
(polymarket, wingbits, military-flights, outages, conflicts, protests)
- Improve sidecar: cloud-preferred routing, failed import caching, log dedup
- Add FINNHUB_API_KEY and NASA_FIRMS_API_KEY to Tauri secret keys
- Add early 503 for missing ACLED token in risk-scores
- Add /api/classify-batch endpoint: classifies up to 20 headlines per Groq call
(reduces 182 individual API calls to ~10 batched calls, 90% rate limit savings)
- Update threat-classifier.ts: collect headlines in batch queue, flush every 500ms
or when batch reaches 20 items
- Route AIS snapshot through Railway directly when VITE_WS_RELAY_URL is set,
falling back to Vercel — eliminates 503 when WS_RELAY_URL not configured on Vercel
- Add /ucdp-events handler to ais-relay.cjs with 6h TTL cache,
background refresh, version discovery, and 30s per-page timeout
- Route client through Railway when VITE_WS_RELAY_URL is set
- Add 8s AbortController timeout to Vercel fallback fetchGedPage()
### Motivation
- Reduce main bundle pressure by splitting large third-party libraries
into logical chunks to improve load/ caching behavior during production
builds.
- Keep documentation accurate and ensure assets referenced from `docs/`
resolve correctly.
### Description
- Replaced static `manualChunks` object in `vite.config.ts` with a
function-based splitter that creates `ml` for ML deps
(`@xenova/transformers`, `onnxruntime-web`), `map` for map/visualization
deps (`@deck.gl`, `maplibre-gl`, `h3-js`), and preserves dedicated `d3`
and `topojson` chunks.
- Updated `docs/DOCUMENTATION.md` to bump the version badge to `2.1.4`
and fixed the dashboard image path to `../new-world-monitor.png` so it
loads when viewing files from the `docs/` folder.
- Changes applied to `vite.config.ts` and `docs/DOCUMENTATION.md` and
committed to the current branch.
### Testing
- Ran `npm run typecheck` and it completed successfully.
- Ran `npm run build` and the production build succeeded; rollup emitted
warnings about `eval` in a dependency and some chunks >500 kB but build
artifacts were produced.
- Ran `npm run test:e2e:full` which failed in this environment because
Playwright browser binaries are not installed (error: `Executable
doesn't exist ... chromium_headless_shell`).
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_698f0083f99083338bf9ea2ebbefd191)
### Motivation
- Make Tauri/Rust dependency resolution reproducible in
restricted-network CI or offline environments by providing a documented
vendored source option.
- Surface two supported packaging flows (online and restricted-network)
in the release docs so packagers know how to provision offline inputs.
- Improve validation reporting to distinguish external registry outages
from failures caused by missing offline artifacts so QA results are
actionable.
- Allow committing `Cargo.lock` for `src-tauri` once generated by
removing it from the local `.gitignore` so dependency graphs can be
pinned.
### Description
- Added `src-tauri/.cargo/config.toml` defining a `vendored-sources`
replacement that points to `src-tauri/vendor/` and included inline
instructions to `cargo vendor` for populating it.
- Updated `docs/RELEASE_PACKAGING.md` with a new section describing two
Rust dependency modes and example commands for (1) the standard online
build and (2) a restricted-network build that uses vendored crates or an
internal mirror.
- Updated `docs/TAURI_VALIDATION_REPORT.md` to classify failures into
**External environment outage** and **Expected failure: offline mode not
provisioned**, with guidance for both.
- Modified `src-tauri/.gitignore` to stop ignoring `Cargo.lock` so a
lockfile can be generated and committed when network or vendored
artifacts are available.
### Testing
- Ran `cd src-tauri && cargo generate-lockfile`, which failed due to
blocked access to `https://index.crates.io` with `403 CONNECT tunnel
failed` (environmental network restriction).
- Ran `cd src-tauri && cargo generate-lockfile --offline`, which failed
as expected because `src-tauri/vendor/` was not provisioned (`no
matching package named keyring found`).
- No additional automated tests were run on the modified files;
documentation and config changes were validated by applying the edits
and confirming file contents.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_698ef3d2ac2c8333b576f1c85e6004a1)
### Motivation
- Provide a compact, maintainable model for tracking which UI features
are fully local vs require keys or cloud fallback so desktop parity work
can be prioritized.
- Surface desktop-specific acceptance checks and per-feature fallback
behavior in the UI so operators can quickly assess "desktop ready"
status.
- Consolidate the local-sidecar parity audit into a machine-readable
matrix and documented acceptance criteria.
### Description
- Added a new desktop parity service `src/services/desktop-readiness.ts`
that defines `DesktopParityFeature`, encodes high-value features
(LiveNews, Monitor, StrategicRisk, map layers, summaries, markets,
wingbits/relays), and exposes `getDesktopReadinessChecks`,
`getKeyBackedAvailabilitySummary`, and `getNonParityFeatures` helper
functions.
- Updated `src/components/ServiceStatusPanel.ts` to render a new
"Desktop readiness" section that shows acceptance-check progress,
key-backed feature availability (`available/total`), and a collapsible
list of non-parity fallbacks for operator visibility.
- Reworked `docs/local-backend-audit.md` into a prioritized parity
matrix with closure ordering and explicit desktop-ready acceptance
criteria (startup, map rendering, core intelligence panels, summaries,
market panel, and at least one live-tracking mode).
### Testing
- Ran type checking with `npm run typecheck`, which completed
successfully.
- Executed an automated browser smoke run (Playwright) to capture a
Service Status screenshot against the dev server, which completed and
produced a screenshot artifact successfully.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_698ec64caff0833386d529e611b468f4)
### Motivation
- Provide a single, reproducible local packaging workflow for desktop
artifacts across macOS and Windows that supports both product variants
(full vs tech).
- Make signing/notarization flows explicit and safe by wiring optional
env-based hooks for Apple notarization and Windows Authenticode.
- Ensure outputs are variant-aware and documented so release engineers
can validate artifacts on clean machines.
### Description
- Add a reusable runner `scripts/desktop-package.mjs` that accepts `--os
<macos|windows>`, `--variant <full|tech>`, and an optional `--sign`
flag, pins bundler targets per-OS, and enforces required signing env
vars when signing is requested.
- Wire `package.json` to use the new runner for all `desktop📦*`
scripts and add a generic `desktop:package` entrypoint.
- Update `src-tauri/tauri.conf.json` and
`src-tauri/tauri.tech.conf.json` to keep explicit bundler targets and
align platform bundle settings (`macOS.hardenedRuntime`, and Windows
`digestAlgorithm`/`timestampUrl`).
- Expand `docs/RELEASE_PACKAGING.md` and update `README.md` with
reproducible per-OS commands, example env hooks for Apple notarization
and Windows Authenticode, variant-aware outputs, artifact locations, and
a clean-machine release checklist.
### Testing
- Ran TypeScript checks with `npm run typecheck`, which completed
successfully.
- Executed the packaging runner guard path with `node
scripts/desktop-package.mjs --os macos --variant full --sign`, which
failed as expected due to missing signing env vars and confirmed the
signing-enforcement checks are working.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_698ec3ac04e0833397294cf0b28ac97e)
### Motivation
- Provide reproducible local packaging commands for both product
variants (full vs tech) across macOS and Windows.
- Make signing/notarization and Authenticode flows optional and
environment-driven so builds can be reproducible unsigned or signed when
keys are available.
- Ensure release artifacts and variant identities are documented and
easy to validate on a clean machine.
### Description
- Added deterministic packaging scripts to `package.json` for macOS
(`.app` + `.dmg`) and Windows (`NSIS .exe` + `.msi`) for both `full` and
`tech` variants and optional signed variants via environment variables.
- Updated `src-tauri/tauri.conf.json` and
`src-tauri/tauri.tech.conf.json` to explicitly target `app`, `dmg`,
`nsis`, and `msi`, and added a Windows signing/timestamp hint (`sha256`
+ timestamp URL).
- Added `docs/RELEASE_PACKAGING.md` containing per-OS packaging
commands, optional signing/notarization env hooks, artifact output
locations, and a clean-machine startup validation checklist.
- Linked the new packaging guide from `README.md` and surfaced the new
`npm run desktop📦*` commands in the contributor command block.
### Testing
- Verified the updated JSON files parse successfully with a `node -e`
JSON parse check (succeeded).
- Ran `npm run -s typecheck` (TypeScript `tsc --noEmit`) to ensure no
type regressions (succeeded).
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_698ebfa41cbc8333ba9aeeab79aa8a72)
### Motivation
- Ensure the app remains useful when cloud APIs are unreachable by
providing durable local fallbacks and clearer UI indicators for
stale/offline data.
- Persist recent feed items, latest computed risk snapshots, and
stale-tolerant map overlays for desktop builds so a packaged app can
operate without network.
- Surface data freshness and whether values are `live`, `cached`, or
`unavailable` in panels so analysts understand what they are seeing.
### Description
- Add a desktop-aware persistent cache service
(`src/services/persistent-cache.ts`) that uses Tauri `invoke` commands
(`read_cache_entry` / `write_cache_entry`) with `localStorage` as a
fallback for non-desktop runtimes.
- Extend the Tauri backend (`src-tauri/src/main.rs`) to provide
file-backed JSON cache storage under app data and register
`read_cache_entry` and `write_cache_entry` handlers.
- Wire persistent fallbacks into runtime services: RSS feeds
(`src/services/rss.ts`), cached risk scores
(`src/services/cached-risk-scores.ts`), earthquake overlay data
(`src/services/earthquakes.ts`), and the Insights world-brief snapshot
(`src/components/InsightsPanel.ts`).
- Enhance circuit-breaker semantics in `src/utils/circuit-breaker.ts` to
track `BreakerDataState` (modes: `live` / `cached` / `unavailable`) and
detect desktop offline mode so callers can show more precise status and
use cached fallbacks.
- Add panel-level data-state badges and helpers in
`src/components/Panel.ts` and apply them to `NewsPanel`,
`InsightsPanel`, and `StrategicRiskPanel`, plus styling in
`src/styles/main.css`.
- Include static resources in desktop bundle config by adding `../data`
and `../src/config` to `src-tauri/tauri.conf.json` so pre-bundled
datasets are available in production desktop builds.
### Testing
- Ran `npm run typecheck` (`tsc --noEmit`) which completed successfully.
- Started the dev server and exercised the UI; captured a screenshot
demonstrating panel freshness badges (artifact recorded during the run).
- `cargo check` could not complete in this environment due to
network/crates.io access being blocked (error: CONNECT tunnel failed /
403), so native Rust/Tauri build verification was not possible here.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_698eb89d5b448333b14174125f76d3c4)
### Motivation
- Provide a local backend for the Tauri desktop app so core
functionality (news, summarization, markets, telemetry, status) does not
require Vercel edge functions and can run offline or with reduced cloud
dependency.
- Minimize frontend changes by exposing the same `/api/*` paths locally
and failing over to the cloud when handlers are missing or local
execution fails.
### Description
- Add a Node sidecar local API server at
`src-tauri/sidecar/local-api-server.mjs` that dispatches `/api/*` to
existing `api/*.js` handlers when present and proxies to the cloud
(`https://worldmonitor.app`) as a fallback.
- Start/stop the sidecar with the Tauri app lifecycle by launching it
from `src-tauri/src/main.rs` and managing the child process state.
- Update Tauri configuration in `src-tauri/tauri.conf.json` to allow the
local API origin (`http://127.0.0.1:46123`) in the CSP and to include
`../api` and `sidecar/local-api-server.mjs` as bundle resources.
- Desktop runtime routing changes in `src/services/runtime.ts`: default
desktop API base set to `http://127.0.0.1:46123`, added
`getRemoteApiBaseUrl()` and an `installRuntimeFetchPatch()` function
that patches `fetch` to route `/api/*` to the local sidecar with
automatic cloud fallback.
- Enable the runtime fetch patch at app start by calling
`installRuntimeFetchPatch()` from `src/main.ts`.
- Update `ServiceStatusPanel` (`src/components/ServiceStatusPanel.ts`)
to render local backend status and to show clear messaging when the
local backend is unavailable and the UI is using the cloud fallback.
- Add documentation `docs/local-backend-audit.md` listing prioritized
`/api/*` endpoints for desktop parity and describing the localization
strategy.
- Minor formatting run via `cargo fmt` adjusted `src-tauri/build.rs`.
### Testing
- `npm run typecheck` (`tsc --noEmit`) passed successfully.
- `cargo fmt` completed successfully and reformatted
`src-tauri/build.rs` where needed.
- `cargo check` failed in this environment due to network restrictions
while downloading crates index (environment-specific HTTP 403); this is
unrelated to the code changes themselves.
- Local sidecar smoke tests (automated invocation): launched `node
src-tauri/sidecar/local-api-server.mjs` and verified `GET
/api/local-status` and `GET /api/service-status` returned expected JSON
responses (local status included), demonstrating the sidecar dispatch
and health endpoints work in this environment.
- Playwright screenshot attempt failed because the dev server was not
reachable from the test environment (`net::ERR_EMPTY_RESPONSE`), so UI
screenshot validation could not be completed here.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_698ead50f548833398717fa3b8c92230)
Explains that `vercel dev` is needed instead of `npm run dev` for
the API edge functions to work. Covers three deployment options
(Vercel, local with CLI, static frontend only) and platform notes
for Raspberry Pi/ARM users. Addresses #44.
## Summary
Fixes visual lag where high-activity hotspot overlays drift from their
geographic positions during map panning operations.
## Problem
HTML overlays for high-activity hotspots (`level=high` or
`hasBreaking=true`) were only updating positions on a throttled 100ms
delay. During map drag operations, this caused overlays to maintain
their screen position while the map moved underneath, resulting in a
"drift and snap" effect where markers would float away from their true
locations then jump back when the update triggered.
## Solution
- Added `updateHotspotPositions()` method that synchronizes overlay
positions with the map viewport in real-time during movement
- Bound method to the map `'move'` event for frame-by-frame position
updates during drag operations
- Eliminated the 100ms throttle delay for position calculations during
active map movement
## Changes
- **Added** `updateHotspotPositions()` method for real-time overlay
position synchronization
- **Added** `'move'` event listener to trigger position updates during
map drag/pan
- **Fixed** drifting issue for high-activity and breaking hotspot
markers
- **Added** dev mode helper to force 3 random hotspots as `breaking` for
easier overlay behavior testing (for some reason they just weren't
appearing on localhost)
https://github.com/user-attachments/assets/a1cfe749-69bb-499c-b396-24054e7891cehttps://github.com/user-attachments/assets/59c4265a-0667-4ea9-a657-a4f67f983f1e
- Add updateHotspotPositions() method that updates overlay positions in real-time during map movement
- Call updateHotspotPositions() on 'move' event to prevent overlays from lagging behind their geographic positions
- Fixes issue where high-activity/breaking hotspot markers would drift during drag and snap back on release
- Add dev mode helper to force 3 random hotspots as breaking for testing overlay behavior
The HTML overlays for high-activity hotspots (level=high or hasBreaking=true) were only updating positions on a throttled/delayed basis (100ms), causing them to maintain screen position while the map moved underneath, then snap back when the update triggered. Now they update smoothly every frame during map movement.
- Implements layer caching to avoid rebuilding unchanged deck.gl layers
- Adds cluster element DOM caching with position-based invalidation
- Adds cluster result caching to prevent redundant spatial computations
- Replaces setInterval with requestAnimationFrame for news pulse animation
- Adds throttle, debounce, and rafSchedule utilities for render optimization
- Removes hover tooltips in favor of click-based interactions (better performance)
- Adds performance monitoring with console warnings for >16ms operations
- Implements smart layer rebuilding only on zoom threshold crossings
- Reduces cluster overlay opacity during map moves for smoother UX
- Configures deck.gl with interleaved=false and useDevicePixels=false for performance
Performance improvements:
- Reduces redundant layer rebuilds by ~90% during map interactions
- Eliminates tooltip hover callback overhead
- Prevents cluster DOM thrashing with element reuse
- 60fps target for map panning/zooming maintained
- Extract close price arrays from Yahoo Finance chart API for indices/commodities
- Switch CoinGecko crypto fetch to /coins/markets endpoint with 7d sparkline data
- Render inline SVG sparklines color-coded green/red by price direction
- Fix Vite dev proxy for CoinGecko (was hitting root instead of /api/v3/simple/price)
- Add endpoint=markets support to CoinGecko edge function
Three new crypto/market intelligence panels with self-fetching data:
- Market Radar (macro-signals): 7 signals from Yahoo Finance, mempool.space,
alternative.me — liquidity, flow structure, macro regime, BTC trend with
30d VWAP, hash rate, mining cost, fear & greed. SVG sparklines and donut gauge.
- BTC ETF Tracker (etf-flows): 10 spot Bitcoin ETFs via Yahoo Finance 5-day
chart with estimated net flows, volume, and price change per ETF.
- Stablecoins (stablecoin-markets): CoinGecko proxy for peg health monitoring
(ON PEG / SLIGHT DEPEG / DEPEGGED) and supply/volume breakdown.
All endpoints include CORS origin guard, input validation, in-memory caching
with stale fallback. Panels registered in both full and tech variants.
## Summary
- Stop destroying the YouTube player when the tab becomes hidden —
audio/video now continues in background tabs
- Suspend the 5-minute idle timer while the tab is hidden so it doesn't
silently kill background playback
- Idle timer resumes when the user returns to the tab
## Test plan
- [ ] Open the live news panel and start a stream
- [ ] Switch to another browser tab — verify audio continues playing
- [ ] Minimize the browser window — verify audio continues
- [ ] Return to the tab after >5 minutes — verify stream is still
playing
- [ ] Leave the tab visible but don't interact for 5 minutes — verify
idle pause still triggers
Previously, switching tabs or minimizing the window would immediately
destroy the YouTube player, cutting audio and video. Now the stream
continues playing when the tab is hidden, with the idle timer suspended
so it doesn't silently kill playback after 5 minutes in the background.
UCDP API fields are conflict_id, location, side_a, intensity_level etc.
(snake_case) — proxy was reading ConflictId, Location, IntensityLevel
(PascalCase), causing all values to be empty/0.
Also paginate through all results instead of just first page.
When Groq returns 500, the queue kept firing requests against a failing
API. Now 500+ errors pause the queue for 30s (429 stays 60s) and flush
pending jobs, preventing the retry storm visible in console.
- Chatham House RSS returns 403 from cloud IPs, use Google News fallback
- FAO FPMA feed returns HTML (Angular app), not RSS — replaced with fao.org/rss
- Updated rss-proxy allowlist domain
- Endpoint was conflict-event (singular), fixed to conflict-events (plural)
- app_identifier must be base64("name:email"), passed as query param
- Response fields are (events, fatalities, event_type) per row, not
pre-aggregated columns — fixed aggregation logic
- Remove Arab News and Times of Israel feeds (403 from cloud IPs)
Replace hardcoded conflict floor scores with real data from three sources:
- ACLED battles/explosions/violence against civilians (30-day events)
- UCDP conflict classification (war vs minor vs none, data-driven floors)
- HDX HAPI aggregated monthly conflict counts (fallback/validation)
New CII component 'conflict' weighted 30% of event score. Updated formula:
unrest(25%) + conflict(30%) + security(20%) + information(25%)
- Gradient background with left accent sidebar in level color
- Large 120px CII score with /100 + labeled score bar with tick marks
- Semicircle arc gauge on right with score + level badge
- Data indicator legend (threat, military, markets, convergence, signals)
- No-score fallback shows 4 feature cards with descriptions
- Bottom bar with W logo circle, brand text, and CTA button
- Subtle grid background, status pill, date stamp
- Story renderer: larger fonts, better contrast (#777 vs #555 headers,
#e0e0e0 vs #ddd text), visible separators (#222 vs #1a1a2e), removed
grid background noise, bigger score (72px), bigger headlines (26px)
- OG image: pass score & level params to og-story endpoint, improve
contrast, larger score text (96px), better fallback when no score
- Meta tags: include s= and l= query params in og:image URL
- /api/og-story: generates SVG OG image with country name, CII score, level
- /api/story: serves meta tags to social crawlers, redirects real users to SPA
- Deep links now point to /api/story for proper crawler support
- Pass CII score/level through URL params for dynamic OG images
- Add NASA FIRMS satellite fire detection map layer and panel
- Add temporal baseline anomaly detection (Welford's algorithm, Redis-backed)
- Wire signal aggregator with fires, temporal anomalies
- Remove 10 dead service files and unused markdown docs
- Deduplicate RSS feeds, clean up story templates
- Fix data freshness, status panel, and verification checklist
- Create og-image.png for social sharing meta tags
- Update README with signal aggregation, source tiering, edge architecture
- Bump version to 2.1.4
Add signal ingestion methods for all 9 new services:
- ingestSatelliteFires() - NASA FIRMS fire detection
- ingestDefenseActivity() - war analysis military activity
- ingestInfrastructureStatus() - IXP/cable/gateway status
- ingestSentimentAlerts() - social media sentiment shifts
Add 4 new SignalType enums:
- satellite_fire
- defense_activity
- infrastructure_status
- sentiment_shift
Update getSummary() and regional convergence to include new signal types.
This integrates all new intelligence services into the main signal feed.
Create src/services/api-server.ts:
- REST API with Express.js
- Authentication via API key (header or query param)
- Rate limiting (100 requests/minute)
- Endpoints: /stories, /signals, /reports, /countries, /categories
- Webhook service for event notifications
- Data export (JSON, CSV, Markdown)
- CORS support for external apps
This completes all features from OSINT research!
Create src/services/infrastructure-map.ts:
- Track IXPs, data centers, cable landings, gateways for 10 regions
- Infrastructure report per region (active/degraded/offline counts)
- Detect cable integrity concerns (Red Sea, Taiwan Strait)
- Convert to threat signals for intelligence feed
- 20+ nodes tracked across monitored countries
This addresses Priority 2.1 from OSINT research.
Create src/services/correlation-engine.ts:
- Temporal correlations (events within same time window, shared keywords)
- Spatial correlations (events within 500km)
- Thematic correlations (3+ events sharing theme)
- Cascade detection (A happens → B happens 6-48h later)
- Temporal patterns (recurring events detection)
- Geographic clustering (events within 100km)
Converts correlations to threat signals for intelligence feed.
This is the "brain" that finds hidden connections between events.
Create src/services/ai-detection.ts:
- Check for AI generation artifacts (EXIF, GPS, lighting, edges, patterns)
- Bellingcat\'s seven deadly sins of bad OSINT
- Quick verification checklist for content validation
- Helps users distinguish real news from AI-generated/propaganda
This addresses Priority 1.2 (Disinformation Detection) from OSINT research.
- Create src/services/firms-satellite.ts for NASA Fire Information API integration
- Fetch active fires, thermal anomalies in monitored regions (Ukraine, Iran, Israel/Gaza, etc.)
- Detect fire anomalies vs baseline activity
- Convert fire data to threat signals for intelligence feed
- Update OSINT_RESEARCH.md to mark as IMPLEMENTED
This adds satellite imagery capability to detect fires, construction, and thermal
activity in conflict zones - a key differentiator from other OSINT tools.
Researched top OSINT tools (Spiderfoot, Shodan, Babel X, Censys) and war analysis
methodologies to identify improvement opportunities for World Monitor.
Priority improvements identified:
- Satellite imagery integration (NASA FIRMS)
- Disinformation detection (Bellingcat framework)
- Social media sentiment tracking
- Internet infrastructure awareness
- Verification checklist UI
Created OSINT_RESEARCH.md with detailed analysis and next steps.
- Detect when activity levels deviate from historical baselines
- Z-score calculation for military, vessels, protests, news
- Alert when activity is 1.5x-3x normal for the time period
- Supports weekday and month-specific baselines
Deep links can't send images. Now: mobile uses Web Share API (sends
image natively), desktop copies image to clipboard then opens WhatsApp
Web so user can paste it.
Block story rendering when data sources haven't loaded yet
(hasSufficientData check + news cluster check). Shows toast
notification instead of generating an incomplete/stale story.
- Throttle classifyWithAI to 2 concurrent requests with 300ms delay
- Pause queue for 60s on 429, flush pending jobs to avoid memory leak
- Cache vessel theater counts in localStorage (30min TTL) so page
refresh instantly restores vessel data while AIS stream re-accumulates
Theater posture now considers Country Instability Index: CII ≥85 forces
critical, ≥70 forces elevated. Fixes Iran Theater appearing lower than
Baltic despite CII 92 and active crisis reporting. Also lowered Iran
naval thresholds (elevated:2, critical:5) to account for poor AIS
coverage in Persian Gulf where military vessels routinely go dark.
Shows classified event category (Conflict, Cyber, Protest, etc.) as a
colored badge on each news item, leveraging the Groq LLM classification
pipeline. Color matches threat level. General category hidden to reduce noise.
Thresholds were calibrated for classified-level data visibility (50+ aircraft for
Iran ELEVATED). Open-source trackers (OpenSky/Wingbits) see ~10-20% of actual
military flights. Lowered all theaters ~5-6x to match real detection rates.
Added localStorage persistence to cached-theater-posture so the Strategic Posture
panel shows last-known data instantly on page reload instead of starting empty.
Wire geo-hub keyword matching into RSS feed parsing to attach lat/lon
to news items, aggregate locations at cluster level, and render as
ScatterplotLayer on the deck.gl map colored by threat level.
Add fetchCountryMarkets() to Polymarket service with country-to-tag
mapping and variant matching, shown in CountryIntelModal with yes/no
bars when clicking a country.
Switch from volume-only market fetching to tag-based event queries
for better relevance. Variant-aware tags (geopolitical vs tech).
Deduplicates across tag queries, falls back to top-volume markets
only when needed. Add clickable links to Polymarket event pages.
- Add DFMGI.AE for UAE (DFM General Index)
- Use range=1mo instead of 5d to handle Sun-Thu trading weeks
- Take last ~5 trading days from monthly data for weekly % change
- Remove broken Qatar symbol (^QSI returns no data)
- Stock index API endpoint (Yahoo Finance, Redis 1h cache, ~50 countries)
- Stock chip in modal: shows weekly % change with green/red styling
- Pause deck.gl renders while country modal is open (fixes flickering)
- Suppress signal modal clicks while country modal is visible
- Single stock fetch reused for both UI chip and AI brief context
- Military signals now counted by geographic bounding box (lat/lon in country)
instead of operator flag — matches what posture panel shows
- Add country aliases for headline matching (Israel → israeli, gaza, hamas, idf...)
- Feed convergence scores + regional alerts to AI context
- Expanded prompt: 5 sections, 300-400 words, military posture analysis,
regional context, cross-signal correlation
- Bump max_tokens 500→900, cache version ci-v1→ci-v2
- Add 25+ country bounding boxes for geo-matching
Click empty map area to detect country via Nominatim reverse geocoding,
highlight it with GeoJSON boundaries, and show an AI-generated intel
brief (Groq, Redis-cached 2h) with CII score, active signals, and
contextual news headlines.
Prevents the jarring effect of all detected locations flashing
simultaneously when the app first loads. Flashes only trigger
after the initial feed collection completes.
Added navalThresholds per theater and recalcPostureWithVessels() that
uses "either triggers" logic: if aircraft OR vessels exceed their
respective thresholds, the theater level escalates. Previously only
aircraft counts were used, so 40 vessels + 10 aircraft showed "normal".
DeckGLMap.setCenter() uses maplibreMap.flyTo() (animated, 500ms) but
callers immediately followed with setZoom() which interrupted the
animation. Added optional zoom param to setCenter() and updated all
callers in App.ts to use setCenter(lat, lon, 4) instead of separate calls.
- PR #33: Disambiguate NAVY callsign by origin country (US vs UK)
- PR #34: Use typecode fallback in determineAircraftInfo + Wingbits
enrichment (guarded: only when type is still 'unknown')
- PR #35: Persist typeCode in enriched data (skip the broken
pre-filter removal and false-positive regex from original PR)
Closes#33, #34, #35
The pan formula was incorrectly dividing by zoom:
pan.x = width/(2*zoom) - pos[0] // Wrong
Corrected to:
pan.x = width/2 - pos[0] // Correct
The zoom-independent formula ensures clicking theaters in Strategic
Posture panel correctly centers on that region at any zoom level.
- Remove CCA from MILITARY_PREFIXES (Air China airline, not military)
- Remove duplicate IAF entry from Middle East section
- Move AIRLINE_CODES to module level Set for O(1) lookup
- Remove duplicate entries (ELY, THY, SWR)
- 20,396 verified military hex IDs from adsbexchange.com
- Updated daily by ADS-B Exchange community
- O(1) lookup using Set
- Combines with callsign detection for comprehensive coverage
Focus on ranges where military/civilian separation is clear:
- US (AE/AF), UK, France, Germany, NATO, Russia, China, Australia, Japan
For other countries, rely on callsign detection since hex ranges
include commercial aircraft mixed with military.
Military aircraft have specific transponder ID ranges by country.
This matches how OpenSky UI identifies military aircraft.
Now checks both callsign patterns AND hex ranges.
Ranges added for: USA, UK, France, Germany, Italy, NATO, Israel,
Saudi, UAE, Qatar, Turkey, Russia, China, Iran, India, Pakistan,
Australia, Japan, South Korea
- Remove UAE from military prefixes (conflicts with Emirates airline)
- Use more specific UAEAF, BAHAF, OMAAF for Gulf military
- Expand airline exclusion list with major carriers worldwide
- This improves accuracy by removing commercial flight false positives
- Add more US military prefixes (ARMY, NAVY, USAF, OPS, etc.)
- Add Middle East military prefixes (RSAF, UAE, EMIRI, etc.)
- Add NATO tactical callsigns (SWORD, LANCE, etc.)
- Add short tactical pattern (3 letters + 1-2 digits) with airline exclusion
- Exclude known airline codes (SVA, QTR, THY, etc.) from short patterns
The patterns [A-Z]{2,3}\d{3,4} and [A-Z]{3,4}\d{2,4} were matching
commercial airlines like PGT5873, IAW9011, IZG3020. New pattern only
matches 4+ letter callsigns like DUKE01, VIPER12 which are typical
military tactical callsigns.
BUG FIX: Wingbits API returns short field names like 'h' for icao24,
'f' for flight, 'la' for latitude, etc. The previous code was looking
for long names like 'icao24', 'callsign', 'latitude' which don't exist
in the response, causing all flights to be filtered out.
- Add /flights and /flights/batch endpoints to wingbits proxy
- Add fetchMilitaryFlightsFromWingbits() to theater-posture API
- Try OpenSky first, fallback to Wingbits on 429/failure
- Transform Wingbits data to match existing flight format
- Add 'source' field to response ('opensky' or 'wingbits')
- Switch Times of Israel from Railway to Vercel RSS proxy (already allowlisted)
- Increase stale cache TTL from 1 hour to 24 hours
- Add 7-day backup cache as last resort during prolonged outages
- Better error handling: try stale → backup → error
- Replace base Panel.showLoading() with radar sweep animation
- Add customizable message parameter to showLoading(message)
- Update CIIPanel to use unified loader
- Add panel-loading CSS classes matching Strategic Posture style
- Consistent green radar aesthetic across all loading states
### Motivation
- Ensure the variant-change reset removes the same panel-order key used
across the app and centralize the key to avoid future mismatches.
### Description
- Added a `PANEL_ORDER_KEY` constant (`'panel-order'`) to `src/App.ts`
and used `this.PANEL_ORDER_KEY` for reads/writes instead of the
scattered literals; replaced the mistaken `'worldmonitor-panel-order'`
removal with the unified key and updated migration code and
`getSavedPanelOrder`/`savePanelOrder`.
### Testing
- No automated tests were run for this change.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_b_6977b8c537cc832eb4ef5d877cb282d6)
### Motivation
- Map share URLs must unambiguously reflect when every layer is disabled
so viewers get the exact layer state.
- Empty or missing `layers` query values were previously ambiguous, so
the URL builder and parser need to explicitly encode and interpret that
state.
### Description
- `parseMapUrlState` now treats a present `layers` parameter with value
`''` or `'none'` as “all off” and sets every key in `LAYER_KEYS` to
`false`, while preserving the existing fallback behavior when the
parameter is omitted.
- `buildMapUrl` always emits a `layers` query parameter, using `none`
when no layers are active and otherwise joining active keys with commas.
- The parser normalizes and trims the `layers` value before splitting to
robustly handle whitespace and empty tokens.
### Testing
- No automated tests were run.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_b_6977b8cdf7dc832eae02f128875f8fed)
### Motivation
- Ensure share URLs fully capture map layer state by including every key
from the `MapLayers` type (geopolitical and tech layers).
- Keep `parseMapUrlState` and `buildMapUrl` consistent so both parsing
and serialization cover the same complete set of layers.
### Description
- Expanded the `LAYER_KEYS` array in `src/utils/urlState.ts` to include
`ais`, `protests`, `military`, `spaceports`, `minerals`, `startupHubs`,
`cloudRegions`, `accelerators`, `techHQs`, and `techEvents` so it
matches `MapLayers`.
- `parseMapUrlState` continues to use `LAYER_KEYS` to populate a
`MapLayers` object from the `layers` query param, now covering the full
set.
- `buildMapUrl` uses `LAYER_KEYS` to serialize active layers into the
`layers` query param, ensuring all layer flags are represented in share
URLs.
- Change is contained to `src/utils/urlState.ts` and committed.
### Testing
- No automated tests were run for this change.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_b_6977b8c992e0832eb1acc9f4dd730f11)
The View Region button was navigating to wrong locations because
`if (lat && lon)` returns false when either coordinate is 0.
Changed to typeof check to properly handle all valid coordinates.
- Change default from 'fighter' to 'unknown' for unrecognized callsigns
- Add 'unknown' count to aircraft breakdown
- Show bombers, transport, drones, and 'other' in summary
- Fixes misleading '179 fighters' when most were unclassified
- Condense README from 3,896 lines to ~230 lines
- Move full documentation to docs/DOCUMENTATION.md
- Add social badges (stars, forks, license, last commit)
- Add 'Why World Monitor?' value proposition table
- Add Quick Start one-liner
- Use collapsible sections for data layers
- Add 'Support the Project' call to action
- Link to full docs throughout
Add useFallbackOnly flag to skip auto-detection for channels
where it returns wrong video IDs. These channels will now
always use their hardcoded fallback video IDs.
- Multi-step progress showing 4 World Bank indicators being fetched
- Animated globe with spinning ring
- Staggered pulse animation on each indicator row
- Shows "Analyzing 200+ countries" context
- Search both title and description for better keyword coverage
- Deduplicate results (article matching multiple monitors shown once)
- Show match count: "3 matches" or "Showing 10 of 25 matches"
- Show search scope when no matches: "No matches in X articles"
- Add v1.9 migration with strategic-posture in priority panels
- Add emoji fallback when no typed aircraft/vessel breakdown exists
- Use proper emoji variation selectors for airplane (✈️)
- hotspotBoost: 30 → 10 max
- newsUrgencyBoost: 15 → 5 max
- focalBoost: 20 → 8 max
- Total max boost: 65 → 23 points
Prevents multiple countries from hitting 100 simultaneously
- ACLED changed API endpoint from api.acleddata.com to acleddata.com/api/
- Add Bearer token authentication via ACLED_ACCESS_TOKEN env var
- Add baseline scores fallback when ACLED is unavailable (no more 500s)
- Better error logging for auth failures
Root cause: Circuit breaker's 5-min cache AND vesselCache were both
caching empty results, blocking fresh data from showing for 5 minutes.
Fixes:
- Add clearCache() method to circuit breaker
- Don't cache empty vessel results in vesselCache
- Clear both caches when stream initializes
- Clear both caches when first vessel arrives
- Add re-augmentation at 90s and 120s (in addition to 30s/60s)
UX improvements:
- Add live elapsed time counter during loading
- Add "30-60 seconds" guidance note
- Stop timer on render/error/destroy (no memory leaks)
Also fixes focal point headlines showing irrelevant content by
only including headlines where entity name appears in title.
- Add stale cache fallback when OpenSky is rate-limited (1h TTL)
- Return cached data instead of 500 error when API fails
- Improve empty/error state messages with context
- Add retry buttons to no-data states
- Show stale data warning when using cached fallback
- Korean Peninsula (covers Korean DMZ area)
- South China Sea (disputed waters)
- Eastern Mediterranean (Israel/Syria/Lebanon)
Bump cache key to v2 to invalidate old 4-theater cache.
- Add missing byOperator to API response (was causing crash)
- Deep clone postures to prevent cached data mutation
- Add drone detection patterns (RQ/MQ/REAPER/PREDATOR)
- Separate drone from reconnaissance in callsign detection
- Add vessel fields to TheaterPostureSummary type (destroyers, frigates,
carriers, submarines, patrol, auxiliaryVessels, totalVessels)
- Update API to return theater bounds for client-side vessel matching
- Panel fetches vessels client-side (AIS WebSocket) and merges with
server-side aircraft data (OpenSky API)
- Display shows AIR and NAVAL sections when vessels detected
- Add posture-section-label CSS for section headers
- Create /api/theater-posture.js endpoint that fetches military flights
from OpenSky, calculates theater postures, and caches in Upstash Redis
- Add cached-theater-posture.ts client service with deduplication
- Update StrategicPosturePanel to fetch from cached endpoint independently
- Update App.ts to use cached postures for critical alert banner
Benefits: Theater posture calculation shared across all users via Redis
cache (5-min TTL), panel works without military layer enabled
- Remove Trump mention from date context for tech variant
- Add variant-aware system prompts:
- Tech: "Focus ONLY on technology, startups, AI, funding..."
- Tech: "IGNORE political news, trade policy, tariffs..."
- Use tech-appropriate examples: "OpenAI announced...", "Series B..."
- Bump cache version to v3 to force new summaries
INTEL_SOURCES (Defense One, Breaking Defense, The War Zone, Janes,
Bellingcat) was being fetched for all variants and added to allNews,
which fed geopolitical content to AI insights in tech variant.
Now gated by SITE_VARIANT === 'full'.
- Gate gdelt-intel, cii, cascade, strategic-risk panels to full variant only
- Wire up TechReadinessPanel (was implemented but never connected):
- Add to TECH_PANELS config with priority 1
- Export from components index
- Instantiate in App.ts
- Reduces console noise and unnecessary data fetching in tech variant
Tech variant was fetching military flights, vessels, protests, and
outages even though it has no CII/focal point panels. This wasted
bandwidth and polluted console logs.
Changes:
- Gate loadIntelligenceSignals() to only run for SITE_VARIANT='full'
- Gate scheduled intelligence refresh to only run for full variant
- This stops: military flights, military vessels, protests (ACLED/GDELT),
outages, and AIS shipping data fetching for tech variant
Tech variant now only fetches data relevant to tech/startup news.
Issues fixed:
1. Tech variant insights panel was using geopolitical signal data
(military flights, protests, AIS) which polluted AI summaries
2. Insights panel was at priority 2 (bottom) for tech variant
Changes:
- panels.ts: Move insights to priority 1 (top) in TECH_PANELS
- InsightsPanel.ts: Skip signalAggregator and focalPointDetector
for tech variant - only summarize tech news without geo context
- App.ts: Add one-time migration to move insights to top for
existing tech variant users
Now tech variant AI insights only analyzes tech/startup news without
geopolitical military/protest/outage correlations.
The Redis cache key for summaries was built from headlines + mode +
geoContext but did NOT include the site variant (full vs tech). This
caused cross-site cache collisions where a summary generated for the
full variant could be returned to tech variant users (and vice versa).
Changes:
- Pass SITE_VARIANT from frontend to summarization API
- Include variant in cache key: `summary:{variant}:{hash}{geoHash}`
- Updated both groq-summarize and openrouter-summarize endpoints
Now cache keys are scoped per variant, preventing incorrect summaries.
The dev.events RSS feed is limited to 100 items sorted by "date added"
(not event date), causing major events like STEP Dubai to be pushed out
when newer events are added. Added a curated events list as fallback
for important conferences that may fall off the RSS feed:
- STEP Dubai 2026 (Feb 11-12) - 8,000+ attendees, AI economy focus
- GITEX Global 2026 (Dec 7-11) - World's largest tech show
- TOKEN2049 Dubai 2026 (Apr 29-30)
- Collision 2026 (Jun 22-25) - Toronto
- Web Summit 2026 (Nov 2-5) - Lisbon
Curated events are deduplicated with feed data to avoid duplicates.
- README: Title to "World Monitor v2", AI-powered description
- index.html: Title "Global Situation with AI Insights"
- All meta tags updated (og, twitter, JSON-LD)
- Added AI keywords and features
- Updated site.webmanifest with AI branding
The original documentation incorrectly described surge detection as
operator-count based. The actual algorithm uses:
- Baseline-based detection (2x historical activity = surge)
- Separate foreign presence detection for operators outside home regions
- Theater-based grouping with 48-hour baseline window
- Add getFocalPointForCountry() and getNewsCorrelationContext() to focal-point-detector
- Enhance foreignPresenceToSignal to include news correlation from focal points
- Map operator countries and affected regions to ISO codes for focal point lookup
- Update SignalModal to display correlated focal points and news headlines
- Add military_surge type label and styling for the signal modal
- Add CSS for focal point and news correlation sections in intelligence findings
- Add loadIntelligenceSignals() that ALWAYS fetches protests, military,
outages regardless of layer visibility
- Add intelligenceCache to prevent duplicate API calls when layers enabled
- Remove redundant layer-gated initial load tasks (handled by intelligence)
- Remove redundant refresh schedules for intelligence layers
- Add surge/foreign presence detection to loadIntelligenceSignals()
- Fix missing ACLED warning status in cached loadProtests() path
This ensures CII scores are accurate even when map layers are disabled.
Previously, Iran showed S:0 (no signals) because military layer was off.
Wait for MapLibre 'load' event before initializing deck.gl overlay.
Previously, deck.gl layers were created before MapLibre finished
initializing its view state, causing incorrect coordinate projections
on first render. After any view change, the map would correct itself.
- Add missing military vessel/flight cluster layers
- Add cluster tooltips and click type mappings
- Fix activity type mismatches (deployment/transport vs patrol/surveillance)
- Add congestion coloring to AIS density (orange for deltaPct >= 15)
- Add port type-based colors and tooltip icons
- Fix getHotspotLevels/setHotspotLevels to use h.name
- Fix setLayerReady to use 'active' class with layer state check
- Create gdacs.ts service fetching from GDACS API (earthquakes, floods,
tropical cyclones, volcanoes, wildfires, droughts)
- Filter to Orange/Red alerts only for relevance
- Merge GDACS events with EONET natural events with deduplication
- Color-code markers: red for Red alerts, orange for Orange alerts
- Prioritize GDACS data (more authoritative) over EONET for overlaps
Feature parity improvements:
- Add layer help popup with ? button (explains all layer types)
- Add LAYER_ZOOM_THRESHOLDS constant for zoom-based label visibility
- Add toggleLayer() public method to toggle layers on/off
- Make zoomIn()/zoomOut() public methods for external access
- Add military cluster data storage (militaryFlightClusters, militaryVesselClusters)
- Add getMilitaryFlightClusters() and getMilitaryVesselClusters() getters
- Store clusters in setMilitaryFlights/setMilitaryVessels methods
- Add news storage for related news lookup on hotspot click
- Add getRelatedNews() method matching old Map.ts behavior
- Fix click handler to show popup with related news + GDELT intel
- Add HTML overlays for high-activity hotspots with CSS pulsating animation
- Filter high-activity hotspots from deck.gl layer to avoid double-render
- Add escapeHtml for safety in HTML templates
- Add debug logging for panel persistence investigation
Problem: CII showed cached backend scores immediately, then updated
when focal points ready - showing misleading preliminary data.
Fix:
- Show "Analyzing intelligence..." state until focal points ready
- Only calculate and render after focal-points-ready event
- Remove unused cached scores logic (simpler, more accurate)
Problem: Once cached scores loaded, usedCachedScores=true forever,
blocking recalculation even when focal points became ready.
Fix: Add forceLocal parameter to refresh() - when focal-points-ready
fires, it now calls refresh(true) to force local recalculation with
focal point urgency boosts applied.
LLMs have outdated knowledge (pre-Jan 2025), causing errors like
"former President Trump" when Trump is current president.
Added dateContext to all summarization prompts with:
- Current date
- Trump is current president (second term, Jan 2025)
Moved TIER1_COUNTRIES to src/config/countries.ts to break the cycle:
country-instability → focal-point-detector → signal-aggregator → country-instability
Now:
- config/countries.ts (no dependencies)
- signal-aggregator imports from config/countries
- country-instability re-exports for backwards compatibility
getCountryScore() was missing the focalBoost that calculateCII() has.
This inconsistency would cause single-country lookups to return
different scores than the batch calculation.
Problem: CII was calculating before FocalPointDetector.analyze() ran,
so the urgency map was empty and focal boosts weren't applied.
Fix: InsightsPanel now dispatches 'focal-points-ready' event after
analysis completes. App.ts listens and triggers CII refresh.
This ensures Iran shows elevated/high when FocalPointDetector
marks it as critical, rather than staying at 38 (green).
Problem: Every panel summary started with 'Breaking news tonight:' because
the prompt said 'write like a news anchor opening the evening news'.
Fix: Rewrote prompts to:
- Explicitly forbid 'Breaking news', 'Good evening', 'Tonight' openings
- Remove TV news anchor framing entirely
- Instruct to start directly with subject: 'Iran's regime...', 'The US Treasury...'
- Focus on substance over style
Updated both groq-summarize.js and openrouter-summarize.js
CII now uses focal point urgency (critical/elevated/watch) to boost scores:
- critical focal point: +20 points
- elevated focal point: +10 points
This ensures countries like Iran show 'elevated' or 'high' when:
1. AI insights detect breaking news (I:80)
2. FocalPointDetector marks them as critical (news + signal correlation)
The CII now reflects the same intelligence the AI summarizer uses,
eliminating the disconnect where AI says 'armada, bloodshed' but
CII shows green/normal.
Problem: CII showed Iran as 'normal' (green, 38) despite AI insights
showing breaking news about 'US armada to Iran, deadly crackdown'.
This happened because S:0 (no military detections) diluted the I:80
(high news velocity).
Fix: Add newsUrgencyBoost that elevates scores when Information is high:
- I >= 70: +15 points (breaking news)
- I >= 50: +10 points (significant news)
- I >= 30: +5 points (moderate news)
Example with fix: Iran with I:80 now scores ~53 (elevated/orange)
instead of 38 (normal/green)
UN News and other servers return gzip-compressed RSS. The proxy was
concatenating binary chunks as strings, corrupting non-UTF8 bytes.
Fix: Use Buffer.concat() and decompress gzip/deflate responses.
Correlates news entities with map signals to identify "main characters"
appearing across multiple intelligence streams:
- Extract entities from all 80+ news sources via NER
- Cross-reference with map signals (flights, vessels, outages, protests)
- Score focal points where news + signals converge
- Generate rich AI context for correlation-aware summarization
New files:
- focal-point-detector.ts: Core detection and scoring service
- signal-aggregator.ts: Geographic signal aggregation
UI shows top focal points with urgency badges and signal icons.
AI prompts updated to leverage intelligence synthesis context.
- Rewrite prompts to vary output (no more "The dominant narrative...")
- Instruct model to lead with substance: location, action, impact
- Add explicit rule: NEVER start with meta-commentary
- New /api/risk-scores endpoint computes and caches scores in Redis
- Uses ACLED protest data + baseline geopolitical risk factors
- 10-minute cache TTL shared across all users
- CIIPanel and StrategicRiskPanel fetch cached scores on load
- Learning Mode banner hidden when cached scores available
- Eliminates 15-minute warmup for users
- Cluster datacenters at zoom < 5, show IconLayer at higher zoom
- Add renderDatacenterClusters() with HTML overlays
- Add datacenterCluster popup type showing total chips/power stats
- Add CSS styles for datacenter markers matching protest/techHQ pattern
- Show cluster badge with count, expand to individual on click
Base map tiles already display country names, so the custom
Country Labels layer was duplicating functionality.
Removed:
- COUNTRY_LABELS array (~80 countries) from geo.ts
- Toggle from layer controls in both Map.ts and DeckGLMap.ts
- renderCountryLabels() and createCountryLabelsLayer() methods
- 'countries' property from MapLayers type
- All countries config entries from panels and variants
- Related CSS styles
- Double MAX_RESULTS from 12 to 24
- Prioritize news over static infrastructure in search
- Update News24 URL to post-redirect destination (feeds.capi24.com)
- Add trailing slash to SCMP URL
- Add feeds.capi24.com to both proxy allowlists
Railway's RSS proxy had only 5 domains in allowlist, causing 403 errors.
Added all domains that use railwayRss() routing in feeds.ts:
- Al Arabiya, Arab News, Times of Israel, SCMP
- UN News, CISA, News24
- Plus IAEA, WHO, Crisis Group, Kyiv Independent, Moscow Times for future use
- CORS regex: now matches root domain (worldmonitor.app) not just subdomains
- Alert keywords: re-add important terms (military, drone strike, terror/cyber attack, evacuation order)
- Add /api/groq-summarize.js - Llama 3.3 70B (1000 req/day free)
- Add /api/openrouter-summarize.js - Fallback (50 req/day free)
- Create summarization service with fallback: Groq -> OpenRouter -> Browser T5
- Browser T5 only loads when cloud APIs fail (lazy loading)
- Add detailed progress bar with Step X/Y indicator
- Show provider badge (groq/openrouter) on World Brief
- Cache summaries for 30 min to respect rate limits
- Cooldown increased to 2 min between brief generations
Required env vars for Vercel:
- GROQ_API_KEY (get from console.groq.com)
- OPENROUTER_API_KEY (get from openrouter.ai)
- Show "Initializing ML models..." when worker not ready
- Show "Analyzing sentiment..." during sentiment classification
- Show "Generating world brief..." during summarization
- Add animated spinner and pulsing text for visual feedback
- Re-enable the ML worker import that was disabled
- Remove @xenova/transformers from rollup externals so it gets bundled
- Previous config had MLWorkerClass set to null, causing "not a constructor" error
- Switch summarization model from T5-small (45MB) to Flan-T5-base (250MB)
for significantly better quality summaries
- Add "World Brief" section to InsightsPanel that synthesizes top stories
into a coherent 2-sentence summary (vs useless per-headline summaries)
- Brief generation has 60s cooldown to avoid excessive model calls
- Add styled World Brief section with accent gradient
- Remove broken T5 summaries (model too weak for abstractive summarization)
- Focus on multi-source confirmed stories (2+ sources)
- Show fast-moving and alert stories
- Add stats bar: multi-source count, fast-moving count, alerts
- Improve sentiment visualization with bar and overall tone
- Filter out single-source noise
These layers existed in buildLayers() with mapLayers.X checks but had
no UI toggles, making them inaccessible. Added toggles for both in the
geopolitical variant layer panel.
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
- Import APT_GROUPS and CRITICAL_MINERALS from config
- Add createAPTGroupsLayer() with red markers and yellow outline for cyber threat actors
- Add createMineralsLayer() with color-coded markers by mineral type (Lithium, Cobalt, Rare Earths, Nickel)
- APT Groups always visible in geopolitical variant (no toggle)
- Add minerals toggle to layer panel for geopolitical variant
- Add tooltips and click handlers for both layers
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
- Add clusterMarkers method for grouping nearby markers
- Create HTML overlay container for cluster badges
- Render Tech HQs with emoji icons and cluster count badges
- Render Tech Events with calendar icons and clustering
- Render Protests with severity icons and clustering
- Clusters expand on click to show popup with all items
- Single items show individual popup on click
- Clustering radius adjusts based on zoom level
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
- Remove FOCUS region selector from header (keep only map's view selector)
- Redesign legend as compact horizontal bar at bottom center
- Add proper shapes: triangles for bases, squares for datacenters, hexagons for nuclear
- Reduce whitespace in legend layout
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
Replaced ScatterplotLayer circles with IconLayer for distinct shapes:
- Military bases: TRIANGULAR icons (color-coded by operator)
- Nuclear facilities: HEXAGONAL icons (yellow/orange)
- Datacenters: SQUARE icons (purple)
- Hotspots: Remain as circles (appropriate for threat indicators)
Each marker type now has a unique geometric shape for easy identification.
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
- Nuclear: Yellow/orange hollow RING with inner dot (radiation symbol look)
- Datacenters: Purple filled circles with light border
- Bases: Color-coded by operator (US-NATO blue, Russia red, China orange, etc.)
- Removed non-working TextLayer emoji approach
Each marker type now has a distinct visual style beyond just color.
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
Restored visual distinction for markers that had icons in the
original D3/SVG implementation:
- Datacenters: 🖥️ icon
- Nuclear facilities: ☢️ icon
Uses TextLayer overlaid on ScatterplotLayer to render emoji icons.
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
Replaced separate Deck instance with MapboxOverlay which properly
integrates deck.gl into MapLibre's WebGL context. This fixes:
- Points moving when zooming/panning (view state sync)
- Coordinate offset issues (London appearing in France)
- Cursor issues (MapLibre now handles all interactions)
MapboxOverlay renders deck.gl layers directly into MapLibre's canvas,
eliminating all view state synchronization issues.
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
The circular sync between deck.gl and MapLibre was causing the
deck.gl layer to be offset from the base map. Fixed by:
- Making deck.gl the single source of truth (has controller enabled)
- Removing MapLibre->deck.gl sync that caused circular updates
- Adding initial sync on map load to ensure alignment
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
Wire up geo hub activity display on the map with glowing markers
similar to tech variant. Includes popup with top stories and click
to view related news.
The source dataset had placeholder coordinates clustering all US
datacenters around Kansas/Oklahoma. Fixed actual locations for:
- Mt Pleasant, Wisconsin (42.7°N, -87.9°W)
- Meta New Albany, Ohio (40.1°N, -82.8°W)
- OpenAI/Microsoft Atlanta (33.7°N, -84.4°W)
- Applied Digital Ellendale, ND (46.0°N, -98.5°W)
- Nebius New Jersey (40.1°N, -74.4°W)
- CoreWeave Denton, TX (33.2°N, -97.1°W)
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
- Add geo-hub-index.ts with 30+ strategic locations (capitals, conflict zones, chokepoints)
- Add geo-activity.ts for news aggregation by geopolitical location
- Add GeoHubsPanel.ts with red/orange color scheme for geopolitical theme
- Wire up geo hubs for full variant only (tech variant keeps tech hubs)
- Remove patents and high-tech exports from Tech Readiness (often stale/irrelevant)
- Tech Readiness now shows: Internet Users, R&D Spending (weights: R&D 35%, Internet 30%, Broadband 20%, Mobile 15%)
- Fix CSS bug: replace undefined var(--orange) with #ff8844
The map now stays fixed on the user's selected region when:
- Clicking items from lists (conflicts, bases, pipelines, etc.)
- Flash location animations
- New data flowing in
Popups now appear at the projected screen position of items
rather than panning the map to center on them. If an item is
off-screen, the popup still appears at its projected position.
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
- Replace 30+ blocked podcast/regional feeds with Google News fallbacks
- Fix World Bank API date range (7 years for delayed indicators)
- Add 30+ countries to Tech Readiness (UAE, Saudi, Qatar, etc.)
- Improve tooltip explaining metrics (🌐🔬📜📦)
- Fix a16z Blog, EFF News, KrASIA with Google News
The deck.gl canvas was blocking mouse events from reaching MapLibre
because it had pointer-events: auto for picking but controller: false.
Enable deck.gl's controller and sync view state changes back to MapLibre.
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
Root cause: The timestamp element had both classes 'map-timestamp' and
'deckgl-timestamp'. These classes set conflicting positioning:
- .map-timestamp: bottom: 8px; right: 10px;
- .deckgl-timestamp: top: 10px; left: 50%;
When all four positioning values (top/bottom/left/right) are set on an
absolutely positioned element without explicit width/height, it stretches
to fill the space between those edges - creating a 724x315px dark overlay.
Fixes:
- Remove 'map-timestamp' class from timestamp element in DeckGLMap.ts
- Add explicit bottom: auto; right: auto; width: auto; height: auto;
to .deckgl-timestamp CSS to prevent any accidental stretching
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
Changes:
- Add explicit z-index to MapLibre (1) and deck.gl overlay (2)
- Add overflow: hidden to wrapper
- Reverted to letting deck.gl create its own canvas
- Added effects: [] to disable any default deck.gl effects
- Set canvas background to transparent after deck initializes
- Added comprehensive CSS rules to ensure all canvas elements
and containers have transparent backgrounds
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
Instead of relying on deck.gl's default canvas/context creation,
manually create canvas and WebGL context with explicit alpha support:
- Create canvas element with transparent background style
- Get WebGL2/WebGL context with alpha: true, premultipliedAlpha: true
- Set gl.clearColor(0, 0, 0, 0) for transparent clear
- Pass pre-created canvas and gl context to Deck constructor
This ensures the WebGL context is properly configured for alpha
transparency from the start, allowing the MapLibre base map to
show through the deck.gl overlay.
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
Two-pronged approach to fix the dark semi-transparent overlay:
1. Add MapLibre background layer:
- Added 'background' layer with dark color (#0a0f0c) to MapLibre style
- This ensures no transparent gaps while tiles are loading
2. Set WebGL clear color to transparent:
- Added onWebGLInitialized callback to Deck initialization
- Explicitly set gl.clearColor(0, 0, 0, 0) for transparent background
- Enable proper alpha blending with gl.blendFunc
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
- Switch World Bank API from Edge to Node.js runtime (Edge gets 403)
- Add User-Agent header for World Bank requests
- Remove broken feeds: NFX Essays, First Round Review
- Fix a16z Blog URL (use future.a16z.com/feed)
- Fix CSIS Tech URL (use Google News RSS fallback)
Root cause: CONFLICT_ZONES config had inconsistent coordinate formats.
Red Sea Crisis and South Lebanon entries were in [lat, lon] format
while other entries were in [lon, lat] format (GeoJSON standard).
This caused polygons to render in completely wrong locations.
Fixes:
- Fix Red Sea Crisis coords: [[12,42]...] → [[42,12]...] (swap to [lon,lat])
- Fix Red Sea Crisis center: [14,43] → [43,14]
- Fix South Lebanon coords: [[33.0,35.1]...] → [[35.1,33.0]...]
- Fix South Lebanon center: [33.2,35.4] → [35.4,33.2]
- Add explicit transparent background to #deckgl-overlay CSS
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
- Add World Bank API endpoint with 17 tech indicators
- Add Tech Readiness panel showing country rankings by composite score
- Add Tech Hubs panel showing active tech hub activity from news
- Add tech activity map layer with animated markers
- Expand tech variant feeds (VC blogs, regional startups, unicorns, etc.)
- Remove ~250 lines of dead code from tech.ts variant config
- Fix API response shape consistency for no-data case
The MapPopup.show() method expects a PopupData object with:
- type: PopupType string identifying the popup renderer
- data: the actual data object to display
- x, y: click coordinates for popup positioning
Previously was passing raw objects which resulted in broken/missing popups.
Changes:
- Import PopupType from MapPopup
- Update handleClick to map layer IDs to PopupType and create proper PopupData
- Update all trigger methods (triggerConflictClick, triggerBaseClick, etc.)
to create proper PopupData with centered x/y coordinates
- Add getContainerCenter() helper for programmatic popup positioning
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
- Fix coordinate swap bugs in createCablesLayer, createPipelinesLayer,
and createConflictZonesLayer - data is already [lon, lat] format
- Fix coordinate order in triggerConflictClick, triggerPipelineClick,
and triggerCableClick to pass (lat, lon) to setCenter correctly
- Fix handleClick to not show popup for hotspots (they have their own handler)
- Add missing tooltips for all layer types (cables, pipelines, conflicts,
natural events, weather, outages, AIS density, military flights,
waterways, economic centers, tech HQs, accelerators, cloud regions,
tech events)
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
Implement Palantir-like interactive map experience using deck.gl with
MapLibre GL as the base map. Key changes:
- Add deck.gl (@deck.gl/core, @deck.gl/layers, @deck.gl/geo-layers) and
maplibre-gl dependencies for GPU-accelerated rendering
- Create DeckGLMap component with WebGL layers for:
- Undersea cables and pipelines (PathLayer)
- Military bases, nuclear facilities, datacenters (ScatterplotLayer)
- Conflict zones (GeoJsonLayer with polygons)
- Hotspots, earthquakes, weather, outages, protests
- AIS density, military vessels and flights
- Strategic waterways, economic centers
- Tech variant: startup hubs, tech HQs, accelerators, cloud regions
- Create MapContainer wrapper that conditionally renders:
- DeckGLMap (WebGL) on desktop with WebGL support
- Existing D3/SVG MapComponent on mobile for graceful degradation
- Add dark theme CSS styles for deck.gl controls, legend, layer toggles
- Import maplibre-gl CSS in main.ts
Desktop users now get smooth 60fps interactions with large datasets
while mobile users retain the optimized SVG experience.
https://claude.ai/code/session_01GTanC7R6aSQNsnijqJRUFz
Add allowlist filtering to StatusPanel so only variant-relevant
feeds and APIs are shown. Tech variant no longer shows Polymarket,
PizzINT, RSS2JSON, FRED, Intel, Thinktanks, etc.
- Change "Global Situation" to "Global Tech" for tech variant
- Rewrite FwdStart scraper to extract actual dates from HTML
(was showing "2m ago" because dates defaulted to now)
- Add vcblogs, regionalStartups, unicorns, accelerators, funding, producthunt panels
- These panels were added to config in PR #27 but not instantiated in App.ts
- Also enable layoffs panel by default in full variant
* Enhance tech variant as reference startup intelligence dashboard
- Add premium startup/VC feeds: The Information, PitchBook, CB Insights, Fortune Term Sheet
- Add VC blog panel with YC, a16z, First Round, Sequoia, NFX, Paul Graham essays
- Add regional startup news: EU Startups, Sifted, Tech in Asia, TechCabal, Inc42, etc.
- Add Unicorn Tracker panel with feeds tracking new unicorns and decacorns
- Add Accelerators panel with YC, Techstars, 500 Global, Demo Day news
- Expand startup ecosystems with 11 new hubs: Miami, Denver, Chicago, Barcelona,
Helsinki, Munich, Jakarta, Lagos, Nairobi, Mexico City, Ho Chi Minh City
- Add source tier rankings for startup/VC sources
https://claude.ai/code/session_01SxRf5YSHtV92EArUY2BvaJ
* Fix panel order and remove duplicate feed
- Move live-news to position 2 (after map) in tech variant panels
- Prevents CSS grid overlap issue with panel-wide class
- Remove duplicate EU Startups from funding feeds (already in regionalStartups)
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Add dedicated incidentio parser for incident.io status pages
- Use browser-like headers to avoid bot blocking
- Parse HTML fallback if JSON blocked
- Increase timeout to 10s
Saved panel order in localStorage was overriding the default order,
causing the wide live-news panel to appear in wrong position and
overlap other panels. Now live-news is always moved to position 0
regardless of saved order.
Transform HTML meta tags (title, description, keywords, OG, Twitter, JSON-LD)
based on VITE_VARIANT during build. Tech Monitor now has distinct branding
focused on AI/tech industry tracking.
- Add HTML response detection (blocked/redirected requests)
- Add JSON parse error handling
- Add alternative Statuspage format parsing
- Improve normalizeStatus to handle more indicator values
- Handle "All Systems Operational" in description
Search improvements:
- Add Tech HQs (unicorns, FAANG, public companies) to search index
- Add Accelerators (Y Combinator, Techstars, etc.) to search index
- Add Tech Events to search index after API load
- Fix search result icons for new types
- Update search hints
Repo cleanup:
- Add .claude/ and .cursor/ to .gitignore
- Remove outdated brainstorming/ folder
- Remove outdated docs/ folder
- Cluster nearby protests within same country
- Show cluster badge with event count
- Cluster popup shows severity breakdown, fatalities, verified count
- Sort by severity (high→low), then by event type (riots first)
- Adaptive cluster radius based on zoom level
- Compact orbital mode selector with icons
- Expands to show labels on hover
- Active variant has colored glow (green=WORLD, blue=TECH)
- Smooth animated transitions
- Extensible for future variants
- Generic clusterMarkers() function groups nearby markers by pixel distance
- Cluster radius adapts to zoom level (15px at zoom 4+, 25px at 3+, 40px otherwise)
- Clusters show count badge and icon of most prominent type
- Click cluster to see list of all companies/events
- Cluster popups show summary by type (Big Tech, Unicorns, Public)
- Tech events cluster popup shows upcoming events within 2 weeks
- API now supports ?limit=N and ?days=N parameters
- Map layer: 50 events within 90 days (reduces clutter)
- Panel: 100 events within 180 days (more context)
- Add statusio parser for GitLab and Docker Hub status pages
- Add slack parser for Slack custom status API format
- Add CircleCI back with working Statuspage.io endpoint
- Make SearchModal variant-aware with tech-specific sources
- Add techEvents to async data layers for proper loading state
- Fix panel heights with consistent grid sizing
- Fetch both Techmeme ICS and dev.events RSS in parallel
- Parse dev.events RSS for upcoming developer conferences/meetups
- Merge events from both sources with deduplication
- Extract location from dev.events descriptions for map markers
- Events now show from both sources in the Tech Events panel
- Add console logging to trace TechEvents panel toggle issue
- Clear panel settings when variant changes (previously only cleared map layers)
- Clear panel order when variant changes to prevent stale saved orders
- Add ServiceStatusPanel: monitors AWS, Azure, GCP, GitHub, OpenAI, Slack, etc.
- Add new feeds: Engadget, Fast Company, MIT Research, Show HN, YC Launches, Dev Events
- Remove broken feeds: Protocol Policy, Axios AI, AnandTech (shut down)
- Add "SOON" badge for events within 2 days
- Fix GitHub Trending to use actual RSS feed (mshibanami.github.io)
- Add domains to rss-proxy allowlist
- Add /api/tech-events endpoint parsing Techmeme events ICS feed
- Create TechEventsPanel with view modes (upcoming/conferences/earnings/all)
- Add tech events map layer with conference location markers
- Implement 500+ city geocoding database for worldwide coverage
- Purple markers with yellow glow for events within 14 days
- Click-to-zoom from panel items to map locations
- Integrated in tech variant (disabled in full variant)
Detects when aircraft from foreign nations concentrate in sensitive
regions (Persian Gulf, Taiwan Strait, Baltics, etc.) without waiting
for baseline establishment. Thresholds: 2 aircraft for US/Russia/China,
3 for UK/France/Germany when operating outside home regions.
Detect unusual transport activity spikes to military theaters:
- Track 5 theaters: Middle East, E/W Europe, Pacific, Horn of Africa
- Compare current transport flights to 48-hour baseline
- Alert when activity exceeds 2x baseline with 5+ aircraft
- Identify C-17/REACH patterns and nearby bases
- Severity: critical (4x), high (3x), medium (2x)
Suppressed during 15-minute learning mode like other alerts.
- Add global idle detection (2 min) to pause CSS animations when user inactive
- Destroy YouTube iframe when idle instead of just pausing (saves ~115 kB/s)
- Reinitialize YouTube player when user returns from idle
- Clean up idle detection listeners on destroy
The 30+ CSS animations were causing constant GPU work and memory churn.
YouTube iframe keeps allocating memory even when paused because the
iframe stays loaded. Now we completely destroy it after 5 minutes idle.
Geographic Convergence alerts were firing with unreliable data during
the 15-minute learning warmup period. Now these alerts are suppressed
until learning mode completes and data stabilizes.
- Append popup to document.body instead of map container
- Use viewport-relative coordinates for positioning
- Change to position: fixed with z-index: 1000
- Popup no longer clipped by map container overflow:hidden
- Smart vertical positioning: flip above click when space below is limited
- Account for bottom panels with 200px buffer
- Handle horizontal overflow by positioning left of click
- Approximate popup height (500px) for hotspot popups with escalation info
- Add 22 more African country labels (Niger, Mali, Chad, CAR, DRC, etc.)
- Add location field to Hotspot interface
- Add location values to all hotspots (e.g., "Sahel Region (Mali, Burkina Faso, Niger)")
- Display location in hotspot popup before coordinates
- Add descriptive location names to all conflict zones (e.g., "Eastern Ukraine", "Gaza Strip")
- Add Greenland to country labels list
- Change Nuuk hotspot subtext from "Arctic Dispute" to "Greenland Intel"
- Always refresh d3 selections from DOM before render to prevent stale references
- Use native DOM operations (removeChild) instead of d3.selectAll('*').remove()
- Add post-render verification to catch silent failures
- Add 30-second health check interval to detect and recover missing countries
- Improve ensureBaseLayerIntact to detect stale d3 selections
- Add detailed console logging for debugging layer issues
- Add ensureBaseLayerIntact() method to detect missing countries
- Call recovery check after setZoom() and setCenter()
- Triggers render() if base layer is empty but should have content
- Prevents black map after clicking location links
- Use native DOM API instead of d3 selections for safety checks
- Count actual .country elements to verify base layer content
- Add debug logging when base layer needs emergency re-render
- More robust orphan cleanup using querySelectorAll
- Create overlays-svg group inside dynamicLayerGroup on each render
- Fix references to use dynamicLayerGroup instead of svg root
- This restores military flight/vessel track line rendering
Add safety checks to detect if layer groups were removed from DOM:
- Verify baseLayerGroup/dynamicLayerGroup are still attached to SVG
- Check if base layer has actual content (.country elements)
- Recreate layer groups if missing
- Force base re-render if content is missing
This prevents the map from going blank after 20+ minutes of use.
- Cache country features from topojson (avoid repeated conversion)
- Split SVG into baseLayerGroup (static) and dynamicLayerGroup (per-render)
- Only rebuild base layer when container size changes
- Replace renderSanctions with updateCountryFills (toggle without rebuild)
- Refactor renderGrid/renderGraticule/renderCountries to accept target group
This reduces expensive full-SVG rebuilds on every render() call.
- Update version badge to 1.5.0
- Document CII Learning Mode (15-minute warmup with visual indicators)
- Add Entity Extraction System section (company/country/leader detection)
- Add Signal Context section ("Why It Matters" explanations)
- Expand Data Freshness with core vs optional sources
- Document Live News Stream optimizations (YouTube Player API, idle detection)
- Replace iframe rebuilding with persistent YT.Player instance
- Load YouTube IFrame API once via static promise
- Use player.playVideo()/pauseVideo() for idle detection
- Use player.mute()/unMute() for sound toggle
- Use player.loadVideoById() for channel switching
- Eliminates iframe reloads on every state change
- Add learning mode state tracking (startLearning, isInLearningMode, getLearningProgress)
- Show learning banner in CII panel with progress bar and countdown
- Show learning indicator in Strategic Risk panel (both full/limited views)
- Suppress CII spike alerts during learning period
- Fix floating point display by rounding component scores
- Add CSS styles for learning mode UI elements
- Top convergence risk is now clickable to zoom to location
- Recent alerts with location show click icon and zoom on click
- Added CSS styling for clickable risk items and alerts
- Location coordinates are now a button with arrow icon
- Clicking zooms map to location (zoom level 4) and dismisses modal
- Added CSS styling for location-link button
- Fix double-add bug: createConvergenceAlert was adding to array,
then updateAlerts added again. Now uses addAndMergeAlert only.
- Add getPriorityFromConvergence() with proper thresholds:
4+ types or 90+ score = critical, 3 types or 70+ = high
- Use stable IDs (conv-${cellId}) for convergence deduplication
- Improve alert title to show country name instead of generic text
- Use stable ID for CII alerts (cii-${country}) instead of random IDs
- Add ID-based deduplication in addAndMergeAlert() to update existing
alerts rather than creating duplicates
- NewsPanel: Store and remove scroll/click activity listeners in destroy()
- App: Add comprehensive cleanup for snapshot interval, refresh timeouts,
and global event listeners (keydown, fullscreen, resize, visibility)
- App: Add isDestroyed flag to stop refresh chains on destroy
- Cross-module-integration: Align priority thresholds with CII levels
(critical at 81+, high at 66+, medium at 51+)
1. analysis-worker.ts: Clear pending requests on reset() to prevent hanging promises
2. App.ts: Store time interval ID and add destroy() method for cleanup
- destroy() clears time interval, calls map.destroy(), disconnects AIS stream
1. Map.ts: Store and clear timestamp interval in destroy()
2. Panel.ts: Store tooltip close handler reference and remove in destroy()
3. WindowedList: Add destroy() method to clean up scroll listener
4. ActivityTracker: Add unregister() method to remove panel callbacks
5. NewsPanel: Add destroy() to clean up WindowedList and ActivityTracker
- Add render throttling to Map.ts (100ms minimum interval)
- Add scheduleRender() to coalesce rapid render calls
- Add CSS rule to pause all animations when tab is hidden
- Toggle animations-paused class on body via visibility API
- Add __APP_VERSION__ define in vite.config.ts
- Create vite-env.d.ts for TypeScript declaration
- Replace hardcoded version in App.ts with dynamic value
- Add warmup period (2 cycles) before emitting CII alerts
- Apply log scaling to unrest score for high-volume democracies
- Prevents false "instability spike" alerts on initial data load
- Remove nuclear facility labels from map (click for popover details)
- Filter protests to show only significant events on map:
- Always show: riots, high severity, fatalities > 0, validated
- Show medium at zoom >= 3
- Hide low severity (still counted in CII analysis)
- Use full country names instead of codes (SA → Saudi Arabia)
- Improve CII alert titles: "Saudi Arabia Instability Spike"
- Better summaries: "Instability index rose from 8 to 28 (+20)"
- Smarter merged alerts with progression tracking
- Priority-based insights: Critical/Significant/Developing
- Add "more findings" modal with all items scrollable
- Clickable findings open detail view
- Skip render() when container has 0 dimensions (browser throttling)
- Add visibilitychange listener to re-render when tab becomes active
- Prevents clearing SVG when nothing can be drawn
- Merge CorrelationSignals and UnifiedAlerts into single badge
- Create UnifiedFinding type to normalize both data sources
- Show CII spikes, geo convergence, and cascade alerts alongside signals
- Add critical priority styling (red with pulse animation)
- Priority-based left border colors on finding items
- Configurable alert time window (6 hours)
- Remove redundant ⚡ signal badge from SignalModal
- Keep 🎯 findings badge as single entry point
- Add showSignal() method to open modal for specific signal
- Move sound alerts to findings badge (plays on new signals)
- Connect findings badge clicks to signal modal
- Remove unused .signal-badge CSS (~800 bytes saved)
- Rename class to IntelligenceFindingsBadge (export alias for compat)
- Add event delegation for finding clicks (removes per-render listeners)
- Properly cleanup document click listener in destroy()
- Extract magic numbers to named constants
- Add missing signal type icons (news_leads_markets, velocity_spike, etc)
- Store signals as class property for event delegation access
- Renamed from IntelligenceGapBadge to show correlation signals/findings
- Shows detected signals from analysis worker with confidence levels
- Color-coded by signal count and confidence (gray/blue/orange)
- Pulse animation on new signals
- Clicking findings opens signal modal
- Added sector_cascade signal type to analysis-constants
Assessment & Documentation:
- Add docs/GEOPOLITICAL_ASSESSMENT.md with full platform analysis
- Strategic improvement roadmap with prioritized recommendations
Quick Win #1 - Data Freshness (intelligence gaps):
- Add getIntelligenceGaps() and getIntelligenceGapSummary() to data-freshness.ts
- Add human-readable messages explaining what analysts CAN'T see
- Add hasCriticalGaps() for alert integration
Quick Win #2 - Escalation Scores:
- Add escalationScore (1-5), escalationTrend, escalationIndicators to Hotspot type
- Update 11 major hotspots with scores (Sahel, Haiti, Horn of Africa, Moscow,
Beijing, Kyiv, Taipei, Tehran, Tel Aviv, Pyongyang, Sana'a)
Quick Win #3 - Signal Context ("Why It Matters"):
- Add SIGNAL_CONTEXT with whyItMatters, actionableInsight, confidenceNote
- Add getSignalContext() helper for all 10 signal types
- Explains analytical significance of each signal type
Quick Win #4 - Historical Context:
- Add HistoricalContext interface with lastMajorEvent, precedentCount,
cyclicalRisk fields
- Add whyItMatters field to Hotspot type
- Update major hotspots with historical precedents and geopolitical significance
Quick Win #5 - Propaganda Risk Flags:
- Add PropagandaRisk type and SourceRiskProfile interface
- Add SOURCE_PROPAGANDA_RISK mapping for state media (Xinhua, TASS, RT, CGTN)
- Add getSourcePropagandaRisk() and isStateAffiliatedSource() helpers
- Flag medium-risk state-affiliated sources (Al Jazeera, France 24, DW, etc.)
- Add Africa, Latin America, Asia-Pacific, Energy & Resources panels
- Add Spaceports layer with launch sites (Cape Canaveral, Baikonur, etc.)
- Add Critical Minerals layer (lithium, rare earths deposits)
- Add new hotspots: Sahel Region, Haiti, Horn of Africa
- Add entity registry entries for defense, semiconductors, minerals
- Add CSS styles for spaceport and mineral markers
- Add mobile touch targets and label hiding for new markers
- Bump version to 1.3.9
- Use setCenter() with lat/lon for regional views instead of fixed pan values
(positioning now relative to container size)
- Remove hotspot labels from map for cleaner view
- Disable protests layer by default, keep bases enabled
- Hide DEFCON indicator and FOCUS region selector on mobile
- Fix URL lat/lon not overriding view presets
- Entity knowledge base with 45+ entities (companies, commodities, crypto, countries, people)
- Entity index for O(1) lookups by alias, keyword, sector, type
- News-to-entity matching for intelligent market-news correlation
- New signal type: explained_market_move (when news correlates with price action)
- Improved silent_divergence (only fires after exhaustive entity search)
- Per-signal-type TTL deduplication (6hr for market signals vs 30min default)
- README documentation for all new features and algorithms
Recalibrated all regional view pan values for the projection
centered at (0°, 8°N) with 72°N to 56°S latitude range:
- Oceania: now shows Australia/Pacific correctly
- Asia: now centered on China/Japan/India
- Europe: zoomed out (2.6) to show full continent including Murmansk
- MENA: centered on Middle East/North Africa
- America: shows full Americas
- Lat Am: focused on South/Central America
- Africa: centered on African continent
- Crop latitude range to 60°N-55°S (removes empty Arctic/Antarctic)
- Map now uses full width with cropped height
- Enable page scrolling (overflow-y: auto on main-content)
- Reduce default map height to 50vh for better panel visibility
Scale calculation now considers both width (360° longitude) and height
(180° latitude), using the minimum to prevent clipping. Previously only
width was considered, causing northern regions (Greenland, Alaska,
Scandinavia) and Antarctica to be truncated.
- Add 'oil' and 'spending' as separate DataSourceIds in data-freshness
- Update oil-analytics.ts to report to 'oil' source
- Update usa-spending.ts to report to 'spending' source
- Add EIA and USASpending to StatusPanel API list
- Update README: version 1.3.8, new API dependencies, project structure
- Document EIA_API_KEY in optional API keys section
- Shorten description to 153 chars (was 203)
- Add canonical URL
- Add search discovery metas (subject, classification, coverage)
- Expand keywords with all tracked features
- Add og:image dimensions and locale
- Fix Twitter tags (name vs property) and add creator/site
- Add JSON-LD WebApplication structured data with featureList
- Reference new og-image.png (1200x630) for social sharing
- Add USASpending.gov API integration for government contracts/awards
- Fetches recent federal spending (no API key required)
- Shows recipient, amount, agency, description
- Add EIA Oil Analytics API integration
- WTI and Brent crude prices
- US production and inventory
- Requires EIA_API_KEY (free from eia.gov/opendata)
- Enhanced EconomicPanel with tabs:
- Indicators (FRED)
- Oil (EIA)
- Gov (USASpending)
- Added api/eia.js Vercel proxy for EIA API
### Motivation
- Clustering was performed on the main UI thread via `clusterNews` in
`NewsPanel.renderNews`, which can stall rendering for large inputs.
- The core clustering used a nested pairwise loop (O(n²) Jaccard
comparisons) that becomes expensive for larger news lists.
### Description
- Offload UI clustering to the existing analysis worker by calling
`analysisWorker.clusterNews(items)` from `NewsPanel` and render results
only after the worker returns, protecting against stale results with a
`renderRequestId` guard and error handling
(`src/components/NewsPanel.ts`).
- Reduce pairwise comparisons in the clustering core by adding a token
inverted-index and candidate set pass so items only compare against
others that share tokens, preserving Jaccard-based behaviour while
drastically cutting the number of comparisons
(`src/services/analysis-core.ts`).
- Keep tokenization and Jaccard similarity logic intact while
introducing `tokenList` and `invertedIndex` to build candidate buckets
and iterate sorted candidate indices for clustering.
### Testing
- No automated tests were run for this change.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_b_6968b4ebae98832e89e61bbb94e51c95)
### Motivation
- Reduce CPU overhead from repeatedly sorting the full RSS item list
during batch processing in `fetchCategoryFeeds`.
- Reduce expensive repeated UI updates from calling `panel.renderNews`
on every batch in `loadNewsCategory`.
### Description
- Replace full-list sorts with a fixed-size incremental top-N buffer in
`src/services/rss.ts` by adding a `topItems` array, `insertTopItem`
helper, and returning a single sorted top-20 list via
`ensureSortedDescending`.
- Track total RSS items seen with `totalItems` and record freshness
using `dataFreshness.recordUpdate('rss', totalItems)` instead of the
sliced list length.
- Throttle per-batch panel updates in `src/App.ts` by adding
`scheduleRender`, `flushPendingRender`, a `pendingItems` slot, a
`renderTimeout`, and a `renderIntervalMs` (250ms) so `onBatch` calls
only trigger UI updates at most once per interval.
- Ensure any pending throttled render is cleared before the final
`panel.renderNews(items)` to avoid duplicate renders.
### Testing
- No automated tests were run on this change.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_b_6968b63e5980832ea5b0dd1b98b33247)
### Motivation
- Virtual scrolling was constructing full HTML for every cluster up
front which negates CPU/memory savings for large lists.
- Defer string construction so only visible chunks produce HTML while
still keeping per-cluster metadata for highlighting and "new" state.
### Description
- Removed the `html` field from `PreparedCluster` and now only store
metadata (`cluster`, `isNew`, `shouldHighlight`, `showNewTag`).
- Updated the `WindowedList` render callback in `initWindowedList` to
call `renderClusterHtml(...)` on-demand instead of using prebuilt HTML.
- Kept the eager HTML generation path for non-virtual (small) lists by
invoking `renderClusterHtml(...)` when `useVirtualScroll` is disabled or
the item count is below `VIRTUAL_SCROLL_THRESHOLD`.
- Preserved the existing `bindRelatedAssetEvents` behavior after
rendering chunks or direct HTML.
### Testing
- No automated tests were run.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_b_6968b5cfb59c832e89e92feb7b296bb9)
### Motivation
- The existing `scheduleRefresh` loop ran at fixed intervals regardless
of tab visibility, causing unnecessary CPU and network usage when the
app was backgrounded.
- Slowing or pausing refreshes for hidden documents and adding jitter
reduces contention and improves background efficiency.
### Description
- Added `computeDelay` helper and constants (`HIDDEN_REFRESH_MULTIPLIER
= 4`, `JITTER_FRACTION = 0.1`, `MIN_REFRESH_MS = 1000`) to compute
jittered delays.
- Use `document.visibilityState === 'hidden'` to slow background
refreshes by the hidden multiplier and avoid running refreshes while
hidden.
- Apply jittered delays to all scheduled `setTimeout` calls including
the initial scheduling, branch where `condition` is false, when an
in-flight job exists, and after a refresh completes.
- Kept existing behavior of skipping refreshes when a provided
`condition` is false or a refresh is already in-flight, but now with
adjusted delays and jitter to stagger fetches.
### Testing
- No automated tests were run for this change.
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_b_6968b4689698832eb83312117af1b579)
- Add Wingbits as tracked data source in data-freshness.ts
- Report Wingbits enrichment status to data freshness tracker
- Add Aircraft Enrichment section to README with classification algorithm
- Add Wingbits to API Dependencies and Optional API Keys
- Add wingbits.ts and wingbits.js to project structure
- Add acknowledgment for Yanal at Wingbits for API access
- Update version badge to 1.3.4
- Add api/wingbits.js proxy (keeps API key server-side)
- Add src/services/wingbits.ts client with caching
- Integrate enrichment into military-flights.ts
- Add enriched field to MilitaryFlight type
- Batch lookup aircraft details for efficiency
- Analyze owner/operator to confirm military status
- Cache aircraft details (24h server, 1h client)
- Set transform-origin to top-left (0 0) on map-wrapper
- Update applyTransform to compensate for new origin with proper centering
- Recalibrate all view presets (Global, EU, MENA, Asia, etc.)
- Allow more generous panning at higher zoom levels
The map was being clipped at the top because CSS scale() from center
pushed content off-screen. Using top-left origin with calculated
offset maintains proper centering while preventing clipping.
- Add live demo link and version badge
- Document OpenSky authentication requirements
- Add OpenSky credentials to API Dependencies table
- Update Roadmap with recent completed features
The alerts array was defined but never populated. Now:
- Convergence alerts are created from geo-convergence data
- CII change alerts are added when country scores change 10+
- Alerts are deduplicated and pruned after 24 hours
- Metrics (Convergence, High Alerts, Infra Events) now work
- Shipping: Show ok if has data (regardless of WebSocket state)
- Military: Show warning (yellow) if 0 items instead of ok
- Base status on data presence, not just connection state
Export getLocationName() and use it to show human-readable
locations like "East Asia" or "Strait of Hormuz" instead of
raw coordinates like "35°, 136°".
- Disabled feeds show gray dot and dimmed row
- Disabled items don't count towards status icon (error/warning)
- Default status is now 'disabled' until data is fetched
- Added setFeedDisabled/setApiDisabled methods
Record GDELT protest count to 'acled' freshness source since
GDELT serves as fallback when ACLED is not configured. This
ensures Strategic Risk panel shows protests as available.
- Changed "Limited Assessment (X/10 feeds)" to "Limited Data - [source names]"
- Changed "Full coverage (X feeds active)" to "All data sources active"
- Users now see which data sources are working instead of cryptic counts
- The X/10 was confusing because layer toggles don't map to backend APIs
- Add 'usa' keyword to match US military flight data (was being ignored)
- Fix beirut mapping: IL → IR (Beirut is in Lebanon, Iran-backed groups)
- Fix brussels mapping: DE → FR (Brussels is in Belgium, not Germany)
- Remove cairo→IL mapping (Cairo is in Egypt, not Israel)
- Add Iran to Gaza conflict zone (gaza: IL,IR instead of just IL)
These bugs were causing incorrect CII scores:
- Israel was getting undeserved hotspot boosts from Beirut/Cairo activity
- US military flights weren't contributing to Security scores
- Iran wasn't getting credit for Gaza-related activity
- Military flights/vessels now credited to LOCATION country (foreign military = threat)
- Internet outages boost unrest score (total=30pts, major=15pts, partial=5pts)
- Hotspot proximity tracking for INTEL_HOTSPOTS, CONFLICT_ZONES, STRATEGIC_WATERWAYS
- Activity near strategic locations adds up to 30 point boost to CII
- Iran/Taiwan/etc now properly score higher when foreign military operates over them
- Add html { height/width: 100%; overflow: hidden }
- Add min-height/min-width: 100vh/100vw to body and #app
- Use position: absolute with inset 0 on #app for bulletproof coverage
- Add flex: 1 1 0 and min-height: 0 to main-content for proper flex behavior
- Ensure width: 100% on main-content
This should permanently fix black borders on right/bottom edges.
Bug: avgCIIDeviation divided by ALL 20 countries, diluting scores
- Israel at 31: deviation (31-30)/20 = 0.05 → ciiScore ≈ 0
Fix: Use weighted top-5 countries instead of average
- Top country: 40%, 2nd: 25%, 3rd: 20%, 4th: 10%, 5th: 5%
- Israel 31 + China 27 + others → weighted ~28 → composite ~14
Also increased CII weight from 35% to 50% (main risk driver)
- Update getGraphStats() to include port and chokepoint counts
- Add port (⚓) and chokepoint (🌊) stats to CascadePanel display
- Infrastructure Cascade now properly shows all node types
Ports and chokepoints were added as nodes but had NO edges connecting
them to countries, so cascade analysis always showed 0 impacts.
Added:
- buildPortCountryEdges(): Connects ports to their host country and
strategic trade partners (Suez ports → EU, Hormuz ports → Asia oil)
- buildChokepointEdges(): Connects chokepoints to nearby ports and
dependent countries based on trade flows
- getAffectedCountries(): Strategic port impact mapping
- getChokepointDependentCountries(): Chokepoint dependency data
Now selecting Port of Aden correctly shows impacts on:
- Yemen (host country)
- UK, Germany, Italy (Europe-Asia shipping route)
- Saudi Arabia (regional trade)
The CII was showing misleading results (Iran ≈ USA) because it only
counted detected events without accounting for:
- Reporting bias (US events over-reported, authoritarian states under-reported)
- Baseline geopolitical risk (some countries are inherently unstable)
Changes:
- Add BASELINE_RISK scores (0-50) for each country reflecting geopolitical reality
- Add EVENT_MULTIPLIER to weight events by significance:
- Lower for open democracies (protests normal, well-covered)
- Higher for authoritarian states (events suppressed, more significant)
- Blend formula: 40% baseline + 60% detected events
This makes:
- Active war zones (Ukraine, Syria, Yemen) always rank high
- Authoritarian states (Iran, NK, China) reflect actual risk
- Stable democracies (US, UK, DE) not inflated by normal protest activity
- Fix earthquakes API URL to use /api/earthquakes proxy (was 404)
- Add in-memory caching to CoinGecko proxy (2 min TTL)
- Return cached data on 429 rate limit instead of error
- Increase Cache-Control to 120s with stale-while-revalidate
- Move dataFreshness.recordUpdate() from protests.ts to App.ts
- Record freshness AFTER ingestProtestsForCII to avoid race condition
- Strategic Risk Panel subscription now triggers refresh() instead of render()
- Add debounce to freshness subscription to batch rapid updates
The issue was that the panel would refresh before CII data was ingested,
resulting in a score of 0 even when protests were enabled.
- Add DataFreshnessTracker service to monitor data source health
- Shows "Insufficient Data" when no feeds active
- Shows "Limited Assessment" with partial data coverage
- Shows "Full Coverage" when all core feeds reporting
- Wire up protests.ts and rss.ts to report data freshness
- Add CSS for data availability states, source rows, action buttons
- Panel now shows which feeds need enabling
- Skip if socket is CONNECTING (not just OPEN)
- Use local socket reference to prevent event handler confusion
- Add race condition guards before send/message operations
- Set upstreamSocket=null on close for clean reconnection
- New geo-convergence service detecting 3+ event types in same 1° grid
- Tracks protests, military flights, naval vessels, earthquakes
- 24h rolling window with automatic pruning
- Integrated into signal panel with 🌐 icon
- Fix map SVG not extending to full canvas width
Add ResizeObserver to detect when container gets proper dimensions
and re-render the map accordingly. Fixes black space appearing in
bottom-right corner on app startup.
- Add news.un.org and www.cisa.gov to Railway RSS allowlist
- Reuse existing VITE_WS_RELAY_URL for RSS proxy (no new env var needed)
- Falls back to Vercel proxy if Railway not configured
- Add NASA EONET API integration for storms, wildfires, volcanoes, floods
- Combine USGS earthquakes + EONET events under single NATURAL toggle
- Filter EONET earthquakes to avoid duplicates (USGS is more authoritative)
- Load both data sources in parallel for faster initial render
- Update help text, CSS layer attributes, and zoom thresholds
- Version bump to 1.3.0
- Rename US view to AMERICA with proper North America coverage
- Remove AlbersUSA projection, use equirectangular for all views
- Fix EU view pan settings (was showing Africa)
- Add RSS feeds: Stratfor, CrisisWatch, UN News, CISA
- Remove unused US-specific map data loading
- Replace view buttons with dropdown selector
- Add 8 region presets with custom zoom/pan settings
- Update URL state to support new views
- All regional views use world projection (except US)
The pan.y=60 value intended to "pan north" actually caused a blank gap
at the top because SVG viewBox clips at its boundaries. CSS translate
shifts the rendered content but doesn't reveal unrendered areas.
Set pan.y=0 for global view to properly center the map.
Documents that this is a proof of concept with:
- Data completeness gaps without paid accounts (ACLED, OpenSky)
- AIS coverage bias toward European/Atlantic waters
- Some data sources blocking cloud providers
- Add /rss endpoint to ais-relay.cjs for blocked domains
- Add railwayRss() helper that routes to Railway relay
- Route Telegraph and CNN through Railway proxy
- Keeps security whitelist on Railway side
- Revert projection to center at equator (stable)
- Set default pan.y=60 to shift view north, showing Europe/UK
- Ignore URL lat/lon when zoom <= 2 to prevent stale state issues
- Update setView() and reset() with consistent northward pan
- Add Live News Streams section documenting 7 YouTube channels
- Add Politico, The Telegraph, Al Arabiya to news sources list
- Add LiveNewsPanel.ts to project structure
- Remove Congress Trades from StatusPanel feed list
- Update Data Export to mention CSV/JSON
- Add blinking "Live" dot that toggles video playback on click
- Reorder channels: Bloomberg, SkyNews, Euronews, DW, France24, AlArabiya, AlJazeera
- Show Export CSV above Export JSON in menu
- Double-width panel with embedded YouTube player
- Channel switcher: Al Jazeera, Sky News, Bloomberg, France 24, DW, Euronews, Al Arabiya
- Auto-plays muted, red indicator on active channel
- Layoffs: Added layoffs.fyi, 3-day filter on Google News
- Think Tanks: Added direct Brookings, CSIS, CFR feeds
- AI/ML: Better Google News query with 2-day filter, The Verge AI
- Intel: Direct Defense News RSS, Janes, The War Zone direct feed
- Updated source tiers and types for new feeds
- Add /api/finnhub proxy endpoint with batch symbol support
- Refactor markets.ts to use Finnhub for stocks (60 req/min vs Yahoo's unpredictable limits)
- Keep Yahoo Finance as fallback for indices (^GSPC, ^DJI) and commodities (GC=F, CL=F)
- Remove complex batching/cooldown logic (no longer needed)
- Rename 'Alpha Vantage' to 'Finnhub' in StatusPanel
Benefits:
- Predictable rate limits (60/min free tier)
- Single batch request for all stocks
- More reliable than Yahoo Finance scraping
- Richer data available (high/low/volume) for future use
Requires FINNHUB_API_KEY environment variable (free at finnhub.io)
- Increase z-index of military-flight-marker from 25 to 55
- Increase z-index of military-vessel-marker from 24 to 54
- Increase z-index of carrier vessels from 26 to 56
- All now above conflict-click-area (z-index: 50)
- Fix escapeHtml order (escape after toUpperCase, not before)
- Escape severityLabel in conflict/hotspot popups
- Escape coordinates in hotspot popup
- Escape itemCount, latency, and formatTime output in StatusPanel
- Try parsing as absolute URL first (existing behavior)
- Fall back to resolving against window.location.origin
- Return original relative URL if it resolves to http/https
- Still blocks dangerous protocols (javascript:, data:, etc.)
- loadAllData now uses runGuarded() wrapper for each task
- loadDataForLayer also checks inFlight before starting
- Both use same inFlight Set as scheduleRefresh
- Prevents duplicate API calls and inconsistent panel state
Use strict equality (=== false) to distinguish between:
- null: unknown/transient failure → show warning status
- false: explicitly not configured → show 'not configured' message
- true: configured and working → show ok status
- Move isMobileDevice() to utils/index.ts for reuse
- Update App.ts and MapPopup.ts to use shared utility
- Reset transform style on desktop to prevent stale mobile transforms
- Apply escapeHtml to all external data rendered via innerHTML
- MarketPanel: stock.name, stock.display, sector.name, commodity names, coin data
- PredictionPanel: prediction titles
- EconomicPanel: FRED series id, name, unit, date
- Move isMobileDevice helper to utils for reuse
- Add mobile-specific default layers (hotspots, conflicts, outages only)
to reduce visual clutter on small screens
- Default to MENA view on mobile devices for better regional focus
- Add 44x44px touch targets using ::before pseudo-elements for all
map markers (hotspots, bases, earthquakes, etc.)
- Update MapPopup to center horizontally on mobile for better visibility
- Add comprehensive mobile CSS with responsive controls and buttons
- Update mobile warning modal messaging to reflect new mobile experience
- Add extra-small screen (480px) specific layout adjustments
Fixes touch target accuracy issues where clicking one marker would
open a different marker's popup.
Covers:
- Fork and clone workflow
- Code style conventions (TypeScript, architecture, security, performance)
- PR submission process with examples
- Good PR practices table
- Types of contributions welcome
- Review process
- Development tips for adding layers and proxies
- Guard connect() for both OPEN and CONNECTING states
- socket.onerror now closes socket and schedules reconnect
- initAisStream only creates cleanup interval if not exists
- disconnectAisStream clears cleanup interval
- cleanupOldData now prunes vesselHistory entries
Problem: Promise.all would reject on any task failure, skipping
updateSearchIndex and leaving stale search results.
Fix:
- Switch to Promise.allSettled to wait for all tasks
- Log individual failures without blocking other tasks
- Always call updateSearchIndex regardless of failures
- Add task names for better error logging
The readyPromise could hang indefinitely if worker failed to load.
Changes:
- Add 10-second ready timeout that rejects if worker doesn't initialize
- Reject readyPromise on worker.onerror before ready state
- Add try/catch around worker creation
- Extract cleanup() helper for consistent state reset
- Worker now properly fails fast instead of hanging forever
Security fix: ACLED API token was embedded in client bundle
Changes:
- Create api/acled.js serverless proxy with token kept server-side
- Add 10-minute cache to reduce API calls
- Add rate limiting (10 req/min per IP)
- Return only needed fields to client
- Update protests.ts to use proxy instead of direct API calls
- Rename env var: VITE_ACLED_ACCESS_TOKEN -> ACLED_ACCESS_TOKEN
The token is now only accessible server-side (no VITE_ prefix).
- Increase aircraft crosshairs from 12px to 20px with stronger glow
- Replace vessel emoji icons with tactical CSS diamond shapes
- Submarines use circle outline, carriers use filled diamond
- Consistent tactical aesthetic across all military markers
- Add scroll event interception to prevent map zoom while scrolling popup
- Add Time Filter section explaining 1H/6H/24H/7D/30D/ALL options
- Clarify data filtering (cables = 20 backbone routes, datacenters ≥10k GPUs)
- Add note about which layers are affected by time filter
- Added (?) button next to layer toggles that opens a popup
- Popup explains each layer grouped by category: Geopolitical, Military & Strategic, Infrastructure, Transport, Natural & Economic, Labels
- Closes on X button or clicking outside
- Use transparent tinted backgrounds instead of solid colors
- Ensure title text is always white
- Subtitle text white for readability
- Attribution text brighter (50% vs 40%)
- China/Russia headers use lighter tints to avoid dark-on-dark
- Replace circles/symbols with CSS crosshairs (+)
- Crosshairs rotate with aircraft heading
- Clusters use targeting bracket corners [ ]
- Much more distinct from shipping circles and bases
- Different glow colors for bombers (pink) and recon (cyan)
- Tactical/targeting aesthetic
- Replace catch-all route with explicit files
- api/pizzint/dashboard-data.js for /api/pizzint/dashboard-data
- api/pizzint/gdelt/batch.js for /api/pizzint/gdelt/batch
- Improve rss-proxy error handling with more details
- Fix cluster popup bug: add missing render methods for militaryFlight,
militaryVessel, militaryFlightCluster, and militaryVesselCluster types
- Add distinct military styling: black/green tactical theme with colored
borders by operator (US olive, UK forest green, China/Russia red)
- Expand military coverage: add 25+ country hex ranges (Israel, Turkey,
Saudi Arabia, UAE, Japan, South Korea, etc.) and callsign patterns
- Shorten cluster labels to military commands (CENTCOM, EUCOM, INDO-PACIFIC)
- Move DEFCON/PizzINT indicator from overlay to header
- Fix promise batching in fetchFromOpenSky to avoid rate limiting
- Added 20+ new ICAO hex ranges: Israel, Turkey, Saudi Arabia, UAE,
Qatar, Kuwait, Japan, South Korea, Australia, Canada, India,
Pakistan, Egypt, Poland, Greece, Sweden, Norway, Singapore, etc.
- Added 25+ new callsign patterns for non-US/NATO air forces
- Expanded country filter from 5 to 26 countries
- Should significantly improve Middle East coverage for Iran tensions
- Changed from fixed positioning (bottom-left) to inline in header-left
- Adjusted sizing to fit header (smaller toggle, dropdown opens downward)
- Panel now drops down from header instead of popping up from bottom
Extracted fetchHotspotRegion() so requests only start when
the batch executes, not when defined. Previously all promises
started immediately due to map(async ...) behavior.
- Add /opensky HTTP endpoint to ais-relay.cjs
- Update military-flights.ts to use Railway for OpenSky
- Converts VITE_WS_RELAY_URL to HTTP URL for same server
- Add explicit timeouts to OpenSky (12s) and RSS (8s) proxies
- Consolidate 10 military hotspots into 4 larger regions
- Increase military refresh interval from 2 to 5 minutes
- Switch OpenAI feed to Google News (OpenAI blocks proxies)
Features:
- Military aircraft detection via OpenSky Network API
- Callsign pattern matching for military operators (NATO, Russia, China)
- Known military ICAO hex code identification
- Military vessel detection via AIS data (naval ship types)
- Clustering for dense military activity areas
- Flight trail history tracking
- Integration with existing layer toggle system
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add comprehensive Data Attribution section to README with proper
citations for all data sources (OpenSky, ACLED, GDELT, CoinGecko,
Yahoo Finance, USGS, FRED, Cloudflare Radar, etc.)
- Add Acknowledgments section crediting Reggie James (@HipCityReg)
for the original dashboard concept inspiration
- Fix OpenSky OAuth2 token endpoint URL (was using wrong endpoint)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Uses OPENSKY_CLIENT_ID and OPENSKY_CLIENT_SECRET env vars
- Token caching with automatic refresh
- Falls back to anonymous if not configured
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add /api/yahoo-finance.js for stock quotes
- Add /api/coingecko.js for crypto prices
- Add /api/polymarket.js for prediction markets
- Add /api/rss-proxy.js for RSS feeds (with domain allowlist)
- Add /api/earthquakes.js for USGS data
- Update feeds.ts to use direct URLs with RSS proxy
- Simplify proxy.ts (no external CORS proxy needed)
- Update earthquakes.ts and polymarket.ts to use new endpoints
Eliminates dependency on unreliable third-party CORS proxy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add types for military flights, vessels, and clusters (MilitaryFlight,
MilitaryVessel, MilitaryFlightCluster, MilitaryVesselCluster)
- Create military-flights.ts service with OpenSky Network API integration
for tracking military aircraft (fighters, bombers, tankers, AWACS, etc.)
- Create military-vessels.ts service leveraging AIS stream to detect
military/government vessels (carriers, destroyers, submarines, etc.)
- Add comprehensive military callsign patterns and aircraft type database
in config/military.ts (US, NATO, Russia, China callsigns)
- Add known naval vessel database with carrier strike groups
- Implement military hotspot detection for strategic regions (Taiwan Strait,
South China Sea, Persian Gulf, Black Sea, etc.)
- Add map rendering for military flights and vessels with operator-specific
colors, aircraft type icons, and flight track trails
- Add CSS styles for military markers with pulsing animations for
interesting activity
- Integrate into App.ts with 2-minute refresh for flights, 5-minute for vessels
- Add OpenSky and ADS-B Exchange API proxies in vite.config.ts
- Updated api/fred-data.js serverless function to use FRED API with
server-side API key (via FRED_API_KEY env var)
- Removed hardcoded API key from frontend code
- Frontend now calls /api/fred-data without exposing the API key
- Updated vite proxy config for local dev
IMPORTANT: Add FRED_API_KEY to Vercel environment variables!
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Async data layers (ships, earthquakes, weather, outages, protests,
flights) now show yellow/loading state when toggled ON
- Button only turns green/active when data is actually rendered
- Added setLayerReady() method to Map component
- Updated all async data loading functions to call setLayerReady
Fixes issue where Ships button turned green immediately even when
no ship data was available to display.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolves merge conflicts with PR #11 changes:
- Combined circuit breaker error states with activity tracking
- Merged FRED API updates with worker changes
- Combined virtual scrolling CSS with error state styles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix economic panel mounting selector (.main → .main-content)
- Add circuit breaker error state to panels (red header on failure)
- Switch FRED to official API with API key (fixes bot protection 404)
- Fix GDELT proxy order (specific /api/gdelt-geo before /api/gdelt)
- Fix click handler violation with requestAnimationFrame
- Add panel error state CSS styling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Removed shape-critical layers from WebGL clustering:
- bases: nation-specific colors (NATO blue, Russia red, etc.)
- nuclear: status-based styling (active/contested/inactive)
- economic: emoji icons (📈, 🏛, 💰)
- datacenters: 🖥️ icons with status
- hotspots: level colors and BREAKING badges
These layers now always render as HTML markers to preserve
visual identity. Generic layers (earthquakes, weather, protests,
flights, outages, irradiators, ais) still use WebGL clustering.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements efficient rendering for large news lists by only rendering
visible chunks of items. This reduces DOM nodes from 200+ to ~24
(3 chunks × 8 items) for panels with many articles.
Implementation:
- Add VirtualList component with two strategies:
- VirtualList: Fixed-height items with DOM recycling
- WindowedList: Variable-height items with chunk-based rendering
- Update NewsPanel to use WindowedList for lists > 15 items
- Extract renderClusterHtml() method for per-item rendering
- Add CSS containment hints for scroll performance
Performance benefits:
- Reduced initial render time (fewer DOM nodes)
- Smoother scrolling (CSS contain: strict)
- Lower memory usage (chunks rendered on-demand)
- Event handlers bound only to visible chunks
Adds visual feedback when panels receive new data:
- Panel header badges showing count of new items (e.g., "3 new")
- Pulsing animation to draw attention to updated panels
- Green "NEW" tag on recently added news items (fades after 2 min)
- Highlight glow effect on new items (first 30 seconds)
- Auto-clear badges when user scrolls or clicks panel
Implementation:
- Add ActivityTracker service to track seen items per panel
- Extend Panel base class with badge element and setNewBadge()
- Update NewsPanel to integrate with activity tracking
- Add CSS animations for badges, highlights, and panel glow
Moves O(n²) Jaccard clustering and correlation signal detection off the
main thread to prevent UI jank during data refresh cycles. The worker
maintains its own state for correlation analysis (previousSnapshot,
recentSignalKeys) and returns serialized results back to the main thread.
Changes:
- Add src/workers/analysis.worker.ts with self-contained clustering and
correlation logic
- Add src/services/analysis-worker.ts as typed async interface to worker
- Update App.ts to use worker for clusterNews and analyzeCorrelations
- Worker is lazily initialized on first use
Show informational popup on mobile devices notifying users that the
dashboard is optimized for desktop viewing. Users can dismiss the
popup and optionally choose not to see it again (persisted to
localStorage). Detection uses both screen width and pointer type.
- Primary meta tags: title, description, keywords, author, theme-color
- Open Graph tags for Facebook/LinkedIn sharing
- Twitter Card tags for Twitter sharing
- Uses worldmonitor-icon-1024.png as og:image
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Intel feed was loading and displaying correctly but wasn't calling
statusPanel.updateFeed() to update the System Health popup status.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add favicon links to index.html
- Move favico assets to public/ for Vite static serving
- Add root favicon.ico for default /favicon.ico requests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
SHIPS toggle now blinks while waiting for WebSocket connection
and vessel data. Polls every second for up to 30s before timeout.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Only show significant AI supercomputer clusters on map
to reduce visual clutter while keeping strategic sites visible
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added keyword filtering for conflicts, leaders, politics, economics
- Exclude sports (NBA, FIFA, NFL) and entertainment
- Fetch 100 markets, filter to 15 relevant ones
- Keywords: war, military, election, sanctions, etc.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- AIS (Ships): Hidden if VITE_WS_RELAY_URL not set in production
- Outages: Hidden if CLOUDFLARE_API_TOKEN not configured
- API returns {configured: false} to signal missing config
- Layer toggle buttons hidden via hideLayerToggle() method
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Disconnect AIS WebSocket when ships layer is disabled
- Only init AIS stream if layer is enabled on startup
- Cloudflare outages API returns empty result instead of 500 when token missing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- cloudflare-outages.js - Cloudflare Radar API
- faa-status.js - FAA NASSTATUS
- fred-data.js - FRED economic data
- gdelt-geo.js - GDELT protest data
- nga-warnings.js - NGA maritime warnings
Updated frontend services to use new endpoints.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- FAA, FRED, GDELT, NGA-MSI proxies
- Fix flights.ts to always use proxy path
- All external APIs now go through serverless functions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Per-feed cooldown after 2 consecutive failures (5 min)
- Cache successful results for 10 minutes
- Return cached content during cooldown or failures
- Log when feed enters cooldown
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Show chokepoints with 5+ vessels (was 40+)
- Show any dark ship activity (was 5+)
- More useful for situation awareness
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Node.js ws library receives messages as Buffer by default.
Browser WebSocket expects string data.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Relay now serves static files from dist/
- Frontend connects to WebSocket on same origin
- Railway build: npm install && npm run build && cd scripts && npm install
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Stop all requests for 5 minutes after 429 error
- Return cached results during cooldown period
- Abort batch processing immediately on rate limit
- Log remaining cooldown time
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Refresh intervals now check layer settings before making API calls.
Disabled layers won't trigger any network requests.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Switch FAA endpoint from defunct soa.smext.faa.gov to nasstatus.faa.gov
- Parse FAA NASSTATUS XML response (single call instead of per-airport)
- Fix Yahoo Finance 429 rate limiting with smaller batches and backoff
- Add conditional API loading based on layer toggle settings
- Load data only when corresponding layer is enabled
- Trigger data load when layer is toggled on
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Railway requires an HTTP endpoint for health checks. Added HTTP server
that responds to / and /health with status JSON, with WebSocket server
attached to the same HTTP server.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement hybrid flight delay monitoring with FAA ASWS API for US airports
and computed delays for 75 global airports across Americas, Europe, APAC,
MENA, and Africa regions.
Features:
- Real-time FAA data for US airports (official, free API)
- 75 monitored airports globally with coordinates
- Severity levels: normal, minor, moderate, major, severe
- Delay types: ground stop, ground delay program, departure/arrival delays
- Color-coded markers with pulsing animation for major/severe
- Popup with airport details, delay reason, and source
- 10-minute refresh interval
- URL state persistence for flights layer
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add package.json for standalone deployment
- Use PORT env variable for Railway compatibility
- Support AISSTREAM_API_KEY env variable
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
aisstream.io blocks browser WebSocket connections (Origin header).
In dev: use local relay server (node scripts/ais-relay.cjs)
In prod: disabled until external WebSocket relay is deployed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename AIS layer to "Shipping" throughout UI for clarity
- Add local WebSocket relay server to proxy aisstream.io data
- Fix cable-activity.ts NGA warnings array handling bug
- Add ws package for relay server
- Improve Shipping service logging and error handling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Map reset() now sets view to 'global' in addition to zoom/pan
- View buttons sync via onStateChanged callback
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Revert viewBox to simple container-matched coordinates (0 0 width height)
instead of using yOffset which caused SVG content to shift while HTML
overlay markers stayed in place, resulting in facilities appearing at
wrong locations (e.g., Dimona appearing far from Israel).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ~120 nuclear power plants and facilities worldwide (was only 11)
- Fix map projection to fill width and allow vertical panning
- Set default zoom to 1.5 for better initial view
- Increase GDELT protest threshold to 200+ reports
- Fix localStorage merge for new layer defaults
- Fix nuclear facility coordinates (South Texas, Laguna Verde)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Features:
- ACLED API integration for verified protest/riot data with precise coordinates
- GDELT GEO 2.0 API for real-time global event coverage (no auth required)
- Multi-source deduplication and validation scoring
- Severity classification (high/medium/low) based on fatalities and event type
- Protest markers with type-specific icons (riot, strike, demonstration)
- Popup with source info, actors, tags, and nearby intel hotspots
- 15-minute refresh interval matching GDELT update frequency
Data sources:
- ACLED: 200+ countries, daily updates, geocoded to city level
- GDELT: 15-min updates, sentiment analysis, global coverage
Requires VITE_ACLED_API_KEY and VITE_ACLED_EMAIL for full coverage
Falls back to GDELT-only mode if ACLED not configured
- Integrated official NGA Maritime Safety Information API
- Real navigation warnings for cable operations worldwide
- Coordinate parsing from warning text (DMS and decimal formats)
- Cableship name extraction (CS, M/V, CABLESHIP patterns)
- Automatic matching to nearest known undersea cable
- Severity detection (fault vs operational warnings)
- Ship status detection (on-station vs enroute)
Data source: https://msi.nga.mil/api/publications/broadcast-warn
- Rewrote AIS service to use aisstream.io free WebSocket API
- Real-time vessel position tracking with in-memory aggregation
- Density zone calculation from 2-degree grid cells
- Chokepoint congestion detection (Hormuz, Suez, Malacca, etc.)
- Dark ship detection via AIS gap analysis
- Graceful handling when no API key is configured
- Requires VITE_AISSTREAM_API_KEY env var for live data
Get free API key at https://aisstream.io
- Replace undefined --panel-border CSS variable with --border
- Use full border declaration for .item.clustered.alert to ensure
red borders display correctly regardless of CSS cascade order
- Add SOURCE_TYPES categorization (wire/gov/intel/mainstream/market/tech)
- Convergence: detect 3+ source types reporting same story in 30m window
- Triangulation: detect when wire + gov + intel align on same topic
- Extend SignalModal with new signal type labels and styling
- Extended search modal to cover pipelines, cables, datacenters, nuclear facilities, irradiators
- Added trigger methods to MapComponent for all searchable entity types
- Auto-enables relevant map layer when selecting search results
- Added ⌘K search button to header with styling
- Expanded pipeline data to 88 real operating pipelines globally
- Fixed type errors and simplified pipeline status handling
- Max zoom increased from 4x to 10x for better Middle East detail
- Switched from 110m to 50m resolution world map
- All zoom handlers updated (buttons, wheel, pinch)
- Moved conflict labels from SVG text to HTML overlays
- Labels now counter-scale with zoom like all other labels
- Much smaller, readable text that doesn't dominate the map
- Added 60+ country labels with centroids (focus on Middle East)
- Countries layer toggle in map controls
- Labels counter-scale with zoom like other overlays
- Subtle styling (60% opacity, text shadow) so they don't dominate
- Signal modal no longer auto-pops; signals accumulate in header badge
- Click badge to view signals; new pulse animation on new signals
- Added 'economic' to MapLayers - FRED panel now toggleable via layer buttons
- Layer changes persist to localStorage
- Monitor panel already forced to last position in sidebar (verified existing logic)
- Changed FRED URL to use /api/fred proxy path
- Added FRED proxy to vite.config.ts and proxy.ts
- Refactored sequential fetching to use Promise.all for 7x speed improvement
- Fixed TypeScript types for async map
- New fred.ts service fetching data from FRED CSV endpoints
- EconomicPanel component displaying key indicators (Fed Assets, Fed Rate, 10Y-2Y Spread, etc.)
- Real-time change tracking with positive/negative indicators
- Auto-refresh every 30 minutes
- Integrated with status panel health monitoring
- Added 10 official government RSS sources to gov category
- Added source tier rankings for government sources (Tier 1-2)
- Updated proxy mappings for new government domains
- Add historical playback with IndexedDB snapshots (15min intervals, 7d retention)
- Enhance signal alerts with badge counter, status dot pulse, header flash
- Create status/health dashboard showing feed health and API status
- Add data export functionality (JSON/CSV) with download button
- Switch from /events to /markets endpoint for better data
- Order by volume descending to show most active markets
- Filter for interesting markets (>10% discrepancy from 50% OR high volume)
- Correct field mapping: question, outcomePrices array
- Show Yes/No probability bars with proper percentages
- Sudan zone now focused on Khartoum region instead of entire country
- Myanmar zone focused on central conflict area
- Reduces visual noise on the map
- Remove feeds with no working RSS: Reuters, White House, Treasury,
Anthropic, Google AI, DeepMind, OpenAI, CSIS, CFR, Brookings
- Replace with working alternatives:
- AP News via RSSHub
- VentureBeat for AI news
- Foreign Policy for think tanks
- Google News RSS searches for AI, gov, think tanks, defense
- Clean up unused proxy mappings
- Add proxy utility that detects dev vs production environment
- In development: use Vite proxy (local /api, /rss paths)
- In production: use corsproxy.io to bypass CORS
- Update all services to use fetchWithProxy
| WORLDMONITOR-1G | `Error: ML request unload-model timed out after 120000ms` | 30 | 27 | Wrapped `unloadModel()` in try/catch; timeout no longer leaks as unhandled rejection. Cleans up `loadedModels` set on failure. |
| WORLDMONITOR-1F | `Error: ML request unload-model timed out after 120000ms` | 9 | 9 | Same root cause as 1G (different release build hash). |
| WORLDMONITOR-1K | `TypeError: this.player.playVideo is not a function` | 1 | 1 | Added optional chaining (`playVideo?.()`, `pauseVideo?.()`) in `LiveNewsPanel.ts`. YT IFrame API player object may not have methods ready during initialization race. |
### NOISE — Filtered
| ID | Title | Events | Users | Filter |
|---|---|---|---|---|
| WORLDMONITOR-1J | `InternalError: too much recursion` | 1 | 1 | i18next internal `translate -> extractFromKey` cycle on Firefox 147. Added `/too much recursion/` to `ignoreErrors`. |
| WORLDMONITOR-1H | `TypeError: Cannot read properties of null (reading 'id')` | 1 | 1 | maplibre-gl internal render crash (`_drawLayers -> renderLayers`). Extended `beforeSend` regex to suppress null `id`/`type` when stack is in map chunk. |
## Files Modified
| File | Change |
|---|---|
| `src/services/ml-worker.ts` | `unloadModel()`: try/catch around `this.request()`, clean `loadedModels` on failure |
| `src/components/LiveNewsPanel.ts` | Optional chaining on `playVideo?.()` and `pauseVideo?.()` |
| `src/main.ts` | Added `/too much recursion/` to `ignoreErrors`; extended maplibre `beforeSend` filter for null `id`/`type` |
## Sentry Status
All 5 issues marked **resolved (in next release)** via API. They will auto-reopen if errors recur after deployment.
All notable changes to World Monitor are documented here.
## [2.4.43]
### Added
- **Finance-terminal alt-asset feeds are live** (`?variant=finance`). The "Art & Collectibles — Auctions" and "Luxury Real Estate" panels now render REAL data instead of "live feed pending":
-`GET /v1/world/auctions` — Christie's public auction results (realized **sale totals**: title, inferred category, price + currency, sale date, location, image, link). Sotheby's gates realized prices behind a login (its sale-results API returns `401 Not signed in`), so the honest public major house is the source.
-`GET /v1/world/luxury-realestate` — LuxuryEstate.com luxury listings (title, location, price + currency, type, image, link). JamesEdition sits behind a Cloudflare JS challenge that blocks datacenter egress, so it cannot be fetched from the pod.
- Both are scraped server-side (`internal/world/handlers_altassets.go`) at most **once an hour** and served from cache to every caller (respectful). On a source failure the last-good copy is served; with no cache yet, an honest empty `{items:[]}` (the panel shows "live feed pending") — **never fabricated data**. The payload attributes its source per feed.
## [2.4.19]
### Fixed
- **Moving/resizing a panel no longer shifts the others** (the "when I shift the 3D map it shifts all other components" report). The dashboard now DEFAULTS to the free layout: every panel owns its own {x,y,w,h}, so a drag or resize leaves every sibling exactly where it was — no more grid reflow. The switch is invisible: the current grid arrangement is frozen as the starting geometry (`grid-config.applyDefaultLayout`), and Grid stays one dropdown-click away for anyone who wants snap-to-grid back.
- **Panels resize granularly** — pixel-exact width AND height from any edge or corner, instead of jumping between coarse column/row spans.
- **Panels can be made much narrower/smaller** ("constrained on min width"): the free-mode floors drop to 96×40px (map 140px), and the grid widget-size slider now reaches a 120px column track (was 140).
### Changed
- A panel shown from the Panels menu / "+ Add widget" while in free mode seeds a tidy default slot below the placed panels (never a full-width block under its absolute siblings). Hidden panels no longer persist 0×0 geometry.
## [2.4.13]
### Fixed
- **Globe overlays now sit ON the sphere with correct occlusion** (the "map spazzes" report). On the native deck.gl GlobeView, data layers (shared with the 2D map) carried no depth parameters, so the far hemisphere showed through and count badges floated above the limb. GlobeNative now seats every data layer against the depth-writing ocean sphere (test, don't write — no inter-marker z-fight), and the far-side billboard cull (`occludeFarSide`) faces the globe's OWN live camera instead of the parked (frozen) mapbox center — extended to `TextLayer` so a back-side "36" count badge disappears instead of hovering over the planet.
- **Terrain striping on the globe**: the draped ESRI imagery tiles each wrote depth, so coplanar neighbours z-fought at the tile seams (horizontal stripes / partial render). Tiles now depth-TEST only; the ocean sphere owns the depth buffer.
- **Live request-geo dots kept polling on the 3D globe**: the `/v1/world/cloud/*` poll was gated on `renderPaused`, which the native GlobeView sets on activation — so the realtime dots froze the moment you entered the default 3D view. It now gates on tab visibility, so the dots stay live (and fade) on the globe.
### Changed
- **Request-origin → serving-region arcs on by default** in the Cloud view: the `/cloud/traffic` arcs derive from the same real native request-geo points as the dots (origin country → nearest Hanzo region) and degrade to empty — never demo.
## [2.4.6]
### Added
- **Enso Flywheel panel (AI variant)**: the router self-improvement loop made visible. New `/v1/world/enso-training` folds the routing-decision ledger tail + reward tail (`export-routing-ledger` / `export-routing-rewards`, super-admin) into ledger growth, engine-vs-heuristic mix, a routing-confidence histogram, and task/model distributions, alongside the latest enso-bench eval scores (an embedded `results/summary.json` snapshot, overridable live via `ENSO_BENCH_URL`). The response is event-typed so future retrain/deploy milestones slot into the same timeline. Eval scores render even signed-out; `state` (live/partial/demo) says which live sources resolved.
## [2.4.5]
### Added
- **AI Compute panel (AI variant)**: live Hanzo inference-plane telemetry over SSE — tokens/s, requests/s, 24h spend, top models by real spend, and the serving fleet (GPUs, machines online, models served). New `/v1/world/ai-pulse` pushes typed `usage`/`fleet`/`status` frames (EventSource) and answers a plain GET with one JSON snapshot as the poll fallback. Honest "connecting"/"unavailable" states — never a zero dressed up as live; the service bearer stays server-side.
## [2.4.4]
### Changed
- **Cloud Pulse is real when a service token is wired**: `/v1/world/cloud-pulse` now folds MEASURED platform-wide 24h request/token volume from the ClickHouse-backed usage ledger (`get-cloud-usages ?org=all`, super-admin) on top of the live model/node/GPU/region counts — dropping both `demo:true` and `volumeModeled:true`. Top models come from real ledger spend. Without a token, or with a non-admin token, it stays honestly demo/modeled — platform numbers are never faked silently. The service bearer stays server-side (never sent to the browser).
## [2.4.2]
### Added
- **Streaming analyst**: answers flow in live over SSE — reasoning shows as dim thinking text, tool calls appear as chips the moment they run, the reply types itself in; the final render and command dispatch are unchanged (done event = the old JSON contract)
- **Model menu**: the composer pill opens a grouped popover (Auto / Zen / GPT / Llama / Claude / Agents) with per-family marks and an active check
### Fixed
- **Model identity**: the Zen ring appears only on zen* models — gpt-oss/llama/claude get their own marks on the pill, menu rows, avatars, and the thinking row
- **Dev server**: `npm run dev` proxies /v1 to production by default (VITE_DEV_API_PROXY overrides)
## [2.4.1]
### Added
- **Western Pacific cyclones**: cross-agency tropical-cyclone attribution (GDACS + HKO warnings via new `/v1/world/hko-warnings` proxy) with per-agency wind observations, canonical dedup, and map popup detail rows
- **Model roster**: `Best (auto)` leads the analyst model picker — the gateway routing alias that always resolves
### Changed
- **AI default model**: `zen5` → `best`; a pinned family id goes dark when the inference plane's claim catalog shifts, the routing alias never does
- **Server cache is now stale-while-revalidate**: `cachedJSON`/`passthrough` serve stale instantly and refresh in the background (single-flight); GDELT/theater-posture no longer stall requests ~10s on TTL expiry
- **GDELT cache warmers**: hot keys (analyst grounding, protests layer) refreshed every ~4min so no user ever eats a cold miss
- **Sparkline payloads**: close arrays rounded to 7 significant digits (float32-widening noise stripped, ~40% smaller; scalars untouched)
### Fixed
- **News first paint**: panels no longer gate their first DOM write on a 65MB ML sentiment model download — headlines paint immediately, sentiment refines in place
- **Analyst grounding snapshot**: context fetches bounded at 2.5s so a cold endpoint can't hold the chat send hostage
- **AI errors are honest**: upstream error codes (`insufficient_balance`, `spend_cap_exceeded`, …) surface in the chat instead of a bare `status 402`
## [2.4.0] - 2026-02-19
### Added
- **Live Webcams Panel**: 2x2 grid of live YouTube webcam feeds from global hotspots with region filters (Middle East, Europe, Asia-Pacific, Americas), grid/single view toggle, idle detection, and full i18n support (#111)
- **Linux download**: added `.AppImage` option to download banner
### Changed
- **Mobile detection**: use viewport width only for mobile detection; touch-capable notebooks (e.g. ROG Flow X13) now get desktop layout (#113)
- **Webcam feeds**: curated Tel Aviv, Mecca, LA, Miami; replaced dead Tokyo feed; diverse ALL grid with Jerusalem, Tehran, Kyiv, Washington
### Fixed
- **Le Monde RSS**: English feed URL updated (`/en/rss/full.xml` → `/en/rss/une.xml`) to fix 404
- **Workbox precache**: added `html` to `globPatterns` so `navigateFallback` works for offline PWA
- **Panel ordering**: one-time migration ensures Live Webcams follows Live News for existing users
**Real-time global intelligence dashboard** — AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface.
> Hanzo World is Hanzo AI, Inc.'s fork of [World Monitor](https://github.com/koala73/worldmonitor) (MIT) by Elie Habib. See [NOTICE](./NOTICE) for provenance and deviations.
- **RTL Support** — Native right-to-left layout support for Arabic (`ar`) and Hebrew.
- **Localized News Feeds** — Region-specific RSS selection based on language preference (e.g., viewing the app in French loads Le Monde, Jeune Afrique, and France24).
- **AI Translation** — Integrated LLM translation for news headlines and summaries, enabling cross-language intelligence gathering.
- **Regional Intelligence** — Dedicated monitoring panels for Africa, Latin America, Middle East, and Asia with local sources.
### Interactive 3D Globe
- **WebGL-accelerated rendering** — deck.gl + MapLibre GL JS for smooth 60fps performance with thousands of concurrent markers. Switchable between **3D globe** (with pitch/rotation) and **flat map** mode via `VITE_MAP_INTERACTION_MODE`
- **35+ data layers** — conflicts, military bases, nuclear facilities, undersea cables, pipelines, satellite fire detection, protests, natural disasters, datacenters, displacement flows, climate anomalies, cyber threat IOCs, stock exchanges, financial centers, central banks, commodity hubs, Gulf investments, and more
- **30+ data layers** — conflicts, military bases, nuclear facilities, undersea cables, pipelines, satellite fire detection, protests, natural disasters, datacenters, displacement flows, climate anomalies, cyber threat IOCs, and more
- **Smart clustering** — Supercluster groups markers at low zoom, expands on zoom in. Cluster thresholds adapt to zoom level
- **Progressive disclosure** — detail layers (bases, nuclear, datacenters) appear only when zoomed in; zoom-adaptive opacity fades markers from 0.2 at world view to 1.0 at street level
- **Label deconfliction** — overlapping labels (e.g., multiple BREAKING badges) are automatically suppressed by priority, highest-severity first
@@ -126,7 +111,7 @@ All three variants run from a single codebase — switch between them with one c
- Undersea cables with landing points
- Oil & gas pipelines
- AI datacenters (111 major clusters)
- 83 strategic ports across 6 types (container, oil, LNG, naval, mixed, bulk) with throughput rankings
- 84 strategic ports across 6 types (container, oil, LNG, naval, mixed, bulk) with throughput rankings
- Internet outages (Cloudflare Radar)
- Critical mineral deposits
- NASA FIRMS satellite fire detection (VIIRS thermal hotspots)
@@ -157,20 +142,9 @@ All three variants run from a single codebase — switch between them with one c
- 92 global stock exchanges — mega (NYSE, NASDAQ, Shanghai, Euronext, Tokyo), major (Hong Kong, London, NSE/BSE, Toronto, Korea, Saudi Tadawul), and emerging markets — with market caps and trading hours
- 19 financial centers — ranked by Global Financial Centres Index (New York #1 through offshore centers: Cayman Islands, Luxembourg, Bermuda, Channel Islands)
- 13 central banks — Federal Reserve, ECB, BoJ, BoE, PBoC, SNB, RBA, BoC, RBI, BoK, BCB, SAMA, plus supranational institutions (BIS, IMF)
- Gulf FDI investment layer — 64 Saudi/UAE foreign direct investments plotted globally, color-coded by status (operational, under-construction, announced), sized by investment amount
</details>
### Live News & Video
- **150+ RSS feeds** across geopolitics, defense, energy, tech, and finance — domain-allowlisted proxy prevents CORS issues. Each variant loads its own curated feed set: ~25 categories for geopolitical, ~20 for tech, ~18 for finance
- **100+ RSS feeds** across geopolitics, defense, energy, tech — domain-allowlisted proxy prevents CORS issues
- **8 live video streams** — Bloomberg, Sky News, Al Jazeera, Euronews, DW, France24, CNBC, Al Arabiya — with automatic live detection that scrapes YouTube channel pages every 5 minutes to find active streams
- **Desktop embed bridge** — YouTube's IFrame API restricts playback in native webviews (error 153). The dashboard detects this and transparently routes through a cloud-hosted embed proxy with bidirectional message passing (play/pause/mute/unmute/loadVideo)
- **Idle-aware playback** — video players pause and are removed from the DOM after 5 minutes of inactivity, resuming when the user returns. Tab visibility changes also suspend/resume streams
@@ -193,11 +167,11 @@ All three variants run from a single codebase — switch between them with one c
### Desktop Application (Tauri)
- **Native desktop app** for macOS and Windows — packages the full dashboard with a local Node.js sidecar that runs all 60+ API handlers locally
- **Native desktop app** for macOS and Windows — packages the full dashboard with a local Node.js sidecar that runs all 45+ API handlers locally
- **OS keychain integration** — API keys stored in the system credential manager (macOS Keychain, Windows Credential Manager), never in plaintext files
- **Token-authenticated sidecar** — a unique session token prevents other local processes from accessing the sidecar on localhost. Generated per launch using randomized hashing
- **Cloud fallback** — when a local API handler fails or is missing, requests transparently fall through to the cloud deployment (worldmonitor.app) with origin headers stripped
- **Settings window** — dedicated configuration UI (Cmd+,) for managing 17 API keys with validation, signup links, and feature-availability indicators
- **Settings window** — dedicated configuration UI (Cmd+,) for managing 15 API keys with validation, signup links, and feature-availability indicators
- **Verbose debug mode** — toggle traffic logging with persistent state across restarts. View the last 200 requests with timing, status codes, and error details
- **DevTools toggle** — Cmd+Alt+I opens the embedded web inspector for debugging
@@ -236,10 +210,6 @@ All three variants run from a single codebase — switch between them with one c
- **Download banner** — persistent notification for web users linking to native desktop installers for their detected platform
- **Download API** — `/api/download?platform={windows-exe|windows-msi|macos-arm64|macos-x64}` redirects to the matching GitHub Release asset, with fallback to the releases page
- **Non-tier country support** — clicking countries outside the 22 tier-1 list opens a brief with available data (news, markets, infrastructure) and a "Limited coverage" badge; country names for non-tier countries resolve via `Intl.DisplayNames`
- **Feature toggles** — 14 runtime toggles (AI/Groq, AI/OpenRouter, FRED economic, EIA energy, internet outages, ACLED conflicts, threat intel feeds, AIS relay, OpenSky, Finnhub, NASA FIRMS) stored in `localStorage`, allowing administrators to enable/disable data sources without rebuilding
- **AIS chokepoint detection** — the relay server monitors 8 strategic maritime chokepoints (Strait of Hormuz, Suez Canal, Malacca Strait, Bab el-Mandeb, Panama Canal, Taiwan Strait, South China Sea, Turkish Straits) and classifies transiting vessels by naval candidacy using MMSI prefixes, ship type codes, and name patterns
- **AIS density grid** — vessel positions are aggregated into 2°×2° geographic cells over 30-minute windows, producing a heatmap of maritime traffic density that feeds into convergence detection
- **Panel resizing** — drag handles on panel edges allow height adjustment (span-1 through span-4 grid rows), persisted to localStorage. Double-click resets to default height
---
@@ -516,7 +486,7 @@ API calls to WorldPop are batched concurrently (max 10 parallel requests) to han
### Strategic Port Infrastructure
83 strategic ports are cataloged across six types, reflecting their role in global trade and military posture:
84 strategic ports are cataloged across six types, reflecting their role in global trade and military posture:
| Type | Count | Examples |
|------|-------|---------|
@@ -596,23 +566,6 @@ VWAP = Σ(price × volume) / Σ(volume) for last 30 trading days
The **Mayer Multiple** (BTC price / SMA200) provides a long-term valuation context — historically, values above 2.4 indicate overheating, while values below 0.8 suggest deep undervaluation.
### Gulf FDI Investment Database
The Finance variant includes a curated database of 64 major foreign direct investments by Saudi Arabia and the UAE in global critical infrastructure. Investments are tracked across 12 sectors:
| Sector | Examples |
|--------|---------|
| **Ports** | DP World's 11 global container terminals, AD Ports (Khalifa, Al-Sokhna, Karachi), Saudi Mawani ports |
| **Energy** | ADNOC Ruwais LNG (9.6 mtpa), Aramco's Motiva Port Arthur refinery (630K bpd), ACWA Power renewables |
| **Renewables** | Masdar wind/solar (UK Hornsea, Zarafshan 500MW, Gulf of Suez), NEOM Green Hydrogen (world's largest) |
| **Megaprojects** | NEOM THE LINE ($500B), Saudi National Cloud ($6B hyperscale datacenters) |
| **Telecoms** | STC's 9.9% stake in Telefónica, PIF's 20% of Telecom Italia NetCo |
Each investment records the investing entity (DP World, Mubadala, PIF, ADNOC, Masdar, Saudi Aramco, ACWA Power, etc.), target country, geographic coordinates, investment amount (USD), ownership stake, operational status, and year. The Investments Panel provides filterable views by country (SA/UAE), sector, entity, and status — clicking any row navigates the map to the investment location.
On the globe, investments appear as scaled bubbles: ≥$50B projects (NEOM) render at maximum size, while sub-$1B investments use smaller markers. Color encodes status: green for operational, amber for under-construction, blue for announced.
### Stablecoin Peg Monitoring
Five major stablecoins (USDT, USDC, DAI, FDUSD, USDe) are monitored via the CoinGecko API with 2-minute caching. Each coin's deviation from the $1.00 peg determines its health status:
@@ -650,25 +603,6 @@ This is an approximation, not a substitute for official flow data, but it captur
---
## Tri-Variant Architecture
A single codebase produces three specialized dashboards, each with distinct feeds, panels, map layers, and branding:
**Build-time selection** — the `VITE_VARIANT` environment variable controls which configuration is bundled. A Vite HTML plugin transforms meta tags, Open Graph data, PWA manifest, and JSON-LD structured data at build time. Each variant tree-shakes unused data files — the finance build excludes military base coordinates and APT group data, while the geopolitical build excludes stock exchange listings.
**Runtime switching** — a variant selector in the header bar (🌍 WORLD | 💻 TECH | 📈 FINANCE) navigates between deployed domains on the web, or sets `localStorage['worldmonitor-variant']` in the desktop app to switch without rebuilding.
---
## Architecture Principles
| Principle | Implementation |
@@ -684,7 +618,7 @@ A single codebase produces three specialized dashboards, each with distinct feed
| **Cache everything, trust nothing** | Three-tier caching (in-memory → Redis → upstream) with versioned cache keys and stale-on-error fallback. Every API response includes `X-Cache` header for debugging. CDN layer (`s-maxage`) absorbs repeated requests before they reach edge functions. |
| **Bandwidth efficiency** | Gzip compression on all relay responses (80% reduction). Content-hash static assets with 1-year immutable cache. Staggered polling intervals prevent synchronized API storms. Animations and polling pause on hidden tabs. |
| **Baseline-aware alerting** | Trending keyword detection uses rolling 2-hour windows against 7-day baselines with per-term spike multipliers, cooldowns, and source diversity requirements — surfacing genuine surges while suppressing noise. |
| **Run anywhere** | Same codebase produces three specialized variants (geopolitical, tech, finance) and deploys to Vercel (web), Railway (relay), Tauri (desktop), and PWA (installable). Desktop sidecar mirrors all cloud API handlers locally. Service worker caches map tiles for offline use while keeping intelligence data always-fresh (NetworkOnly). |
| **Run anywhere** | Same codebase deploys to Vercel (web), Railway (relay), Tauri (desktop), and PWA (installable). Desktop sidecar mirrors all cloud API handlers locally. Service worker caches map tiles for offline use while keeping intelligence data always-fresh (NetworkOnly). |
---
@@ -705,7 +639,7 @@ Feeds also carry a **propaganda risk rating** and **state affiliation flag**. St
## Edge Function Architecture
World Monitor uses 60+ Vercel Edge Functions as a lightweight API layer. Each edge function handles a single data source concern — proxying, caching, or transforming external APIs. This architecture avoids a monolithic backend while keeping API keys server-side:
World Monitor uses 45+ Vercel Edge Functions as a lightweight API layer. Each edge function handles a single data source concern — proxying, caching, or transforming external APIs. This architecture avoids a monolithic backend while keeping API keys server-side:
- **RSS Proxy** — domain-allowlisted proxy for 100+ feeds, preventing CORS issues and hiding origin servers. Feeds from domains that block Vercel IPs are automatically routed through the Railway relay.
- **AI Pipeline** — Groq and OpenRouter edge functions with Redis deduplication, so identical headlines across concurrent users only trigger one LLM call. The classify-event endpoint pauses its queue on 500 errors to avoid wasting API quota.
@@ -713,7 +647,6 @@ World Monitor uses 60+ Vercel Edge Functions as a lightweight API layer. Each ed
- **Market Intelligence** — macro signals, ETF flows, and stablecoin monitors compute derived analytics server-side (VWAP, SMA, peg deviation, flow estimates) and cache results in Redis
- **Temporal Baseline** — Welford's algorithm state is persisted in Redis across requests, building statistical baselines without a traditional database
- **Custom Scrapers** — sources without RSS feeds (FwdStart, GitHub Trending, tech events) are scraped and transformed into RSS-compatible formats
- **Finance Geo Data** — stock exchanges (92), financial centers (19), central banks (13), and commodity hubs (10) are served as static typed datasets with market caps, GFCI rankings, trading hours, and commodity specializations
All edge functions include circuit breaker logic and return cached stale data when upstream APIs are unavailable, ensuring the dashboard never shows blank panels.
@@ -721,12 +654,12 @@ All edge functions include circuit breaker logic and return cached stale data wh
## Multi-Platform Architecture
All three variants run on three platforms that work together:
World Monitor runs on three platforms that work together:
```
┌─────────────────────────────────────┐
│ Vercel (Edge) │
│ 60+ edge functions · static SPA │
│ 45+ edge functions · static SPA │
│ CORS allowlist · Redis cache │
│ AI pipeline · market analytics │
│ CDN caching (s-maxage) · PWA host │
@@ -736,7 +669,7 @@ All three variants run on three platforms that work together:
│ ┌───────────────────────────────────┐
│ │ Tauri Desktop (Rust + Node) │
│ │ OS keychain · Token-auth sidecar │
│ │ 60+ local API handlers · gzip │
│ │ 45+ local API handlers · gzip │
│ │ Cloud fallback · Traffic logging │
│ └───────────────────────────────────┘
│
@@ -778,7 +711,7 @@ The Tauri desktop app wraps the dashboard in a native window with a local Node.j
@@ -792,7 +725,7 @@ The Tauri desktop app wraps the dashboard in a native window with a local Node.j
### Secret Management
API keys are stored in the operating system's credential manager (macOS Keychain, Windows Credential Manager) — never in plaintext config files. At sidecar launch, all 17 supported secrets are read from the keyring, trimmed, and injected as environment variables. Empty or whitespace-only values are skipped.
API keys are stored in the operating system's credential manager (macOS Keychain, Windows Credential Manager) — never in plaintext config files. At sidecar launch, all 15 supported secrets are read from the keyring, trimmed, and injected as environment variables. Empty or whitespace-only values are skipped.
Secrets can also be updated at runtime without restarting the sidecar: saving a key in the Settings window triggers a `POST /api/local-env-update` call that hot-patches `process.env` and clears the module cache so handlers pick up the new value immediately.
@@ -883,7 +816,7 @@ The AI summarization pipeline adds content-based deduplication: headlines are ha
| Layer | Mechanism |
|-------|-----------|
| **CORS origin allowlist** | Only `worldmonitor.app`, `tech.worldmonitor.app`,`finance.worldmonitor.app`, and `localhost:*` can call API endpoints. All others receive 403. Implemented in `api/_cors.js`. |
| **CORS origin allowlist** | Only `worldmonitor.app`, `tech.worldmonitor.app`, and `localhost:*` can call API endpoints. All others receive 403. Implemented in `api/_cors.js`. |
| **RSS domain allowlist** | The RSS proxy only fetches from explicitly listed domains (~90+). Requests for unlisted domains are rejected with 403. |
| **Railway domain allowlist** | The Railway relay has a separate, smaller domain allowlist for feeds that need the alternate origin. |
| **API key isolation** | All API keys live server-side in Vercel environment variables. The browser never sees Groq, OpenRouter, ACLED, Finnhub, or other credentials. |
@@ -903,7 +836,7 @@ The AI summarization pipeline adds content-based deduplication: headlines are ha
vercel dev # Runs frontend + all 60+ API edge functions
vercel dev # Runs frontend + all 45+ API edge functions
```
Open [http://localhost:3000](http://localhost:3000)
@@ -936,7 +869,7 @@ See [`.env.example`](./.env.example) for the complete list with registration lin
## Self-Hosting
World Monitor relies on **60+ Vercel Edge Functions** in the `api/` directory for RSS proxying, data caching, and API key isolation. Running `npm run dev` alone starts only the Vite frontend — the edge functions won't execute, and most panels (news feeds, markets, AI summaries) will be empty.
World Monitor relies on **45+ Vercel Edge Functions** in the `api/` directory for RSS proxying, data caching, and API key isolation. Running `npm run dev` alone starts only the Vite frontend — the edge functions won't execute, and most panels (news feeds, markets, AI summaries) will be empty.
### Option 1: Deploy to Vercel (Recommended)
@@ -947,7 +880,7 @@ npm install -g vercel
vercel # Follow prompts to link/create project
```
Add your API keys in the Vercel dashboard under **Settings → Environment Variables**, then visit your deployment URL. The free Hobby plan supports all 60+ edge functions.
Add your API keys in the Vercel dashboard under **Settings → Environment Variables**, then visit your deployment URL. The free Hobby plan supports all 45+ edge functions.
### Option 2: Local Development with Vercel CLI
@@ -1006,9 +939,8 @@ Set `WS_RELAY_URL` (server-side, HTTPS) and `VITE_WS_RELAY_URL` (client-side, WS
- [x] AIS maritime chokepoint detection and vessel density grid
- [x] Runtime feature toggles for 14 data sources
- [x] Panel height resizing with persistent layout state
- [ ] Mobile-optimized views
- [ ] Push notifications for critical alerts
- [ ] Self-hosted Docker image
@@ -1094,7 +1017,7 @@ See [full roadmap](./docs/DOCUMENTATION.md#roadmap).
## Support the Project
If you find Hanzo World useful:
If you find World Monitor useful:
- **Star this repo** to help others discover it
- **Share** with colleagues interested in OSINT
@@ -1109,16 +1032,15 @@ MIT License — see [LICENSE](LICENSE) for details.
---
## Authors
## Author
**Hanzo World** is maintained by **Hanzo AI, Inc.** — fork of the original **World Monitor** by **Elie Habib**([GitHub](https://github.com/koala73)), used under the MIT License. See [NOTICE](./NOTICE).
# World Monitor Roadmap: Intelligence Correlation Enhancements
This document outlines the top 5 features a geopolitical intelligence analyst would want, focusing on **correlation between existing data points** and leveraging **free APIs/RSS feeds only**.
---
## Current Correlation Capabilities
### What We Already Do Well
| Signal Type | Description | Data Sources |
|------------|-------------|--------------|
| **Convergence** | 3+ source types report same story within 30min | News feeds |
| **Silent Divergence** | Market moves 2%+ with minimal related news | Yahoo/Finnhub + News |
| **Flow/Price Divergence** | Energy price spike without pipeline news | Markets + News |
| **Related Assets** | News stories enriched with nearby infrastructure | Hotspots + All assets |
| **GDELT Tensions** | Country-pair tension scores with 7-day trends | GDELT GPR API |
### What's Missing
1.**No cross-layer correlation** - Protests, military movements, and economic data don't talk to each other
2.**No temporal pattern detection** - Can't detect "unusual for this time of year"
3.**No geographic clustering** - Multiple event types in same region not flagged
4.**No country-level aggregation** - No unified risk view per country
5.**No infrastructure dependency mapping** - Don't show cascade effects
---
## Top 5 Priority Features
### 1. Multi-Signal Geographic Convergence
**What:** When 3+ independent data types converge on the same geographic region within 24-48 hours, generate a high-priority alert.
**Why:** The most valuable I&W (Indications & Warning) signals come from multiple independent sources detecting activity in the same area. A protest + military flight activity + shipping disruption in the same region is far more significant than any single event.
**Data Sources (Already Have):**
- Protests (ACLED/GDELT) → lat/lon
- Military flights (OpenSky) → lat/lon
- Military vessels (AIS) → lat/lon
- Earthquakes/natural events → lat/lon
- News hotspots → lat/lon
- Chokepoint congestion → lat/lon
- Pipeline incidents → lat/lon (inferred)
**Implementation:**
```
1. Define 50km grid cells globally
2. Each refresh cycle, tag events to grid cells
3. Track event counts by type per cell over 24h window
4. Alert when: cell has events from 3+ distinct data types
- **World Bank Governance Indicators** (annual, free API)
- **UN Refugee Data** (UNHCR, RSS feeds)
- **Election proximity** (static calendar)
**Implementation:**
```
1. Map all events to ISO country codes
2. Maintain rolling 7-day and 30-day baselines per country
3. Calculate Z-scores for each component
4. Weight and sum to 0-100 index
5. Track index changes for trend detection
```
**UI:**
- Choropleth map layer showing index by color
- Sortable country list panel
- Click country → drill-down to component breakdown
- Alert when country moves 10+ points in 24h
---
### 3. Trade Route Risk Scoring
**What:** Real-time risk assessment for major shipping routes, showing which supply chains are most vulnerable right now.
**Why:** Supply chain disruptions are the primary economic consequence of geopolitical events. An analyst needs to quickly assess "if X happens, what trade is affected?"
**Major Routes to Score:**
| Route | Chokepoints | Commodities |
|-------|-------------|-------------|
| Asia → Europe (Suez) | Suez, Bab el-Mandeb, Malacca | Containers, oil |
| Asia → US West Coast | Malacca, Taiwan Strait, Panama | Containers, electronics |
| Middle East → Europe | Hormuz, Suez, Bosphorus | Oil, LNG |
| Russia → Europe | Baltic, Bosphorus | Oil, gas, grain |
| South America → Asia | Panama, Magellan | Commodities, grain |
**Risk Components:**
| Factor | Source | Notes |
|--------|--------|-------|
| Chokepoint congestion | AIS density | Real-time |
| Sanctions impact | Config | Which ports blocked |
| Port delays | Inferred from AIS | Real-time |
**Implementation:**
```
1. Define route polylines with chokepoint waypoints
2. For each chokepoint, calculate: density_change + gap_rate + weather_alerts + conflict_distance
3. Weight by chokepoint criticality (Hormuz > Malacca > Panama for oil)
4. Sum to 0-100 risk score per route
5. Compare to 30-day baseline for trend
```
**UI:**
- Route lines on map colored by risk (green → yellow → red)
- Panel showing route rankings with trends
- Click route → show chokepoint breakdown
- Alert when route risk jumps 20+ points
---
### 4. Infrastructure Cascade Visualization
**What:** When you click any infrastructure asset, show what depends on it and what would be affected by its disruption.
**Why:** Critical infrastructure is interconnected. A submarine cable fault affects countries downstream. A pipeline disruption affects refineries and ports. Analysts need to see the "so what."
**Dependency Mappings:**
**Ports → dependent on:**
- Pipelines (oil/LNG terminals)
- Submarine cables (data for port operations)
- Nearby naval bases (protection)
- Chokepoints (access routes)
**Cables → serve:**
- Countries (list from cable data)
- Data centers (proximity)
- Financial centers (criticality)
**Pipelines → connect:**
- Origin countries
- Transit countries
- Destination ports/refineries
- Alternate routes
**Implementation:**
```
1. Build static dependency graph in config
2. For cables: map landing points to countries
3. For pipelines: map to origin/transit/destination
4. For ports: map to pipelines that terminate there
5. On asset click: traverse graph, highlight dependents on map
6. Show impact panel: "Disruption would affect: X countries, Y trade volume"
- **Global Energy Monitor** pipeline database (public)
- **UN COMTRADE** for trade flow volumes (free API)
---
### 5. Temporal Anomaly Detection
**What:** Detect when current activity levels deviate significantly from historical norms for the same time period (day of week, month, season).
**Why:** "Unusual activity" only makes sense in context. Military flights on a Tuesday might be normal; the same level on a Sunday might be significant. Activity in December might be normal for end-of-year exercises but unusual in March.
**What to Track:**
| Data Type | Baseline Period | Anomaly Threshold |
| Multi-Signal Geographic Convergence | Medium | Very High | 1 |
| Country Instability Index | Medium | High | 2 |
| Temporal Anomaly Detection | Medium | High | 3 |
| Trade Route Risk Scoring | High | High | 4 |
| Infrastructure Cascade Viz | High | Medium | 5 |
**Recommended approach:** Implement features 1-3 first as they primarily leverage existing data with new correlation logic. Features 4-5 require additional data mapping and UI work.
The most valuable enhancements for a geopolitical analyst focus on **correlation, not accumulation**. The dashboard already aggregates vast amounts of data; the next step is making that data talk to each other.
Priority 1 (Geographic Convergence) alone would significantly elevate the tool's I&W capability by detecting when multiple independent signals point to the same location—the hallmark of significant events.
All proposed features use **existing data sources** or **free APIs/RSS feeds**, keeping with the project's accessible, open-source philosophy.
@@ -144,9 +136,6 @@ export default async function handler(request) {
constisTechVariant=variant==='tech';
constdateContext=`Current date: ${newDate().toISOString().split('T')[0]}.${isTechVariant?'':' Donald Trump is the current US President (second term, inaugurated Jan 2025).'}`;
// Language instruction
constlangInstruction=lang&&lang!=='en'?`\nIMPORTANT: Output the summary in ${lang.toUpperCase()} language.`:'';
if(mode==='brief'){
if(isTechVariant){
// Tech variant: focus on startups, AI, funding, product launches
@@ -158,7 +147,7 @@ Rules:
- IGNORE political news, trade policy, tariffs, government actions unless directly about tech regulation
- Lead with the company/product/technology name
- Start directly: "OpenAI announced...", "A new $50M Series B...", "GitHub released..."
- No bullet points, no meta-commentary${langInstruction}`;
- No bullet points, no meta-commentary`;
}else{
// Full variant: geopolitical focus
systemPrompt=`${dateContext}
@@ -170,7 +159,7 @@ Rules:
- Start directly with the subject: "Iran's regime...", "The US Treasury...", "Protests in..."
- CRITICAL FOCAL POINTS are the main actors - mention them by name
- If focal points show news + signals convergence, that's the lead
- No bullet points, no meta-commentary${langInstruction}`;
- No bullet points, no meta-commentary`;
}
userPrompt=`Summarize the top story:\n${headlineText}${intelSection}`;
}elseif(mode==='analysis'){
@@ -198,19 +187,10 @@ Rules:
userPrompt=isTechVariant
?`What's the key tech trend or development?\n${headlineText}${intelSection}`
:`What's the key pattern or risk?\n${headlineText}${intelSection}`;
}elseif(mode==='translate'){
consttargetLang=variant;// In translate mode, variant param holds the target language code (e.g., 'fr', 'es')
systemPrompt=`You are a professional news translator. Translate the following news headlines/summaries into ${targetLang}.
Rules:
- Maintain the original tone and journalistic style.
- Do NOT add any conversational filler (e.g., "Here is the translation").
- Output ONLY the translated text.
- If the text is already in ${targetLang}, return it as is.`;
userPrompt=`Translate to ${targetLang}:\n${headlines[0]}`;
}else{
systemPrompt=isTechVariant
?`${dateContext}\n\nSynthesize tech news in 2 sentences. Focus on startups, AI, funding, products. Ignore politics unless directly about tech regulation.${langInstruction}`
:`${dateContext}\n\nSynthesize in 2 sentences max. Lead with substance. NEVER start with "Breaking news" or "Tonight" - just state the insight directly. CRITICAL focal points with news-signal convergence are significant.${langInstruction}`;
?`${dateContext}\n\nSynthesize tech news in 2 sentences. Focus on startups, AI, funding, products. Ignore politics unless directly about tech regulation.`
:`${dateContext}\n\nSynthesize in 2 sentences max. Lead with substance. NEVER start with "Breaking news" or "Tonight" - just state the insight directly. CRITICAL focal points with news-signal convergence are significant.`;
@@ -145,9 +137,6 @@ export default async function handler(request) {
constisTechVariant=variant==='tech';
constdateContext=`Current date: ${newDate().toISOString().split('T')[0]}.${isTechVariant?'':' Donald Trump is the current US President (second term, inaugurated Jan 2025).'}`;
// Language instruction
constlangInstruction=lang&&lang!=='en'?`\nIMPORTANT: Output the summary in ${lang.toUpperCase()} language.`:'';
if(mode==='brief'){
if(isTechVariant){
// Tech variant: focus on startups, AI, funding, product launches
@@ -159,7 +148,7 @@ Rules:
- IGNORE political news, trade policy, tariffs, government actions unless directly about tech regulation
- Lead with the company/product/technology name
- Start directly: "OpenAI announced...", "A new $50M Series B...", "GitHub released..."
- No bullet points, no meta-commentary${langInstruction}`;
- No bullet points, no meta-commentary`;
}else{
// Full variant: geopolitical focus
systemPrompt=`${dateContext}
@@ -171,7 +160,7 @@ Rules:
- Start directly with the subject: "Iran's regime...", "The US Treasury...", "Protests in..."
- CRITICAL FOCAL POINTS are the main actors - mention them by name
- If focal points show news + signals convergence, that's the lead
- No bullet points, no meta-commentary${langInstruction}`;
- No bullet points, no meta-commentary`;
}
userPrompt=`Summarize the top story:\n${headlineText}${intelSection}`;
}elseif(mode==='analysis'){
@@ -199,18 +188,10 @@ Rules:
userPrompt=isTechVariant
?`What's the key tech trend or development?\n${headlineText}${intelSection}`
:`What's the key pattern or risk?\n${headlineText}${intelSection}`;
}elseif(mode==='translate'){
consttargetLang=variant;// In translate mode, variant param holds the target language code
systemPrompt=`You are a professional news translator. Translate the following news headlines/summaries into ${targetLang}.
Rules:
- Maintain the original tone and journalistic style.
- Do NOT add any conversational filler.
- Output ONLY the translated text.`;
userPrompt=`Translate to ${targetLang}:\n${headlines[0]}`;
}else{
systemPrompt=isTechVariant
?`${dateContext}\n\nSynthesize tech news in 2 sentences. Focus on startups, AI, funding, products. Ignore politics unless directly about tech regulation.${langInstruction}`
:`${dateContext}\n\nSynthesize in 2 sentences max. Lead with substance. NEVER start with "Breaking news" or "Tonight" - just state the insight directly. CRITICAL focal points with news-signal convergence are significant.${langInstruction}`;
?`${dateContext}\n\nSynthesize tech news in 2 sentences. Focus on startups, AI, funding, products. Ignore politics unless directly about tech regulation.`
:`${dateContext}\n\nSynthesize in 2 sentences max. Lead with substance. NEVER start with "Breaking news" or "Tonight" - just state the insight directly. CRITICAL focal points with news-signal convergence are significant.`;
- **Middle East / MENA** - Al Jazeera, BBC ME, Guardian ME, Al Arabiya, Times of Israel
- **Africa** - BBC Africa, News24, Google News aggregation (regional & Sahel coverage)
@@ -223,7 +213,6 @@ Multi-source RSS aggregation across categories:
The **📡 SOURCES** button in the header opens a global source management modal, enabling fine-grained control over which news sources appear in the dashboard.
**Capabilities:**
- **Search**: Filter the source list by name to quickly find specific outlets
- **Individual Toggle**: Click any source to enable/disable it
- **Bulk Actions**: "Select All" and "Select None" for quick adjustments
@@ -231,14 +220,12 @@ The **📡 SOURCES** button in the header opens a global source management modal
- **Persistence**: Settings are saved to localStorage and persist across sessions
**Use Cases:**
- **Noise Reduction**: Disable high-volume aggregators (Google News) to focus on primary sources
- **Regional Focus**: Enable only sources relevant to a specific geographic area
- **Source Quality**: Disable sources with poor signal-to-noise ratio
- **Bias Management**: Balance coverage by enabling/disabling sources with known editorial perspectives
**Technical Details:**
- Disabled sources are filtered at fetch time (not display time), reducing bandwidth and API calls
- Affects all news panels simultaneously—disable BBC once, it's gone everywhere
- Panels with all sources disabled show "All sources disabled" message
@@ -273,7 +260,6 @@ Embedded YouTube live streams from major news networks with channel switching:
| **Al Jazeera** | Middle East & international news |
**Core Features:**
- **Channel Switcher** - One-click switching between networks
The system maintains 48-point history (24 hours at 30-minute intervals) per hotspot:
- **Linear regression** calculates slope of recent scores
- **Rising**: Slope > +0.1 points per interval
- **Falling**: Slope < -0.1 points per interval
@@ -866,7 +838,6 @@ The system maintains 48-point history (24 hours at 30-minute intervals) per hots
**Signal Generation**
Escalation signals (`hotspot_escalation`) are emitted when:
1. Final score exceeds threshold (typically 60)
2. At least 2 hours since last signal for this hotspot (cooldown)
3. Trend is rising or score is critical (>80)
@@ -1027,7 +998,6 @@ The floor applies *after* the standard calculation—if the computed score excee
### Trend Detection
The CII tracks 24-hour changes to identify trajectory:
- **Rising**: Score increased by ≥5 points (escalating situation)
- **Stable**: Change within ±5 points (steady state)
- **Falling**: Score decreased by ≥5 points (de-escalation)
@@ -1043,18 +1013,15 @@ Beyond the base component scores, several contextual factors can boost a country
| **Focal Point** | 8 | AI focal point detection on country | Multi-source convergence indicator |
**Hotspot Boost Calculation**:
- Hotspot activity (0-100) scaled by 1.5× then capped at 10
- Zero boost for countries with no associated hotspot activity
**News Urgency Boost Tiers**:
- Information ≥70: +5 points
- Information ≥50: +3 points
- Information <50: +0 points
**Focal Point Boost Tiers**:
- Critical urgency: +8 points
- Elevated urgency: +4 points
- Normal urgency: +0 points
@@ -1072,7 +1039,6 @@ On dashboard startup, the CII system enters **Learning Mode**—a 15-minute cali
**Note**: Server-side pre-computation now provides immediate scores to new users—Learning Mode primarily affects client-side dynamic adjustments and alert generation rather than initial score display.
**Why 15 minutes?** Real-world testing showed that CII scores stabilize after approximately 10-20 minutes of data collection. The 15-minute window provides sufficient time for:
- Multiple refresh cycles across all data sources
- Trend detection to establish direction (rising/stable/falling)
- Cross-source correlation to normalize bias
@@ -1107,7 +1073,6 @@ This ensures users understand that early scores are provisional while preventing
- Residential IP ranges (not blocked like cloud providers)
- WebSocket support for persistent connections
- Global edge deployment for low latency
@@ -1661,26 +1614,22 @@ Vessels within 50km of these bases are flagged, enabling detection of unusual ac
Military aircraft are tracked via the OpenSky Network using ADS-B data. OpenSky blocks unauthenticated requests from cloud provider IPs (Vercel, Railway, AWS), so aircraft tracking requires a relay server with credentials.
**Authentication**:
- Register for a free account at [opensky-network.org](https://opensky-network.org)
- Create an API client in account settings to get `OPENSKY_CLIENT_ID` and `OPENSKY_CLIENT_SECRET`
- The relay uses **OAuth2 client credentials flow** to obtain Bearer tokens
- Tokens are cached (30-minute expiry) and automatically refreshed
**Identification Methods**:
- **Callsign matching**: Known military callsign patterns (RCH, REACH, DUKE, etc.)
- **ICAO hex ranges**: Military aircraft use assigned hex code blocks by country
3. EONET geometry provides more precise coordinates
@@ -2908,7 +2817,6 @@ When both GDACS and EONET report the same event:
### Filtering Logic
To prevent map clutter, natural events are filtered:
- **Wildfires**: Only events < 48 hours old (older fires are either contained or well-known)
- **Earthquakes**: M4.5+ globally, lower threshold for populated areas
- **Storms**: Only named storms or those with warnings
@@ -3020,7 +2928,6 @@ Nine geographic theaters are monitored continuously, each with custom thresholds
Beyond raw counts, the system assesses whether forces in a theater constitute an **offensive strike package**—the combination of assets required for sustained combat operations.
This design prioritizes geographic awareness over label density—users can quickly scan for markers and then interact for context.
### Panel Management
- **Drag panels** - Reorder layout
- **Settings (⚙)** - Toggle panel visibility
### Shareable Links
The current view state is encoded in the URL, enabling:
- **Bookmarking**: Save specific views for quick access
- **Sharing**: Send colleagues a link to your exact map position and layer configuration
- **Deep linking**: Link directly to a specific region or feature
@@ -3529,11 +3429,9 @@ Values are validated and clamped to prevent invalid states.
## Data Sources
### News Feeds
Aggregates **70+ RSS feeds** from major news outlets, government sources, and specialty publications with source-tier prioritization. Categories include world news, MENA, Africa, Latin America, Asia-Pacific, energy, technology, AI/ML, finance, government releases, defense/intel, think tanks, and international crisis organizations.
### Geospatial Data
- **Hotspots**: 30+ global intelligence hotspots with keyword correlation (including Sahel, Haiti, Horn of Africa)
- **Conflicts**: 10+ active conflict zones with involved parties
- **Military Bases**: 220+ installations from US, NATO, Russia, China, and allies
@@ -3545,7 +3443,6 @@ Aggregates **70+ RSS feeds** from major news outlets, government sources, and sp
@@ -3563,36 +3460,30 @@ Aggregates **70+ RSS feeds** from major news outlets, government sources, and sp
This project uses data from the following sources. Please respect their terms of use.
### Aircraft Tracking
Data provided by [The OpenSky Network](https://opensky-network.org). If you use this data in publications, please cite:
> Matthias Schäfer, Martin Strohmeier, Vincent Lenders, Ivan Martinovic and Matthias Wilhelm. "Bringing Up OpenSky: A Large-scale ADS-B Sensor Network for Research". In *Proceedings of the 13th IEEE/ACM International Symposium on Information Processing in Sensor Networks (IPSN)*, pages 83-94, April 2014.
### Conflict & Protest Data
- **ACLED**: Armed Conflict Location & Event Data. Source: [ACLED](https://acleddata.com). Data must be attributed per their [Attribution Policy](https://acleddata.com/attributionpolicy/).
- **GDELT**: Global Database of Events, Language, and Tone. Source: [The GDELT Project](https://www.gdeltproject.org/).
### Financial Data
- **Stock Quotes**: Powered by [Finnhub](https://finnhub.io/) (primary), with [Yahoo Finance](https://finance.yahoo.com/) as backup for indices and commodities
- **Cryptocurrency**: Powered by [CoinGecko API](https://www.coingecko.com/en/api)
- **Economic Indicators**: Data from [FRED](https://fred.stlouisfed.org/), Federal Reserve Bank of St. Louis
The application fetches news via `src/services/rss.ts`.
- **Mechanism**: Direct HTTP requests (via proxy) to RSS/Atom XML feeds.
- **Processing**: `DOMParser` parses XML client-side.
- **Storage**: Items are stored in-memory in `App.ts` (`allNews`, `newsByCategory`).
## The Challenge
Legacy RSS feeds are static XML files in their original language. There is no built-in "negotiation" for language. To display French news, we must either:
1. Fetch French feeds.
2. Translate English feeds on the fly.
## Proposed Solutions
### Option 1: Localized Feed Discovery (Recommended for "Major" Support)
Instead of forcing translation, we switch the *source* based on the selected language.
- **Implementation**:
- In `src/config/feeds.ts`, change the simple URL string to an object: `url: { en: '...', fr: '...' }` or separate constant lists `FEEDS_EN`, `FEEDS_FR`.
- **Pros**: Zero latency, native content quality, no API costs.
- **Cons**: Hard to find equivalent feeds for niche topics (e.g., specific mil-tech blogs) in all languages.
- **Strategy**: Creating a curated list of international feeds for major categories (World, Politics, Finance) is the most robust & scalable approach.
### Option 2: On-Demand Client-Side Translation
Add a "Translate" button to each news card.
- **Implementation**:
- Click triggers a call to a translation API (Google/DeepL/LLM).
- Store result in a local cache (Map).
- **Pros**: Low cost (only used when needed), preserves original context.
- **Quality**: Short headlines often translate poorly without context.
## Recommendation
**Hybrid Approach**:
1. **Primary**: Source localized feeds where possible (e.g., Le Monde for FR, Spiegel for DE). This requires a community effort to curate `feeds.json` for each locale.
2. **Fallback**: Keep English feeds for niche tech/intel sources where no alternative exists.
3. **Feature**: Add a "Summarize & Translate" button using the existing LLM worker. The prompt to the LLM (currently used for summaries) can be adjusted to "Summarize this in [Current Language]".
## Next Steps
1. Audit `src/config/feeds.ts` to structure it for multi-language support.
2. Update `rss.ts` to select the correct URL based on `i18n.language`.
Validated desktop build readiness for the World Monitor Tauri app by checking frontend compilation, TypeScript integrity, and Tauri/Rust build execution.
## Preflight checks before desktop validation
@@ -26,7 +25,6 @@ If any of these checks fail, treat downstream desktop build failures as environm
5. `cargo check` (from `src-tauri/`) — failed because the environment blocks downloading crates from `https://index.crates.io` (`403 CONNECT tunnel failed`).
## Assessment
- The web app portion compiles successfully.
- Full Tauri desktop validation in this run is blocked by an **external environment outage/restriction** (registry access denied with HTTP 403).
- No code/runtime defects were observed in project sources during this validation pass.
@@ -44,7 +42,6 @@ Use these labels in future reports so outcomes are actionable:
- Action: provision offline artifacts/mirror config first, enable offline override (`config.local.toml` or CLI `--config`), then rerun.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.