Commit Graph

1738 Commits

Author SHA1 Message Date
Hasan AlDoy
02f3fe77a9 feat: Arabic font support and HLS live streaming UI (#1020)
* feat: enhance support for HLS streams and update font styles

* chore: add .vercelignore to exclude large local build artifacts from Vercel deploys

* chore: include node types in tsconfig to fix server type errors on Vercel build

* fix(middleware): guard optional variant OG lookup to satisfy strict TS

* fix: desktop build and live channels handle null safety

- scripts/build-sidecar-sebuf.mjs: Skip building removed [domain]/v1/[rpc].ts (removed in #785)
- src/live-channels-window.ts: Add optional chaining for handle property to prevent null errors
- src-tauri/Cargo.lock: Bump version to 2.5.24

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: address review issues on PR #1020

- Remove AGENTS.md (project guidelines belong to repo owner)
- Restore tracking script in index.html (accidentally removed)
- Revert tsconfig.json "node" types (leaks Node globals to frontend)
- Add protocol validation to isHlsUrl() (security: block non-http URIs)
- Revert Cargo.lock version bump (release management concern)

* fix: address P2/P3 review findings

- Preserve hlsUrl for HLS-only channels in refreshChannelInfo (was
  incorrectly clearing the stream URL on every refresh cycle)
- Replace deprecated .substr() with .substring()
- Extract duplicated HLS display name logic into getChannelDisplayName()

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Elie Habib <elie.habib@gmail.com>
2026-03-05 10:16:43 +04:00
Elie Habib
f06db59720 fix: graceful degradation for seed scripts with missing keys or downed sources (#1045)
- seed-unrest-events: relax validation (ACLED missing + GDELT 404 = crash),
  console.warn → console.log for non-fatal failures
- seed-natural-events: relax validation, console.error → console.log
- seed-climate-anomalies: relax validation, console.error → console.log
- seed-internet-outages: console.error → console.log for missing key

Railway tags console.warn/error as severity:error, making healthy runs
look like crashes in the dashboard.
2026-03-05 10:09:27 +04:00
Fayez Bast
ca131bb075 feat/localize map(#1017) (#1032)
* feat/localize map(#1017)

* feat :map localization

* fix: address map-localizatio
2026-03-05 09:28:07 +04:00
Elie Habib
383f2e9596 fix: switch basemap fallback from Stadia Maps to OpenFreeMap (#1042)
Stadia Maps tiles return HTTP 401 without an API key — style.json loads
but actual .pbf tile requests fail, leaving the map black with floating
data points (issue #1031).

Changes:
- Switch fallback to OpenFreeMap (free, no auth, CORS *, dark style)
- Replace overly broad 'style.json' error match with 'cartocdn.com'
- Store style load timeout on instance, clear in destroy()
- Update TODO-131 text to reference OpenFreeMap
2026-03-05 09:10:50 +04:00
Elie Habib
9514f52971 fix: harden basemap fallback with timeout and broader CORS error matching (#1039)
CARTO basemap CORS errors weren't triggering the Stadia fallback because
the error message didn't always match 'Failed to fetch' or 'AJAXError'.
Add broader error pattern matching (CORS, NetworkError, style.json) and
a 5-second timeout that switches to fallback if style hasn't loaded.

Also adds TODO-131 for self-hosted Protomaps + CloudFront tiles to
eliminate third-party basemap dependency entirely.
2026-03-05 08:25:22 +04:00
Elie Habib
f4c43f0af6 fix: fallback to Stadia Maps when CARTO basemap fails to load (#1037)
When CARTO CDN is unreachable (CORS error, outage), the map canvas
shows blank. Add automatic fallback to Stadia Maps (alidade_smooth_dark
/ alidade_smooth) on MapLibre error event. Theme switching respects
the fallback state.
2026-03-05 07:55:56 +04:00
Elie Habib
4f291c3469 fix: reduce infrastructure proximity radius and clarify nuclear label (#1038)
Reduce MAX_DISTANCE_KM from 600 to 300 so countries like Lebanon no
longer show distant foreign nuclear facilities (e.g. Dimona at ~320km).
Rename infra label from "Nuclear Facilities" to "Nearby Nuclear" across
all 21 locales to clarify these are proximity-based, not domestic.
2026-03-05 07:55:44 +04:00
Pranav Garg
b793a61c87 fix(api): harden IP extraction, input validation, redirect SSRF check, and origin-pattern parity (#1013)
- register-interest.js: coerce source/appVersion to string with a 100-char cap
  before forwarding to Convex. Non-string values (objects, arrays) are truthy so
  the previous || 'unknown' guard passed them through, causing Convex to throw
  a type-validation error and surface a 500 to the caller. Also fixes unbounded
  metadata strings filling the registrations table cheaply.

- rss-proxy.js: apply the same www-normalization used by the initial domain check
  to the 301-redirect hostname check. The old bare ALLOWED_DOMAINS.includes(hostname)
  call rejected canonical redirects (e.g. bbc.co.uk -> www.bbc.co.uk) even when
  one form is allowlisted, breaking several feeds silently.

- _api-key.js: align BROWSER_ORIGIN_PATTERNS Vercel-preview regex with the
  narrower pattern already enforced by _cors.js (worldmonitor-*-elie-*.vercel.app).
  The broader worldmonitor-*.vercel.app pattern was dead code because _cors.js
  rejects those origins before _api-key.js is reached.
2026-03-05 07:18:59 +04:00
Elie Habib
083b027f8d fix: prevent clock auto-translation churn (#1034)
* fix: prevent auto-translation churn on header clock

* fix: prevent auto-translation churn in world clock panel
2026-03-05 07:08:20 +04:00
Elie Habib
32d3023815 fix: query 3 VIIRS satellites sequentially for fire detections (#1036)
SNPP and NOAA20 frequently return 0 rows (data gaps). The previous
single-source parallel approach hit all 9 regions simultaneously,
causing FIRMS rate limits and timeouts on Railway.

Changes:
- Query SNPP + NOAA20 + NOAA21 with deduplication
- Sequential requests with 200ms delay (avoids rate limiting)
- 30s timeout (was 15s)
- Restore strict validation (length > 0)
2026-03-05 07:08:06 +04:00
Elie Habib
6764be834c fix: replace hardcoded FIRMS error with localized "no data" message (#1035)
The fires panel showed "NASA_FIRMS_API_KEY not configured" whenever
detections were empty, which is misleading — empty data can have
multiple causes (seed cron lag, Redis expiry, circuit breaker).

Use the existing i18n key `panels.satelliteFires.noData` instead,
which is already translated across all 21 supported languages.
2026-03-05 06:56:00 +04:00
Elie Habib
d695dbb49e Revert "Localize basemap place labels by active app language (#1027)" (#1030)
This reverts commit 9d247f7747.
2026-03-05 06:33:01 +04:00
Elie Habib
2f2486cc8e fix: harden seed scripts with 429 rate-limit retry and relaxed validation (#1026)
* fix: allow zero fire detections in seed validation

FIRMS NRT data has a rolling window — at certain hours, all 9 monitored
regions can legitimately return 0 active fire detections. The strict
length > 0 validation caused CRASHED status on Railway cron runs
during these periods. Structure-only validation is sufficient.

* fix: add rate-limit-aware retry for CoinGecko 429s

The default withRetry (1s/2s/4s backoff) is too short for CoinGecko
rate limits. New fetchWithRateLimitRetry uses 10s/20s/30s/40s/50s
delays with up to 5 attempts specifically for 429 responses.

* fix: add 429 rate-limit retry to all Yahoo and CoinGecko seed scripts

Yahoo Finance and CoinGecko both return 429 when rate limited. The
default withRetry (1s/2s/4s) is too fast for rate limits. Added
per-request 429-specific retry with longer backoff:
- Yahoo: 5s/10s/15s/20s (4 attempts per symbol)
- CoinGecko: 10s/20s/30s/40s/50s (5 attempts)

Scripts updated: seed-etf-flows, seed-gulf-quotes, seed-commodity-quotes,
seed-market-quotes, seed-stablecoin-markets.
2026-03-05 06:31:04 +04:00
Elie Habib
9d247f7747 Localize basemap place labels by active app language (#1027) 2026-03-05 06:20:24 +04:00
Elie Habib
a81b3b8240 fix: AI Insights panel stuck on Loading on mobile browsers (#1025)
The panel was visible but never loaded content on mobile. Root cause:
constructor called hide() + set isHidden=true on mobile, but
applyPanelSettings() later called toggle(true) removing the hidden
class. Panel was visible but updateInsights() bailed on isHidden flag.

Server-side pre-computed insights (via bootstrap hydration from Railway)
need zero client-side ML — no reason to block mobile. Now allows server
insights through while still guarding against the heavy client-side
fallback pipeline on mobile devices.
2026-03-05 06:07:38 +04:00
Elie Habib
27bb5793da fix: raise modal overlay z-index above globe WebGL canvas (#1022)
The settings and signal modal overlays had z-index: 1000, which was
lower than the stacking context created by the globe's WebGL canvas
container. Bump both to z-index: 9999 to match other fullscreen overlays.
2026-03-04 23:33:44 +04:00
Elie Habib
06c13cbb93 feat(telegram): add 8 new intel channels (#1021)
Add nayaforiraq, yediotnews25, DDGeopolitics, FotrosResistancee,
RezistanceTrench1, geopolitics_prime, thecradlemedia, LebUpdate
2026-03-04 23:18:55 +04:00
Elie Habib
3d349caa30 fix: preserve all map data and callbacks across globe/flat mode switches (#1018)
Globe mode switching created fresh map instances without re-pushing cached
data or re-wiring callbacks, losing all markers and event handlers.

- Cache all 30 data setter args and 6 callbacks in MapContainer
- Snapshot viewport state before switch, restore after
- Rehydrate cached data + callbacks on new map instance
- Disconnect ResizeObserver before switch to prevent duplicates
- Release all cached references on destroy()
- Handle severity "critical" in all Iran event renderers
- Fix full.ts DEFAULT_MAP_LAYERS iranAttacks: false → true
2026-03-04 23:08:36 +04:00
Elie Habib
a2e81a81f6 perf(globe): decouple flush channels, set accessors once, add Eco mode workload reduction (#1019)
Root cause: any data update destroyed/recreated ALL DOM markers, arcs, paths,
and polygons. Eco mode only adjusted pixel ratio — no actual CPU reduction.

- Decouple flush channels so marker updates don't rebuild arcs/paths/polygons
- Set globe.gl accessors once at init; flush methods now only update data arrays
- Hybrid debounce (100ms trailing + 300ms maxWait) coalesces startup burst
- Eco mode disables dash animations (stops globe.gl RAF loop), pulse CSS, atmosphere
- Cache reversed GeoJSON rings to avoid recomputing on every flushPolygons()
- Remove duplicate ResizeObserver (MapContainer's observer handles it)
- Layer channel routing via static LAYER_CHANNELS map (single source of truth)
- Move conflict color maps to static readonly (no per-call allocation)
2026-03-04 23:06:38 +04:00
Elie Habib
b001d25527 fix(relay): add GPS jamming seed loop to Railway relay (#1016)
The GPS jamming data pipeline had no scheduled seed — fetch-gpsjam.mjs
existed as a standalone script but was never wired into the relay's
setInterval-based seed system. Redis key intelligence:gpsjam:v1 was
always empty, forcing the edge function to fall back to direct
gpsjam.org fetches (without lat/lon pre-computation).

Adds startGpsJamSeedLoop() that runs every 6 hours:
- Fetches manifest + CSV from gpsjam.org
- Parses H3 hex data with min-aircraft threshold
- Converts H3→lat/lon via h3-js (pre-computed for frontend)
- Classifies regions for conflict zone tagging
- Writes enriched data to Redis with 24h TTL
2026-03-04 22:41:12 +04:00
Elie Habib
554bbadaf1 fix(hydration): guard all bootstrap consumers against empty CDN-cached data (#1015)
When CDN serves stale bootstrap responses with empty arrays (e.g.,
{anomalies:[]}), the hydration check `if (hydrated)` is truthy, causing
panels to skip the RPC fallback and show empty data. Fixed all 12
vulnerable getHydratedData consumers to verify the payload has actual
data before trusting it.

Also rewrote api/telegram-feed.js to inspect response body — empty
responses get short CDN TTL (15s) to prevent prolonged staleness.
2026-03-04 22:35:22 +04:00
Elie Habib
d4dc44330e fix: gateway always applies cache tier headers (overrides Vercel default) (#1009)
Vercel edge runtime adds a default `Cache-Control: public` to responses.
The gateway's `!mergedHeaders.has('Cache-Control')` guard prevented
s-maxage tier headers from being applied, so Cloudflare had no TTL
guidance — caching was inconsistent across endpoints.

Remove the guard so the gateway always sets the correct tier
(fast/medium/slow/static/daily) with proper s-maxage values.
2026-03-04 22:15:56 +04:00
Elie Habib
f83daf5cb3 fix(csp): add emrldco inline script hash to index.html CSP (#1014)
PR #1006 moved the emrldco analytics loader from <body> to <head>
but didn't add its sha256 hash to the Content-Security-Policy
script-src directive, causing the script to be blocked.
2026-03-04 22:14:31 +04:00
Elie Habib
4a8ab3855f fix: seed-insights digest shape extraction (#1011)
* feat: add seed-first pattern to 15 RPC handlers with Railway seed scripts

Migrate handlers from direct external API calls to seed-first pattern:
Railway cron seeds Redis → handlers read from Redis → fallback to live
fetch if seed stale and SEED_FALLBACK_* env enabled.

Handlers updated: earthquakes, fire-detections, internet-outages,
climate-anomalies, unrest-events, cyber-threats, market-quotes,
commodity-quotes, crypto-quotes, etf-flows, gulf-quotes,
stablecoin-markets, natural-events, displacement-summary, risk-scores.

Also adds:
- scripts/_seed-utils.mjs (shared seed framework with atomic publish,
  distributed locks, retry, freshness metadata)
- 13 seed scripts for Railway cron
- api/seed-health.js monitoring endpoint
- scripts/validate-seed-migration.mjs post-deploy validation
- Restored multi-source CII in get-risk-scores (8 sources: ACLED,
  UCDP, outages, climate, cyber, fires, GPS, Iran)

* feat: add seed scripts for market quotes, commodity quotes & airport delays

New seed scripts:
- seed-market-quotes.mjs: 28 symbols via Yahoo Finance + Finnhub
- seed-commodity-quotes.mjs: 6 commodity futures via Yahoo Finance
- seed-airport-delays.mjs: FAA + NOTAM airport closure data

Handler changes (seed-first pattern):
- list-market-quotes.ts: read from seed data before live fetch
- list-commodity-quotes.ts: read from seed data before live fetch
- list-airport-delays.ts: seed-first for FAA and NOTAM data

Other changes:
- ais-relay.cjs: add DISABLE_RELAY_MARKET_SEED guard for cutover
- _seed-utils.mjs: add sleep, parseYahooChart, writeExtraKey helpers
- seed-health.js: monitor 4 new seed domains
- validate-seed-migration.mjs: add new domains to validation

* fix: extract digest items from category buckets in seed-insights

The news digest Redis key stores items nested in category buckets
({ categories: { politics: { items: [...] }, ... } }), not as a
flat array. The script was checking for digest.items which is
undefined, causing "Digest has no items" errors on every run.
2026-03-04 21:59:23 +04:00
Elie Habib
16661457e0 fix(alerts): persist dedup map + startup grace to stop repeat breaking banners (#1007)
RSS feeds (CBS, CNN) update pubDate on article edits, making hours-old
stories appear "2m ago". Combined with the in-memory dedup map clearing
on every page load, the same article re-fires as "breaking" every app
open.

Two fixes:
- Persist dedup map to localStorage so seen alerts survive page reloads
- 10s startup grace period suppresses RSS alerts during initial feed
  fetch (OREF siren alerts bypass this — real-time sirens never delayed)
2026-03-04 21:16:35 +04:00
Elie Habib
cfc282bd88 fix: hide redundant header search icon on mobile (FAB is primary) (#1008) 2026-03-04 21:16:14 +04:00
Mert Efe Şensoy
f771114522 feat: aviation monitoring layer with flight tracking, airline intel panel, and news feeds (#907)
* feat: Implement comprehensive aviation monitoring service with flight search, status, news, and tracking.

* feat: Introduce Airline Intelligence Panel with aviation data tabs, map components, and localization.

* feat: Implement DeckGL-based map for advanced visualization, D3/SVG fallback, i18n support, and aircraft tracking.

* Update server/worldmonitor/aviation/v1/get-carrier-ops.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update server/worldmonitor/aviation/v1/search-flight-prices.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update server/worldmonitor/aviation/v1/track-aircraft.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update server/worldmonitor/aviation/v1/get-airport-ops-summary.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update proto/worldmonitor/aviation/v1/position_sample.proto

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update server/worldmonitor/aviation/v1/list-airport-flights.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update proto/worldmonitor/aviation/v1/price_quote.proto

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* feat: Add server-side endpoints for aviation news and aircraft tracking, and introduce a new DeckGLMap component for map visualization.

* Update server/worldmonitor/aviation/v1/list-airport-flights.ts

The cache key for listAirportFlights excludes limit, but the upstream fetch/simulated generator uses limit to determine how many flights to return. If the first request within TTL uses a small limit, larger subsequent requests will be incorrectly capped until cache expiry. Include limit (or a normalized bucket/max) in cacheKey, or always fetch/cache a fixed max then slice per request.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update server/worldmonitor/aviation/v1/get-flight-status.ts

getFlightStatus accepts origin, but cacheKey does not include it. This can serve cached results from an origin-less query to an origin-filtered query (or vice versa). Add origin (normalized) to the cache key or apply filtering after fetch to ensure cache correctness.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* feat: Implement DeckGL map for advanced visualization and new aviation data services.

* fix(aviation): prevent cache poisoning and keyboard shortcut in inputs

- get-carrier-ops: move minFlights filter post-cache to avoid cache
  fragmentation (different callers sharing cached full result)
- AviationCommandBar: guard Ctrl+J shortcut so it does not fire when
  focus is inside an INPUT or TEXTAREA element

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

* feat: introduce AviationCommandBar component for parsing user commands, fetching aviation data, and displaying results.

* feat: Implement aircraft tracking service with OpenSky and simulated data sources.

* feat: introduce DeckGLMap component for WebGL-accelerated map visualizations using deck.gl and maplibre-gl.

* fix(aviation): address code review findings for PR #907

Proto: add missing (sebuf.http.query) annotations on all GET request
fields across 6 proto files; add currency/market fields to
SearchFlightPricesRequest.

Server: add parseStringArray to aviation _shared.ts and apply to
get-airport-ops-summary, get-carrier-ops, list-aviation-news handlers
to prevent crash on comma-separated query params; remove leaked API
token from URL params in travelpayouts_data; fix identical simulated
flight statuses in list-airport-flights; remove unused endDate var;
normalize cache key entity casing in list-aviation-news.

Client: refactor AirlineIntelPanel to extend Panel base class and
register in DEFAULT_PANELS for full/tech/finance variants; fix
AviationCommandBar reference leak with proper destroy() cleanup in
panel-layout; rename priceUsd→priceAmount in display type and all
usages; change auto-refresh to call refresh() instead of loadOps().

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

* feat: introduce aviation command bar component with aircraft tracking and flight information services.

* feat: Add `AirlineIntelPanel` component for displaying airline operations, flights, carriers, tracking, news, and prices in a tabbed interface.

* feat: Add endpoints for listing airport flights and fetching aviation news.

* Update proto/worldmonitor/aviation/v1/search_flight_prices.proto

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* feat: Add server endpoint for listing airport flights and client-side MapPopup types and utilities.

* feat: Introduce MapPopup component with support for various data types and responsive positioning for map features.

* feat: Add initial English localization file (en.json).

* fix(aviation): address PR review findings across aviation stack

- Add User-Agent header to Travelpayouts provider (server convention)
- Use URLSearchParams for API keys instead of raw URL interpolation
- Add input length validation on flightNumber (max 10 chars)
- Replace regex XML parsing with fast-xml-parser in aviation news
- Fix (f as any)._airport type escape with typed Map<FI, string>
- Extract DEFAULT_WATCHED_AIRPORTS constant from hardcoded arrays
- Use event delegation for AirlineIntelPanel price search listener
- Add bootstrap hydration key for flight delays
- Bump OpenSky cache TTL to 120s (anonymous tier rate limit)
- Match DeckGLMap aircraft poll interval to server cache (120s)
- Fix GeoJSON polygon winding order (shoelace check + auto-reversal)

* docs: add aviation env vars to .env.example

AVIATIONSTACK_API, ICAO_API_KEY, TRAVELPAYOUTS_API_TOKEN

* feat: Add aviation news listing API and introduce shared RSS allowed domains.

* fix: add trailing newline to rss-allowed-domains.json, remove unused ringIsClockwise

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Elie Habib <elie.habib@gmail.com>
2026-03-04 21:09:37 +04:00
Elie Habib
50df05dfd3 feat(markets): customizable watchlist symbols (#952)
* feat(markets): user-custom watchlist symbols

* feat(markets): support friendly labels in custom watchlist
2026-03-04 20:56:54 +04:00
Elie Habib
40abcae887 feat: CII Railway seed — pre-compute instability scores from 8 sources (#996)
Adds seedCiiScores() to ais-relay.cjs that runs every 10 minutes:
- Reads 7 Redis sources (UCDP, outages, climate, cyber, fires, GPS jam, Iran)
- Calls ACLED API directly for protests/riots/battles
- Computes simplified CII scores for 20 TIER1 countries
- Writes to risk:scores:sebuf:v1 (TTL 900s) + stale key (TTL 3600s)

Frontend bootstrap hydration (already on main) consumes these scores
for instant CII panel render on page load.
2026-03-04 20:56:39 +04:00
Elie Habib
fdbf208e8e fix: move emrldco script from <body> to <head> (#1006) 2026-03-04 20:56:28 +04:00
Elie Habib
058cfd89f4 refactor: merge geopolitical boundaries layer into conflict zones (#1005)
The geopoliticalBoundaries layer had only one entry (Korean DMZ) but
carried its own toggle, type, rendering code, popup, i18n strings,
and variant configs. Merge it into the existing conflicts layer to
reduce surface area.

- Add Korean DMZ as a ConflictZone (intensity: low) in CONFLICT_ZONES
- Remove GeopoliticalBoundary type, GEOPOLITICAL_BOUNDARIES export
- Remove dedicated layer rendering in DeckGLMap and GlobeMap
- Remove popup type and renderer in MapPopup
- Clean layer registry, variant configs, panels, E2E harnesses
- Remove i18n keys from all 21 locale files
2026-03-04 20:56:14 +04:00
ValMtp
6fb227db00 feat: add JSON export and import for settings persistence (#800)
* feat: add JSON export and import for settings persistence

* Update src/components/UnifiedSettings.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/utils/settings-persistence.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/settings-main.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* feat: Add localization for unified settings data management, export, and import.

* fix(settings): address PR feedback on export/import security and i18n

* style(i18n): ensure trailing newline in locale files

* fix: address review feedback on settings export/import

- Replace denylist with allowlist of known settings key prefixes
- Add version check and Array.isArray guard on import
- Remove window.alert/confirm — use inline toast and setActionStatus
- Restore deleted countryBrief.noMarkets and components i18n keys
- Move settings i18n keys to components.settings namespace
- Replace inline styles with CSS classes
- Return ImportResult from importSettings for caller feedback

---------

Co-authored-by: ValMtp3 <ValMtp3@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Elie Habib <elie.habib@gmail.com>
2026-03-04 20:46:50 +04:00
Elie Habib
124085edd6 fix: add process.exit(0) to seed scripts for Railway cron compatibility (#999)
Railway marks cron jobs as "failed" when the Node.js process doesn't
exit cleanly. The seed scripts relied on natural event loop drain,
but undici's connection pool keeps handles alive, causing Railway to
kill the process and mark it as failed.

Changes:
- Add process.exit(0) on success and lock-skip paths in runSeed()
- Fix recordCount for crypto (.quotes) and stablecoin (.stablecoins)
- Add writeExtraKey, sleep, parseYahooChart shared utilities
- Add extraKeys option to runSeed for bootstrap hydration keys
2026-03-04 20:43:16 +04:00
Elie Habib
80b8071356 feat: server-side AI insights via Railway cron + bootstrap hydration (#1003)
Move the heavy AI insights pipeline (clustering, scoring, LLM brief)
from client-side (15-40s per user) to a 5-min Railway cron job. The
frontend reads pre-computed insights instantly via bootstrap hydration,
with graceful fallback to the existing client-side pipeline.

- Add _clustering.mjs: Jaccard clustering + importance scoring (pure JS)
- Add seed-insights.mjs: Railway cron reads digest, clusters, calls
  Groq/OpenRouter for brief, writes to Redis with LKG preservation
- Register insights key in bootstrap.js FAST_KEYS tier
- Add insights-loader.ts: module-level cached bootstrap reader
- Modify InsightsPanel.ts: server-first path (2-step progress) with
  client fallback (4-step, unchanged behavior)
- Add unit tests for clustering (12) and insights-loader (7)
2026-03-04 20:42:51 +04:00
Elie Habib
e679db9902 fix(globe): render conflict zone polygons correctly on 3D globe (#994)
* fix(globe): add polygonGeoJsonGeometry accessor for polygon rendering

globe.gl's default polygonGeoJsonGeometry accessor looks for d.geometry,
but our GlobePolygon objects store coordinates in d.coords. Without this
explicit accessor, all polygon data (conflict zones, boundaries, CII
choropleth) was silently ignored — polygonsData was set but nothing
rendered.

* fix(globe): render conflict zone polygons correctly on 3D globe

Root cause: globe.gl's ConicPolygonGeometry + earcut renders CCW exterior
rings as their complement on the sphere (filling everything EXCEPT the
polygon). The simplified CONFLICT_ZONES coords also lacked enough vertices
for proper spherical tessellation.

Fix:
- Use real GeoJSON country geometries from countriesGeoData instead of
  simplified CONFLICT_ZONES.coords (maps zone IDs to ISO-2 codes)
- Reverse winding order (CCW → CW) on all polygon rings so earcut fills
  the polygon interior, not the complement
- Apply same winding fix to GEOPOLITICAL_BOUNDARIES polygons
- Zones without country mapping (Strait of Hormuz, South Lebanon, Red Sea)
  are represented by center markers only

Tested: Iran, Ukraine, Gaza/Israel, Sudan, Myanmar all render as localized
red polygon overlays on the globe without envelope artifacts.

* fix(globe): guard against null geometry in GeoJSON features

Some GeoJSON features have null geometry which caused TypeError
in flushPolygons. Add null guards for both conflict zone and CII
choropleth polygon rendering paths.
2026-03-04 20:38:19 +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
Elie Habib
2058d71897 fix(strategic-risk): use server-cached scores when data sources insufficient (#1001)
The panel showed "Insufficient Data" even when Railway/Vercel had
pre-computed 8-source CII scores available, because the insufficient
check only looked at client-side GDELT/RSS freshness. Now fetches
cached server scores when overallStatus is insufficient (not just
during learning mode) and bypasses the insufficient view when those
scores are available.
2026-03-04 20:33:01 +04:00
Elie Habib
1e670a08ed feat: add mobile search FAB and cleaner mobile results (#998)
- Add 56px accent-blue floating action button (bottom-right)
- Always visible regardless of scroll or horizontal overflow
- Hide subtitles and type badges on mobile results for cleaner look
- Wire FAB to same openSearch handler as header icon
2026-03-04 20:12:03 +04:00
Elie Habib
42cd258f5a fix: RSS redirect crash — allowedDomains was renamed but redirect handler not updated (#995)
The RSS_ALLOWED_DOMAINS refactor missed the redirect handler at line 4755,
causing ReferenceError: allowedDomains is not defined every time an RSS
feed returns a 301/302 redirect. This crashes the entire relay process.
2026-03-04 19:34:09 +04:00
Elie Habib
4f78ce73af fix(globe): flush polygons on layer changes and init (#993)
flushPolygons() was only called from setCIIScores() and GeoJSON load,
never from setLayers()/enableLayer() or initial globe setup. Conflict
zone polygons were pushed to polygonsData but never rendered because
the flush never fired when the conflicts layer was active at startup.
2026-03-04 18:45:04 +04:00
Elie Habib
898ac7b1c4 perf(rss): route RSS direct to Railway, skip Vercel middleman (#961)
* perf(rss): route RSS direct to Railway, skip Vercel middleman

Vercel /api/rss-proxy has 65% error rate (207K failed invocations/12h).
Route browser RSS requests directly to Railway (proxy.worldmonitor.app)
via Cloudflare CDN, eliminating Vercel as middleman.

- Add VITE_RSS_DIRECT_TO_RELAY feature flag (default off) for staged rollout
- Centralize RSS proxy URL in rssProxyUrl() with desktop/dev/prod routing
- Make Railway /rss public (skip auth, keep rate limiting with CF-Connecting-IP)
- Add wildcard *.worldmonitor.app CORS + always emit Vary: Origin on /rss
- Extract ~290 RSS domains to shared/rss-allowed-domains.cjs (single source of truth)
- Convert Railway domain check to Set for O(1) lookups
- Remove rss-proxy from KEYED_CLOUD_API_PATTERN (no longer needs API key header)
- Add edge function test for shared domain list import

* fix(edge): replace node:module with JSON import for edge-compatible RSS domains

api/_rss-allowed-domains.js used createRequire from node:module which is
unsupported in Vercel Edge Runtime, breaking all edge functions (including
api/gpsjam). Replaced with JSON import attribute syntax that works in both
esbuild (Vercel build) and Node.js 22+ (tests).

Also fixed middleware.ts TS18048 error where VARIANT_OG[variant] could be
undefined.

* test(edge): add guard against node: built-in imports in api/ files

Scans ALL api/*.js files (including _ helpers) for node: module imports
which are unsupported in Vercel Edge Runtime. This would have caught the
createRequire(node:module) bug before it reached Vercel.

* fix(edge): inline domain array and remove NextResponse reference

- Replace `import ... with { type: 'json' }` in _rss-allowed-domains.js
  with inline array — Vercel esbuild doesn't support import attributes
- Replace `NextResponse.next()` with bare `return` in middleware.ts —
  NextResponse was never imported

* ci(pre-push): add esbuild bundle check and edge function tests

The pre-push hook now catches Vercel build failures locally:
- esbuild bundles each api/*.js entrypoint (catches import attribute
  syntax, missing modules, and other bundler errors)
- runs edge function test suite (node: imports, module isolation)
2026-03-04 18:42:00 +04:00
MirzaSamadAhmedBaig
8fd732be1f Harden natural event category handling against class injection (#991)
* Harden natural event category handling against class injection

* refactor: DRY up category allowlist, harden sanitizeClassToken

- Extract NATURAL_EVENT_CATEGORIES Set to src/types (single source of truth)
- eonet.ts imports shared Set instead of duplicating it
- sanitizeClassToken strips leading digits/hyphens for valid CSS class names
- Server copy kept separate (cannot import from src/types)

---------

Co-authored-by: Elie Habib <elie.habib@gmail.com>
2026-03-04 18:17:01 +04:00
Elie Habib
9a8e7c00c6 feat(globe): render conflict zone polygons on 3D globe (#992)
The 3D globe only showed tiny circle markers at conflict zone centers.
Now renders full polygon overlays from CONFLICT_ZONES coords data with
intensity-based styling (color, altitude, stroke) and rich hover tooltips.

- Add 'conflict' kind to GlobePolygon with intensity/parties/casualties
- Fix layer semantics: boundaries use geopoliticalBoundaries toggle
- 3-way style branching for cii/conflict/boundary polygon rendering
- Ring normalization: close unclosed rings, skip degenerate polygons
- Shrink center marker (28→20px) since polygons carry visual weight
2026-03-04 18:06:16 +04:00
Elie Habib
78a14306d9 feat: add seed-first pattern to 15 RPC handlers with Railway seed scripts (#989)
Migrate handlers from direct external API calls to seed-first pattern:
Railway cron seeds Redis → handlers read from Redis → fallback to live
fetch if seed stale and SEED_FALLBACK_* env enabled.

Handlers updated: earthquakes, fire-detections, internet-outages,
climate-anomalies, unrest-events, cyber-threats, market-quotes,
commodity-quotes, crypto-quotes, etf-flows, gulf-quotes,
stablecoin-markets, natural-events, displacement-summary, risk-scores.

Also adds:
- scripts/_seed-utils.mjs (shared seed framework with atomic publish,
  distributed locks, retry, freshness metadata)
- 13 seed scripts for Railway cron
- api/seed-health.js monitoring endpoint
- scripts/validate-seed-migration.mjs post-deploy validation
- Restored multi-source CII in get-risk-scores (8 sources: ACLED,
  UCDP, outages, climate, cyber, fires, GPS, Iran)
2026-03-04 17:37:15 +04:00
Elie Habib
05e1e9d6b1 fix: improve mobile search bottom sheet UX (#988)
* feat: add mobile bottom sheet search with trigger bar

Replace the desktop-only Cmd+K search modal with a native-feeling
bottom sheet on mobile (<768px). Adds a fixed pill-shaped trigger
bar at the bottom of the screen, suggestion chips for quick access
to countries/commands, 48px touch targets, and slide-up animation.

Also removes the deprecated MobileWarningModal, adds a community
discussion link in settings, and improves mobile header layout.

* fix: improve mobile search bottom sheet UX

- Remove redundant trigger pill (header search icon is primary entry)
- Reduce sheet height from 75vh to 50vh for half-sheet feel
- Cap mobile results to 5 (2 per type) to avoid overwhelming small screens
- Add visualViewport listener for iOS keyboard-aware sheet resizing
- Replace clipped "Cancel" text with × icon + aria-label
- Hide chips on first keystroke to preserve vertical space
- Show 2 tips instead of 4 on mobile
- Revert panels-grid padding-bottom (no longer needed without pill)
2026-03-04 17:33:00 +04:00
Elie Habib
cdb311ae78 Fix mobile horizontal overflow from deck.gl legend (#987)
* fix(mobile): prevent deckgl legend overflow

* fix: clean up redundant CSS in mobile legend override

Remove redundant max-width (left/right already constrains) and fix
gap shorthand overwriting row-gap (use gap: 6px 8px instead).
2026-03-04 17:31:15 +04:00
Elie Habib
aaf06cae79 fix(webcams): recover from blocked/stuck YouTube embeds (#986)
* enhance supply chain panel

* fix(supply-chain): resolve P1 threat zeroing and P2 geo-first misclassification

P1: threat baseline is now always applied regardless of config
staleness — stale config only adds a review-recommended note,
never zeros the score.

P2: resolveChokepointId now checks text evidence first and only
falls back to proximity when text has no confident match.

Adds regression test: text "Bab el-Mandeb" with location near
Suez correctly resolves to bab_el_mandeb.

* fix(webcams): recover from blocked/stuck youtube embeds

---------

Co-authored-by: fayez bast <fayezbast15@gmail.com>
2026-03-04 15:15:19 +04:00
Elie Habib
ea8d64a042 feat: add mobile bottom sheet search with trigger bar (#985)
Replace the desktop-only Cmd+K search modal with a native-feeling
bottom sheet on mobile (<768px). Adds a fixed pill-shaped trigger
bar at the bottom of the screen, suggestion chips for quick access
to countries/commands, 48px touch targets, and slide-up animation.

Also removes the deprecated MobileWarningModal, adds a community
discussion link in settings, and improves mobile header layout.
2026-03-04 15:12:44 +04:00
Elie Habib
c7942b800a feat: Railway CII seed + bootstrap hydration for instant panel render (#984)
* fix: add circuit breaker + bootstrap to CII risk scores

Same pattern as theater posture (#948): replace fragile in-memory cache
+ manual persistent-cache with circuit breaker (SWR, IndexedDB, cooldown)
and bootstrap hydration. Eliminates learning-mode delay on cold start
and survives RPC failures without blanking the panel.

* fix: add localStorage sync prime for CII risk scores

getCachedScores() is called synchronously by country-intel.ts as a
fallback during learning mode. Without localStorage priming, the
breaker's async IndexedDB hydration hasn't run yet and returns null.

- Add shape validator (isValidCiiEntry) for untrusted localStorage data
- Add loadFromStorage/saveToStorage with 24h staleness ceiling
- Prime breaker synchronously at module load from localStorage
- Skip priming for empty cii arrays to avoid cached-empty trap
- Save to localStorage on both bootstrap and RPC success paths

* feat: Railway CII seed + bootstrap hydration for instant panel render

- Add 8-source CII seed to Railway (ACLED, UCDP, outages, climate, cyber, fires, GPS, Iran strikes)
- Neuter Vercel handler to read-only (returns Railway-seeded cache, never recomputes)
- Register riskScores in bootstrap FAST tier for CDN-cached delivery
- Add early CII hydration in data-loader before intelligence signals
- Add CIIPanel.renderFromCached() for instant render from bootstrap data
- Refactor cached-risk-scores.ts: circuit breaker + localStorage sync prime + bootstrap hydration
- Progressive enhancement: cached render → full 18-source local recompute (no spinner)

* fix: remove duplicate riskScores key in BOOTSTRAP_TIERS after merge
2026-03-04 15:09:48 +04:00
Elie Habib
4de2f74210 feat: move EONET/GDACS to server-side with Redis caching (#983)
* feat: move EONET/GDACS to server-side with Redis caching and bootstrap hydration

Browser-direct fetches to eonet.gsfc.nasa.gov and gdacs.org caused CORS
errors and had no server-side caching. This moves both to the standard
Vercel edge → cachedFetchJson → Redis → bootstrap hydration pattern.

- Add proto definitions for NaturalService with ListNaturalEvents RPC
- Create server handler merging EONET + GDACS with 30min Redis TTL
- Add Vercel edge function at /api/natural/v1/list-natural-events
- Register naturalEvents in bootstrap SLOW_KEYS for CDN hydration
- Replace browser-direct fetches with RPC client + circuit breaker
- Delete src/services/gdacs.ts (logic moved server-side)

* fix: restore @ts-nocheck on generated files stripped by buf generate
2026-03-04 15:02:03 +04:00