Commit Graph

151 Commits

Author SHA1 Message Date
Elie Habib
442fb46a5d fix(sidecar): parallelize llm-health provider probes (#1564) 2026-03-14 12:59:03 +04:00
Jon Torrez
987ed03f5d feat(webcams): add webcam map layer with Windy API integration (#1540) (#1540)
- Webcam markers on flat, globe, and DeckGL maps with category-based icons
- Server-side spatial queries via Redis GEOSEARCH with quantized bbox caching
- Pinned webcams panel with localStorage persistence
- Seed script for Windy API with regional bounding boxes and adaptive splitting
- Input validation (webcamId regex + encodeURIComponent) and NaN projection guards
- Bandwidth optimizations: zoom threshold, bbox overlap check, 1s cooldown
- Client-side image cache with 200-entry FIFO eviction
- Globe altitude-based viewport estimation for webcam loading
- CSP updates for webcam iframe sources
- Seed-meta key for health.js freshness tracking
2026-03-14 09:34:54 +04:00
Jon Torrez
d9db5ab6c2 fix: move LLM health gate inside caching callbacks to prevent null-caching (#1522)
Moves isProviderAvailable() check from before cachedFetchJson() to inside
the fetcher callback. This ensures cache hits still serve valid data during
provider outages instead of returning empty results.

Changes:
- classify-event: health gate moved inside cachedFetchJson callback
- deduct-situation: same
- get-country-intel-brief: same
- summarize-article: same
- _batch-classify: break → return results on health gate failure
- callLlm (llm.ts): health gate added to provider chain
- local-api-server: /api/llm-health endpoint + startup warmup

Scope cleanup per review:
- Reverted LlmStatusIndicator (extracted to #1528)
- Reverted ACLED credential cleanup (extracted to #1530)
- Reverted isSidecar → isLocalDeployment rename (extracted to #1532)

Co-authored-by: Elie Habib <elie.habib@gmail.com>
2026-03-14 08:25:12 +04:00
Elie Habib
15121f2092 chore: remove ACLED_EMAIL/ACLED_PASSWORD credential validation (#1530)
ACLED migrated to token-based auth (ACLED_ACCESS_TOKEN). The email/password
OAuth flow is no longer used. Remove the dead validation cases and drop
both keys from ALLOWED_ENV_KEYS.

Extracted from PR #1522 (scope split).

Co-authored-by: Jon Torrez <jrtorrez31337@users.noreply.github.com>
2026-03-13 20:26:03 +04:00
Nicolas Dos Santos
59cd313e16 fix(csp): add commodity variant to CSP and fix iframe variant navigation (#1506)
* fix(csp): add commodity variant to CSP and fix iframe variant navigation

- Add commodity.worldmonitor.app to frame-src and frame-ancestors in
  vercel.json and index.html CSP — was missing while all other variants
  were listed
- Open variant links in new tab when app runs inside an iframe to prevent
  sandbox navigation errors ("This content is blocked")
- Add allow-popups and allow-popups-to-escape-sandbox to pro page iframe
  sandbox attribute

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(csp): add missing variant subdomains to tauri.conf.json frame-src

Sync tauri.conf.json CSP with index.html and vercel.json by adding
finance, commodity, and happy worldmonitor.app subdomains to frame-src.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add PR screenshots for CSP fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Elie Habib <elie.habib@gmail.com>
2026-03-13 01:12:27 +04:00
Fayez Bast
36d0954720 feat(cache): key market quote breakers by symbol set (#1379)
* feat(cache): key market quote breakers by symbol set

* feat(cache): key market quote breakers by symbol set

* feat(cache): key market quote breakers by symbol set

---------

Co-authored-by: Elie Habib <elie.habib@gmail.com>
2026-03-13 00:21:08 +04:00
RepairYourTech
0420a54866 fix(acled): add OAuth token manager with automatic refresh (#1437)
* fix(acled): add OAuth token manager with automatic refresh

ACLED access tokens expire every 24 hours, but WorldMonitor stores a
static ACLED_ACCESS_TOKEN with no refresh logic — causing all ACLED
API calls to fail after the first day.

This commit adds `acled-auth.ts`, an OAuth token manager that:
- Exchanges ACLED_EMAIL + ACLED_PASSWORD for an access token (24h)
  and refresh token (14d) via the official ACLED OAuth endpoint
- Caches tokens in memory and auto-refreshes before expiry
- Falls back to static ACLED_ACCESS_TOKEN for backward compatibility
- Deduplicates concurrent refresh attempts
- Degrades gracefully when no credentials are configured

The only change to the existing `acled.ts` is replacing the synchronous
`process.env.ACLED_ACCESS_TOKEN` read with an async call to the new
`getAcledAccessToken()` helper.

Fixes #1283
Relates to #290

* fix: address review feedback on ACLED OAuth PR

- Use Redis (Upstash) as L2 token cache to survive Vercel Edge cold starts
  (in-memory cache retained as fast-path L1)
- Add CHROME_UA User-Agent header on OAuth token exchange and refresh
- Update seed script to use OAuth flow via getAcledToken() helper
  instead of raw process.env.ACLED_ACCESS_TOKEN
- Add security comment to .env.example about plaintext password trade-offs
- Sidecar ACLED_ACCESS_TOKEN case is a validation probe (tests user-provided
  value, not process.env) — data fetching delegates to handler modules

* feat(sidecar): add ACLED_EMAIL/ACLED_PASSWORD to env allowlist and validation

- Add ACLED_EMAIL and ACLED_PASSWORD to ALLOWED_ENV_KEYS set
- Add ACLED_EMAIL validation case (store-only, verified with password)
- Add ACLED_PASSWORD validation case with OAuth token exchange via
  acleddata.com/api/acled/user/login
- On successful login, store obtained OAuth token in ACLED_ACCESS_TOKEN
- Follows existing validation patterns (Cloudflare challenge handling,
  auth failure detection, User-Agent header)

* fix: address remaining review feedback (duplicate OAuth, em dashes, emoji)

- Extract shared ACLED OAuth helper into scripts/shared/acled-oauth.mjs
- Remove ~55 lines of duplicate OAuth logic from seed-unrest-events.mjs,
  now imports getAcledToken from the shared helper
- Replace em dashes with ASCII dashes in acled-auth.ts section comments
- Replace em dash with parentheses in sidecar validation message
- Remove emoji from .env.example security note

Addresses koala73's second review: MEDIUM (duplicate OAuth), LOW (em
dashes), LOW (emoji).

* fix: align sidecar OAuth endpoint, fix L1/L2 cache, cleanup artifacts

- Sidecar: switch from /api/acled/user/login (JSON) to /oauth/token
  (URL-encoded) to match server/_shared/acled-auth.ts exactly
- acled-auth.ts: check L2 Redis when L1 is expired, not only when L1
  is null (fixes stale L1 skipping fresher L2 from another isolate)
- acled-oauth.mjs: remove stray backslash on line 9
- seed-unrest-events.mjs: remove extra blank line at line 13

---------

Co-authored-by: Elie Habib <elie.habib@gmail.com>
Co-authored-by: RepairYourTech <30200484+RepairYourTech@users.noreply.github.com>
2026-03-12 22:24:40 +04:00
Elie Habib
651cd3d08b feat(desktop): sidecar cloud proxy, domain handlers, and panel fixes (#1454)
* feat(desktop): compile domain handlers + add in-memory sidecar cache

The sidecar was broken for all 23 sebuf/RPC domain routes because
the build script (build-sidecar-handlers.mjs) never existed on main
while package.json already referenced it. This adds the missing script
and an in-memory TTL+LRU cache so the sidecar doesn't need Upstash Redis.

- Add scripts/build-sidecar-handlers.mjs (esbuild multi-entry, 23 domains)
- Add server/_shared/sidecar-cache.ts (500 entries, 50MB max, lazy sweep)
- Modify redis.ts getCachedJson/setCachedJson to use dynamic import for
  sidecar cache when LOCAL_API_MODE=tauri-sidecar (zero cost on Vercel Edge)
- Update tauri.conf.json beforeDevCommand to compile handlers
- Add gitignore pattern for compiled api/*/v1/[rpc].js

* fix(desktop): gate premium panel fetches and open footer links in browser

Skip oref-sirens and telegram-intel HTTP requests on desktop when
WORLDMONITOR_API_KEY is not present. Use absolute URLs for footer
links on desktop so the Tauri external link handler opens them in
the system browser instead of navigating within the webview.

* fix(desktop): cloud proxy, bootstrap timeouts, and panel data fixes

- Set Origin header on cloud proxy requests (fixes 401 from API key validator)
- Strip If-None-Match/If-Modified-Since headers (fixes stale 304 responses)
- Add cloud-preferred routing for market/economic/news/infrastructure/research
- Enable cloud fallback via LOCAL_API_CLOUD_FALLBACK env var in main.rs
- Increase bootstrap timeouts on desktop (8s/12s vs 3s/5s) for sidecar proxy hops
- Force per-feed RSS fallback on desktop (server digest has fewer categories)
- Add finance feeds to commodity variant (client + server)
- Remove desktop diagnostics from ServiceStatusPanel (show cloud statuses only)
- Restore DeductionPanel CSS from PR #1162
- Deduplicate repeated sidecar error logs
2026-03-12 06:50:30 +04:00
Elie Habib
f26c1b3016 chore: bump version to 2.6.1 with changelog (#1410)
Release 2.6.1 covering blog platform, country intelligence,
satellite imagery overhaul, and numerous fixes since 2.6.0.
2026-03-11 10:51:10 +04:00
Elie Habib
47c337014d revert: use youtube.com embeds and remove sandbox to fix bot-check (#1361)
Reverts commit 04af5ea8 which switched web webcam embeds back to
youtube-nocookie.com and restored sandbox. The nocookie domain triggers
YouTube's "Sign in to confirm you're not a bot" prompt, breaking all
live webcam feeds on the web app.

Changes:
- Web embeds: youtube-nocookie.com -> youtube.com (sends session cookies)
- Remove iframe sandbox attribute (allows storage-access to work)
- Add storage-access to iframe allow attribute
- Sidecar: restore autoplay-based MutationObserver gate
2026-03-10 07:39:55 +04:00
Jon Torrez
8bd4ab1cbf fix: resolve YouTube 'sign in to confirm' bot-check in embed panels (#1284)
* fix: resolve YouTube 'sign in to confirm' bot-check in embed panels

YouTube was showing a bot-verification prompt in the LiveWebcamsPanel
and LiveNewsPanel despite the user being logged into YouTube in the
same browser session.

LiveWebcamsPanel (primary fix):
- Changed embed domain from youtube-nocookie.com to youtube.com.
  The nocookie domain deliberately strips all cookies, so YouTube
  can never verify a signed-in session.
- Removed sandbox attribute which blocked the Storage Access API
  (allow-storage-access-by-user-activation was missing).
- Added storage-access to iframe allow attribute.

LiveNewsPanel:
- renderDesktopEmbed now passes origin and parentOrigin query params
  so postMessage is not silently dropped by the embed.
- Added storage-access to iframe allow attribute.
- Fixed MutationObserver target: was watching this.playerElement but
  YT.Player(domElement) replaces that div in its parent, so the
  observer never fired. Now observes playerContainer with a YouTube
  iframe filter, and YT.Player receives the element ID string so the
  iframe is inserted as a child of the div instead.

local-api-server.mjs (youtube-embed handler):
- MutationObserver patches inner YouTube iframe with storage-access.
- Added Permissions-Policy: storage-access=* response header.
- Embed page calls document.requestStorageAccess() on load.

api/youtube/embed.js (Vercel/edge path):
- Added tauri://localhost to ALLOWED_PARENT_ORIGINS.
- Added Permissions-Policy: storage-access=* response header.
- Embed page calls document.requestStorageAccess() on load.

* fix(pr-review): address review feedback on YouTube Storage Access API changes

- LiveWebcamsPanel: tested allow-storage-access-by-user-activation sandbox token
  as suggested; reverted — Chrome silently blocks Storage Access API even with
  the token present. Documented why sandbox removal is the only working approach.
- LiveWebcamsPanel: added comment documenting youtube-nocookie→youtube.com
  privacy trade-off as intentional.
- LiveNewsPanel: wrap YT.Player constructor in try/catch to disconnect
  storageObserver on error; add 10 s auto-disconnect timeout to prevent leaks.
- embed.js + local-api-server.mjs: scope permissions-policy storage-access to
  self + youtube.com rather than *.
- embed.js + local-api-server.mjs: add gesture-gated requestStorageAccess()
  fallback on first user interaction.
- embed.js: remove duplicate tauri://localhost from ALLOWED_PARENT_ORIGINS
  (already covered via ALLOWED_ORIGINS spread).

* fix(review): gate sidecar patch on storage-access, revert web webcam path

1. Sidecar MutationObserver: gate iframe patch on storage-access absence
   instead of autoplay absence. If YouTube ships iframes with autoplay
   already present, the old check would skip adding storage-access entirely.

2. Web webcam path: revert to youtube-nocookie.com and restore sandbox.
   The raw YouTube iframe cannot call requestStorageAccess() (no controlled
   bridge document), so switching to youtube.com only regressed privacy
   and sandbox security without actually fixing the bot-check.

---------

Co-authored-by: Elie Habib <elie.habib@gmail.com>
2026-03-10 07:00:07 +04:00
lspassos1
294984d315 fix(test): update stale origin-header assertion to match current behavior (#1329)
Since e14af08f (#709), the sidecar strips the browser Origin header but
immediately replaces it with `http://127.0.0.1:<port>` (line 1321 of
local-api-server.mjs). This ensures local handlers receive a valid
Origin for CORS while preventing browser-supplied origins from leaking
into server-to-server calls.

The test was written before that commit and still asserted
`originPresent: false`. Update the test to:
  - Assert `originPresent: true` (a localhost origin IS present)
  - Assert `originValue` equals `http://127.0.0.1:<port>` (verify it's
    the replaced localhost origin, not the browser's)
  - Rename the test to describe the actual behavior

npm run test:sidecar: 55/55 pass 
2026-03-09 23:57:49 +04:00
Elie Habib
595e3dbb86 feat: premium finance stock analysis suite (#1268)
* Add premium finance stock analysis suite

* docs: link premium finance from README

Add Premium Stock Analysis entry to the Finance & Markets section
with a link to docs/PREMIUM_FINANCE.md.

* fix: address review feedback on premium finance suite

- Chunk Redis pipelines into batches of 200 (Upstash limit)
- Add try-catch around cachedFetchJson in backtest handler
- Log warnings on Redis pipeline HTTP failures
- Include name in analyze-stock cache key to avoid collisions
- Change analyze-stock and backtest-stock gateway cache to 'slow'
- Add dedup guard for concurrent ledger generation
- Add SerpAPI date pre-filter (tbs=qdr:d/w)
- Extract sanitizeSymbol to shared module
- Extract buildEmptyAnalysisResponse helper
- Fix RSI to use Wilder's smoothing (matches TradingView)
- Add console.warn for daily brief summarization errors
- Fall back to stale data in loadStockBacktest on error
- Make daily-market-brief premium on all platforms
- Use word boundaries for short token headline matching
- Add stock-analysis 15-min refresh interval
- Stagger stock-analysis and backtest requests (200ms)
- Rename signalTone to stockSignalTone
2026-03-08 22:54:40 +04:00
Elie Habib
9129a3bbe3 chore: bump version to 2.6.0 (#1282)
* chore: bump version to 2.6.0

* fix: non-null assertion for SearchModal list access
2026-03-08 22:00:31 +04:00
Elie Habib
8d83aa02eb fix(economic): guard against undefined BIS and spending data (#1162)
* feat: premium panel gating, code cleanup, and backend simplifications

Recovered stranded changes from fix/desktop-premium-error-unification.

Premium gating:
- Add premium field ('locked'|'enhanced') to PanelConfig and LayerDefinition
- Panel.showLocked() with lock icon, CTA button, and _locked guard
- PRO badge for enhanced panels when no WM API key
- Exponential backoff auto-retry on showError() (15s→30s→60s→180s cap)
- Gate oref-sirens and telegram-intel panels behind WM API key
- Lock gpsJamming and iranAttacks layer toggles, badge ciiChoropleth
- Add tauri-titlebar drag region for custom titlebar

Code cleanup:
- Extract inline CSS from AirlineIntelPanel, WorldClockPanel to panels.css
- Remove unused showGeoError() from CountryBriefPage
- Remove dead geocodeFailed/retryBtn/closeBtn locale keys (20 files)
- Clean up var names and inline styles across 6 components

Backend:
- Remove seed-meta throttle from redis.ts (unnecessary complexity)
- Risk scores: call handler functions directly instead of raw Redis reads
- Update OpenRouter model to gpt-oss-safeguard-20b:nitro
- Add direct UCDP API fetching with version probing

Config:
- Remove titleBarStyle: Overlay from tauri.conf.json
- Add build:pro and build-sidecar-handlers to build:desktop
- Remove DXB/RUH from default aviation watchlist
- Simplify reverse-geocode (remove AbortController wrapper)

* fix: cast handler requests to any for API tsconfig compat

* fix: revert stale changes that conflict with merged PRs

Reverts files to main versions where old branch changes would
overwrite intentional fixes from PRs #1134, #1138, #1144, #1154:

- news/_shared.ts: keep gemini-2.5-flash model (not stale gpt-oss)
- redis.ts: keep seed-meta throttle from PR #1138
- reverse-geocode.ts: keep AbortController timeout from PR #1134
- CountryBriefPage.ts: keep showGeoError() from PR #1134
- country-intel.ts: keep showGeoError usage from PR #1134
- get-risk-scores.ts: revert non-existent imports
- watchlist.ts: keep DXB/RUH airports from PR #1144
- locales: restore geocodeFailed/retryBtn/closeBtn keys

* fix: neutralize language, parallel override loading, fetch timeout

- Rename conflict zone from "War" to "Border Conflict", intensity high→medium
- Rewrite description to factual language (no "open war" claim)
- Load country boundary overrides in parallel with main GeoJSON
- Neutralize comments/docs: reference Natural Earth source, remove political terms
- Add 60s timeout to Natural Earth fetch script (~24MB download)
- Add trailing newline to GeoJSON override file

* fix: restore caller messages in Panel errors and vessel expansion in popups

- Move UCDP direct-fetch cooldown after successful fetch to avoid
  suppressing all data for 10 minutes on a single failure
- Use caller-provided messages in showError/showRetrying instead of
  discarding them; respect autoRetrySeconds parameter
- Restore cluster-toggle click handler and expandable vessel list
  in military cluster popups
2026-03-07 09:43:27 +04:00
Elie Habib
739333aa80 docs: expand AGPL-3.0 license section in README (#1143)
* fix(desktop): settings UI redesign, IPC security hardening, release profile

Settings window:
- Add titlebar drag region (macOS traffic light clearance)
- Move Export/Import from Overview to Debug & Logs section
- Category cards grid changed to 3-column layout

Security (IPC trust boundary):
- Add require_trusted_window() to get_desktop_runtime_info, open_url,
  open_live_channels_window_command, open_youtube_login
- Validate base_url in open_live_channels_window_command (localhost-only http)

Performance:
- Add [profile.release] with fat LTO, codegen-units=1, strip, panic=abort
- Reuse reqwest::Client via app state with connection pooling
- Debounce window resize handler (150ms) in EventHandlerManager

* docs: expand AGPL-3.0 license section in README

Add plain-language explanation of AGPL-3.0 rights and obligations
including attribution requirements, copyleft conditions, network
use clause (SaaS must share source), and a use-case reference table.
2026-03-06 23:47:04 +04:00
Elie Habib
6ccda09246 fix(sidecar): upstream concurrency limiter, Yahoo rate gate, startup batching (#1145)
- Sidecar: add global concurrency limiter (max 6 concurrent upstream requests)
- Sidecar: add Yahoo Finance rate gate (600ms spacing) in fetch patch
- Sidecar: fix default remoteBase to api.worldmonitor.app
- data-loader: stagger startup tasks in batches of 4 with 300ms delay
- get-country-stock-index: add yahooGate() before Yahoo fetch
- tauri.conf: add titleBarStyle Overlay
2026-03-06 23:45:23 +04:00
Elie Habib
426994e343 fix(desktop): DRY debounce, error handling, retry cap (review follow-up) (#1084)
* fix(desktop): address code review findings — DRY debounce, error handling, retry cap

- Extract duplicated flush-scheduling into schedule_debounced_flush() helper
- Drop flush_scheduled lock before spawning thread to narrow lock scope
- Add .catch() to lazyPanel() for silent import failure visibility
- Convert happy-variant panels to use lazyPanel() helper (consistency + error handling)
- Cap flush retries at 5 to prevent infinite loop on persistent disk errors
- Only clear sidecar caches when at least one batch entry succeeded
- Log batch fallback error for debugging

* fix: remove unsafe type casts in happy-variant lazy panels

Move ctx property assignments into the loader callback where the
concrete type is known, eliminating all `as unknown as` double casts.
2026-03-06 01:57:50 +04:00
Elie Habib
e3afcd45b4 perf(desktop): debounce cache writes, batch secret push, lazy panels, pause hidden polls (#1077)
- Rust PersistentCache: generation-counter debounce (2s coalesce) + atomic
  flush via temp file + rename to prevent corruption on crash
- Sidecar: add /api/local-env-update-batch endpoint; loadDesktopSecrets()
  now pushes all secrets in 1 request instead of 23, with single-endpoint
  fallback for older sidecars
- App startup: waitForSidecarReady() polls service-status before bootstrap
  fetch so sidecar port-file races no longer cause silent fallback
- Lazy panel instantiation: 16 niche/variant panels converted to dynamic
  import().then() — disabled panels cost zero at cold boot
- pauseWhenHidden: true on RefreshScheduler, OREF alerts, and Gulf
  Economies poll loops — zero background network when app is hidden
2026-03-06 00:48:31 +04:00
Elie Habib
29ef8eae2f docs: update README with accurate counts and 9 new feature sections (#1071)
- Fix stale counts: 170+ feeds → 435+, 15 bootstrap keys → 38,
  28+ data sources → 31, 20+ search types → 24, panel counts
- Add Aviation Intelligence Panel documentation
- Add Customizable Market Watchlist section
- Add News Importance Scoring algorithm details
- Add Railway Seed Data Pipeline table (21 cron jobs)
- Add SmartPollLoop adaptive polling documentation
- Expand Prediction Markets with 4-tier fetch strategy
- Add Iran conflict monitoring layer details
- Add Mobile search sheet and FAB section
- Expand Regression Testing section (30 files, 554 tests)
- Expand Bootstrap Hydration with full 38-key tier listing
- Bump version 2.5.24 → 2.5.25
2026-03-05 23:40:37 +04:00
Elie Habib
fccfa79a29 fix: remove emrldco analytics and improve basemap fallback reliability (#1052)
- Remove emrldco.com analytics script and CSP entries from index.html,
  vercel.json, and tauri.conf.json
- Replace setStyle() basemap fallback with full map recreation — setStyle()
  after a failed initial style load leaves MapLibre in a broken state
- Add 403/Forbidden to error detection patterns for basemap failures
- Scope fallback to pre-style-load errors only (post-load tile errors
  don't warrant destroying a working map)
2026-03-05 12:39:22 +04:00
Elie Habib
e771c3c6e0 feat: add emrldco analytics script with lazy loading (#1000)
Loads the script on window.load event so it never blocks initial render.
CSP updated in both index.html and tauri.conf.json.
2026-03-04 20:33:11 +04:00
Dharun Ashokkumar
dc30db9ce9 fix: desktop youtube cloud fallback via sidecar hardcoded route (#917)
Fixes #903. YouTube live detection from the desktop sidecar now proxies directly to cloud via tryCloudFallback(), bypassing the cloudFallback flag (off by default). Matches the existing register-interest pattern.
2026-03-04 08:46:04 +04:00
Narvis Bot
5579e6bc5d fix: use 'cmd /c start' instead of 'explorer' to open URLs on Windows (#741)
explorer.exe treats URLs as file paths, opening a file dialog.
'cmd /c start' properly delegates URLs to the default browser.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:17:40 -08:00
Elie Habib
d1318781ff docs(readme): update stats, add 8 new sections, bump to v2.5.24 (#874)
Correct all stale numbers to match current codebase:
- Languages: 16 → 19 (added Czech, Greek, Korean)
- RSS feeds: 150+ → 170+, live channel pool: 30+ → 70+
- Airports: 128 → 107, AviationStack: 114 → 40
- Hotspots: 74 → 217, proto domains: 20 → 22
- Telegram: 27 → 26, OREF locations: 1,478 → 1,480
- Panel counts: 45/31/31/8 → 47/35/33/10

Add 8 new documentation sections:
- Bootstrap Hydration (2-tier parallel pre-fetch)
- Breaking News Alert Pipeline (5 origins)
- Cross-Stream Correlation Engine (14 signal types)
- Adaptive Refresh Scheduling (backoff, jitter, throttle)
- Localization Architecture (bundles, boost, RTL, fonts)
- Intelligence Analysis Tradecraft (SATs, ACH, gap awareness)
- Client-Side Circuit Breakers (IndexedDB persistence)
- Programmatic API Access (api.worldmonitor.app)

Expand Happy Monitor with humanity counters, conservation,
renewables, and giving detail. Add negative caching docs.
Bump version 2.5.23 → 2.5.24.
2026-03-03 08:36:49 +04:00
Nicolas Gomes Ferreira Dos Santos
ba95f62477 fix(sidecar): add required params to ACLED API key validation probe (#804)
* fix(sidecar): add required params to ACLED API key validation probe

The validation endpoint was calling ACLED without event_type, event_date,
or event_date_where parameters. The production code in acled.ts always
passes these — ACLED may reject requests missing them, causing valid
tokens to fail validation.

Add Protests event type and a 7-day date range to match production usage.

Fixes #290.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(military): harden USNI fleet report ship name regex

The ship extraction regex only matched <em> and <i> tags. If USNI
changes HTML to use <strong>, <b>, <span>, or plain text, all ship
parsing silently fails.

Broaden the regex to handle any inline HTML tag or no tag at all.
Add console.warn when a strike group section yields zero ships to
aid debugging when HTML format changes.

Addresses #197 (L-12).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 00:58:24 +04:00
Elie Habib
b423995363 feat(conflict): wire UCDP (#760)
* feat(conflict): wire UCDP API access token across full stack

UCDP API now requires an `x-ucdp-access-token` header. Renames the
stub `UC_DP_KEY` to `UCDP_ACCESS_TOKEN` (matching ACLED convention)
and wires it through Rust keychain, sidecar allowlist + verification,
handler fetch headers, feature toggles, and desktop settings UI.

- Rename UC_DP_KEY → UCDP_ACCESS_TOKEN in type system and labels
- Add ucdpConflicts feature toggle with required secret
- Add UCDP_ACCESS_TOKEN to Rust SUPPORTED_SECRET_KEYS (24→25)
- Add sidecar ALLOWED_ENV_KEYS entry + validation with dynamic GED version probing
- Handler sends x-ucdp-access-token header when token is present
- UC_DP_KEY fallback in handler for one-release migration window
- Update .env.example, desktop-readiness, and docs

* feat(conflict): pre-fetch UCDP events via Railway cron + Redis cache

Replace the 228-line edge handler that fetched UCDP GED API on every
request with a thin Redis reader. The heavy fetch logic (version
discovery, paginated backward fetch, 1-year trailing window filter)
now runs as a setInterval loop in the Railway relay (ais-relay.cjs)
every 6 hours, writing to Redis key conflict:ucdp-events:v1.

Changes:
- Add UCDP seed loop to ais-relay.cjs (6h interval, 6 pages, 2K cap)
- Rewrite list-ucdp-events.ts as thin Redis reader (35 lines)
- Add conflict:ucdp-events:v1 to bootstrap batch keys
- Protect key from cache-purge via durable data prefix
- Add manual-only seed-ucdp-events workflow + standalone script
- Rename panel "UCDP Events" → "Armed Conflict Events" in locale
- Add 24h TTL + 25h staleness check as safety nets
2026-03-02 16:17:17 +04:00
Nicolas Gomes Ferreira Dos Santos
d90c845621 fix(market): move Finnhub API key from query string to X-Finnhub-Token header (#744)
API keys in URL query strings can leak via server logs, proxy logs,
Referer headers, and error reporting tools. Finnhub supports both
authentication methods — this moves to the header-based approach.

Addresses #197 (L-16).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 12:37:38 +04:00
Elie Habib
8a414228b4 fix: harden windows installer update path and map resize behavior (#739) 2026-03-02 11:33:24 +04:00
Elie Habib
aa94b0fd5e fix(csp): allow localhost in media-src for proxied HLS & remove CNN HLS (#711)
CSP media-src only allowed https: — blocked <video> from loading HLS
streams through the sidecar proxy at http://127.0.0.1:PORT. Direct HLS
channels (Sky, DW, Fox) use https:// CDN URLs and worked; proxied
channels (CNBC, CNN) were silently blocked, falling back to YouTube.

Also remove CNN from PROXIED_HLS_MAP — the upstream stream is wrong.
2026-03-02 03:02:49 +04:00
Elie Habib
e14af08f2d fix(desktop): resolve sidecar 401s, variant lock, and registration form (#v2.5.23) (#709)
- Sidecar 401 fix: inject trusted localhost Origin on requests passed to
  handler modules. The handler's validateApiKey() was seeing empty Origin
  (stripped by toHeaders) + no API key → 401 for ALL desktop API calls.
- Variant fix: check localStorage FIRST when running in Tauri desktop,
  so .env.local VITE_VARIANT doesn't override user's variant selection.
- Registration: force-show form for email delivery testing.
- Bump version to 2.5.23.
2026-03-02 02:08:57 +04:00
Elie Habib
6adfda8061 chore: bump version to 2.5.22 & comprehensive README update (#706)
Bump version 2.5.21 → 2.5.22 across package.json, Cargo.toml, and
tauri.conf.json.

README: document 15+ recently shipped features that were missing from
the README — AI Deduction panel, Headline Memory (RAG), server-side
feed aggregation, Gulf Economies panel, TV Mode, mobile map with touch
gestures, fullscreen live video, 18+ HLS channels, breaking news
click-through, badge animation toggle, cache purge admin endpoint,
locale-aware feed boost, OREF Redis persistence + 1,478 Hebrew→English
translations, and Oceania region tab. Update PostHog → Vercel Analytics.
Add 21 new completed roadmap items.
2026-03-02 01:24:20 +04:00
Elie Habib
078a239ceb feat(live-news): add CNN & CNBC HLS streams via sidecar proxy (#682)
* feat(live-news): add CNN & CNBC HLS streams via sidecar proxy (desktop only)

Add /api/hls-proxy route to sidecar that proxies HLS manifests and
segments from allowlisted CDN hosts, injecting the required Referer
header that browsers cannot set. Rewrites m3u8 URLs so all segments
and encryption keys also route through the proxy.

Desktop gets native <video> HLS playback for CNN and CNBC; web falls
through to YouTube as before (no bandwidth cost on Vercel).

* fix(types): add missing @types/dompurify dev dependency
2026-03-01 21:06:18 +04:00
Elie Habib
36e36d8b57 Cost/traffic hardening, runtime fallback controls, and PostHog removal (#638)
- Remove PostHog analytics runtime and configuration
- Add API rate limiting (api/_rate-limit.js)
- Harden traffic controls across edge functions
- Add runtime fallback controls and data-loader improvements
- Add military base data scripts (fetch-mirta-bases, fetch-osm-bases)
- Gitignore large raw data files
- Settings playground prototypes
2026-03-01 11:53:20 +04:00
Elie Habib
cac2a4f5af fix(desktop): route register-interest to cloud when sidecar lacks CONVEX_URL (#639)
* fix(desktop): route register-interest to cloud when sidecar lacks CONVEX_URL

The waitlist registration endpoint needs Convex (cloud-only dependency).
The sidecar handler returned 503 without cloud fallback, and
getRemoteApiBaseUrl() returned '' on desktop (VITE_WS_API_URL unset),
so the settings window fetch resolved to tauri://localhost → 404.

Three-layer fix:
1. Sidecar: tryCloudFallback() when CONVEX_URL missing (proxies to
   https://worldmonitor.app via remoteBase)
2. runtime.ts: getRemoteApiBaseUrl() defaults to https://worldmonitor.app
   on desktop when VITE_WS_API_URL is unset
3. CI: add VITE_WS_API_URL=https://worldmonitor.app to all 4 desktop
   build steps

* chore(deps): bump posthog-js to fix pre-push typecheck
2026-03-01 11:46:31 +04:00
Elie Habib
d0a2a50506 fix(desktop): backoff on errors to stop CPU abuse + shrink settings window (#633)
Three bugs combine to burn 130% CPU when sidecar auth fails:

1. RefreshScheduler resets backoff multiplier to 1 (fastest) on error,
   causing failed endpoints to poll at base interval instead of backing off.
   Fix: exponential backoff on errors, same as unchanged-data path.

2. classify-event batch system ignores 401 (auth failure) — only pauses
   on 429/5xx. Hundreds of classify calls fire every 2s, each wasted.
   Fix: pause 120s on 401, matching the 429/5xx pattern.

3. Fetch patch retries every 401 (refresh token + retry), doubling all
   requests to the sidecar even when token refresh consistently fails.
   Fix: 60s cooldown after a retry-401 still returns 401.

Also shrinks settings window from 760→600px (min 620→480) to reduce
the empty whitespace below content on all tabs.
2026-03-01 10:53:54 +04:00
Fayez Bast
eb1d596c0a fix(linux): sanitize env for xdg-open in AppImage (#631) 2026-03-01 10:27:20 +04:00
Elie Habib
8a9aa2b254 fix(sidecar): add AVIATIONSTACK_API and ICAO_API_KEY to env allowlist (#632)
Both keys were added to Rust SUPPORTED_SECRET_KEYS and runtime-config.ts
but the sidecar's own ALLOWED_ENV_KEYS was never updated. This caused
"key not in allowlist" 403 when saving/verifying these keys from the
desktop settings UI.

Also adds AviationStack API validation in validateSecretAgainstProvider.
2026-03-01 10:23:37 +04:00
Elie Habib
5bb3696f7a chore: bump version to 2.5.21 (#605) 2026-03-01 02:40:41 +04:00
Elie Habib
9ad9117689 feat(aviation): add NOTAM closure detection via ICAO API (#583)
* feat(aviation): add NOTAM closure detection via ICAO API

Adds international airport closure detection via ICAO NOTAMs:
- New fetchNotamClosures() queries ICAO realtime-notams endpoint
- Detects closures via Q-codes (FA/AH/AL/AW/AC/AM) and text patterns
- Batches airports in groups of 20 per API call
- 4-hour cache TTL via cachedFetchJson (stampede-safe)
- NOTAM closures override existing AviationStack alerts for same airport
- Graceful: no ICAO_API_KEY env var = silently skipped

To activate: set ICAO_API_KEY env var (register at dataservices.icao.int)

* feat(settings): add ICAO_API_KEY to desktop app settings

Adds ICAO NOTAM API key to the desktop settings UI:
- Rust: SUPPORTED_SECRET_KEYS [23→24]
- TypeScript: RuntimeSecretKey + RuntimeFeatureId unions
- Feature definition: 'icaoNotams' in Tracking & Sensing category
- Settings UI: label, signup URL, analytics name

* feat(aviation): limit NOTAM queries to MENA airports only

Per user request, ICAO NOTAM closure detection is scoped to
Middle East airports only (region='mena', ~35 airports).
This reduces API calls (2 batches vs 5) and focuses on the
region where closures are most relevant.

* fix(aviation): align NOTAM cache TTL to 30 min (matching FAA/intl)
2026-02-28 23:25:41 +04:00
Elie Habib
74dc8545a8 feat(settings): add AVIATIONSTACK_API to desktop settings page (#553)
Register the key in Rust keychain (SUPPORTED_SECRET_KEYS), frontend
RuntimeSecretKey type, feature toggle, and settings UI under
"Tracking & Sensing" category.
2026-02-28 19:41:47 +04:00
Elie Habib
b4638b281e chore: bump version to 2.5.20 + changelog
Covers PRs #452–#484: Cloudflare edge caching, commodities SWR fix,
security advisories panel, settings redesign, 52 POST→GET migrations.
2026-02-28 00:40:04 +04:00
Elie Habib
84e39ba4b1 fix(desktop): enable click-to-play YouTube embeds + CISA feed fixes (#476)
* fix(tech): use rss() for CISA feed, drop build from pre-push hook

- CISA Advisories used dead rss.worldmonitor.app domain (404), switch to rss() helper
- Remove Vite build from pre-push hook (tsc already catches errors)

* fix(desktop): enable click-to-play for YouTube embeds in WKWebView

WKWebView blocks programmatic autoplay in cross-origin iframes regardless
of allow attributes, Permissions-Policy, mute-first retries, or secure
context. Documented all 10 approaches tested in docs/internal/.

Changes:
- Switch sidecar embed origin from 127.0.0.1 to localhost (secure context)
- Add MutationObserver + retry chain as best-effort autoplay attempts
- Use postMessage('*') to fix tauri://localhost cross-origin messaging
- Make sidecar play overlay non-interactive (pointer-events:none)
- Fix .webcam-iframe pointer-events:none blocking clicks in grid view
- Add expand button to grid cells for switching to single view on desktop
- Add http://localhost:* to CSP frame-src in index.html and tauri.conf.json
2026-02-27 22:02:06 +04:00
Elie Habib
3bd501c4ce fix+feat: RSS feed repairs, HLS native playback, summarization cache fix, embed improvements (#452)
* fix(feeds): replace 25 dead/stale RSS URLs and add feed validation script

- Replace 16 dead feeds (404/403/timeout) with working alternatives
  (Google News proxies or corrected direct RSS endpoints)
- Replace 6 empty feeds with correct RSS paths (VnExpress, Tuoi Tre,
  Live Science, Greater Good, News24, ScienceDaily)
- Replace 3 stale feeds (CNN World, TVN24, Layoffs.fyi) with active sources
- Remove Disrupt Africa (inactive since Jan 2024)
- Add scripts/validate-rss-feeds.mjs to check all 420 feeds
- Add test:feeds npm script

* feat(live-news): use stable CDN HLS feeds for desktop native playback

Direct HLS feeds bypass YouTube's expiring tokenized URLs and iframe
cookie issues on WKWebView. 10 channels (Sky, DW, France24, Euronews,
Al Arabiya, Al Jazeera, CBS News, TRT World, Sky News Arabia, Al Hadath)
now play via native <video> on desktop with automatic YouTube fallback
when CDN feeds are down (5-min cooldown).

Also:
- Fix euronews handle typo (@euabortnews → @euronews)
- Fix TRT World handle (@taborrtworld → @TRTWorld)
- Add fallbackVideoId to CBS News, Sky News Arabia, TRT World
- Extract hlsManifestUrl from YouTube API for non-mapped channels
- Add sidecar /api/youtube-embed endpoint (auth-exempt for iframes)
- Switch webcam/embed iframes from cloud to local sidecar origin
- CSP: allow frame-src http://127.0.0.1:* for sidecar embeds
- Remove legacy WEBKIT_FORCE_SANDBOX env var (deprecated in WebKitGTK)
- Add 37 tests covering HLS map integrity, decision tree ordering,
  cooldown logic, race safety, service layer, sidecar endpoint, and CSP

* fix(summarization): pass panelId as geoContext to prevent Redis cache key collision

When breaking news appeared across multiple panels (World, US, Europe,
Middle East), all panels generated identical cache keys because geoContext
was always undefined. The first panel's summary was served to all others.

* fix(desktop): sidecar embed autoplay, webcam fullscreen, optional channel fallbacks

- Sidecar YouTube embed: use mute param (not hardcoded), add play overlay
  for WKWebView autoplay fallback, add postMessage bridge for play/pause/
  mute/unmute commands matching the cloud embed handler
- Webcam iframes: only set allowFullscreen on web to prevent grid-breaking
  fullscreen on desktop click
- Optional channels: add fallbackVideoId + useFallbackOnly for livenow-fox,
  abc-news, nbc-news, wion so they play instead of showing "not currently live"
- Tests: 9 new assertions covering mute param, postMessage bridge, play
  overlay, yt-ready message, and optional channel fallback coverage (46 total)
2026-02-27 13:55:06 +04:00
Elie Habib
f066c7c34c fix(linux): detect NVIDIA GPU and work around EGL_BAD_ALLOC on Wayland (#446)
Linux users with NVIDIA proprietary drivers on Wayland report crashes:
  "Could not create surfaceless EGL display: EGL_BAD_ALLOC. Aborting..."

WebKitGTK's web process calls eglGetPlatformDisplay with the
EGL_PLATFORM_SURFACELESS_MESA platform, which fails with NVIDIA's EGL
implementation and triggers abort(). WEBKIT_DISABLE_DMABUF_RENDERER=1
(already set) only controls buffer sharing, not EGL initialization.

Detect NVIDIA via /proc/driver/nvidia and:
- Set __NV_DISABLE_EXPLICIT_SYNC=1 to prevent Wayland flickering
- Force GDK_BACKEND=x11 on NVIDIA+Wayland (user can override)

Also bumps version to 2.5.19.

Refs: tauri-apps/tauri#9394, gitbutlerapp/gitbutler#5282
2026-02-27 00:20:50 +04:00
Elie Habib
6106f368f7 chore: bump v2.5.18 (#445) 2026-02-26 23:59:42 +04:00
Elie Habib
2fdecae5e5 fix(linux): stop mixing host GStreamer plugins with bundled AppImage plugins (#444)
The AppImage bundles GStreamer from CI (Ubuntu 24.04, GStreamer 1.24).
Previously, host plugin directories (/usr/lib/gstreamer-1.0/) were
appended as fallback. This caused ABI version mismatches — host plugins
compiled against a different GStreamer version fail with undefined symbol
errors (gst_util_floor_log2, mpg123_open_handle64, etc.), leaving WebKit
without usable codecs for YouTube playback.

Since PR #434 installs the full GStreamer codec suite on CI, the AppImage
is fully self-contained. Remove the host fallback and block host plugin
scanning to prevent ABI conflicts across distro GStreamer versions.
2026-02-26 23:56:56 +04:00
Elie Habib
6e15b99830 chore: bump v2.5.17 (#443) 2026-02-26 23:38:12 +04:00
Elie Habib
a660ca7aaa fix(linux): disable WebKitGTK compositing in VMs to fix black iframes (#441)
WebKitGTK promotes iframes, <video>, and canvas elements to GPU-textured
compositing layers. In VMs (Apple Virtualization.framework, QEMU, VMware,
etc.) the virtio-gpu driver often only supports 2D or limited GL, so GBM
buffer allocation for compositing layers fails silently — rendering
iframe/video content as black while the main page works fine.

Detect VM environments via /proc/cpuinfo hypervisor flag and sys_vendor
strings, then set WEBKIT_DISABLE_COMPOSITING_MODE=1 and
LIBGL_ALWAYS_SOFTWARE=1 to force software rendering for all content.
Native Linux machines with real GPUs are unaffected.
2026-02-26 23:30:21 +04:00
Elie Habib
c66e740f30 chore: bump v2.5.16 (#440) 2026-02-26 22:54:41 +04:00