mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
e51058e1765ef2f0c83ccb1d08d984bc59d23f10
1524 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e51058e176 |
fix(world-clock): make drag handles always visible (#715)
Change opacity from 0 (hidden) to 0.35 (subtle but visible) so users can see the drag affordance without needing to hover first.v2.5.23 |
||
|
|
d0e12fa3ec |
fix(world-clock): merge duplicate .wc-row CSS rules (#714)
The drag reorder PR added a second .wc-row rule that overrode display:grid but dropped align-items, padding, and border-bottom, breaking the row layout. Merge into a single rule. |
||
|
|
fd129dff5e |
feat(world-clock): add drag-to-reorder city rows (#712)
Add drag handles on each row for mouse-based reorder. Pauses tick interval during drag to prevent DOM replacement. Order persists to localStorage. Uses mousedown/mousemove/mouseup for WKWebView compatibility. |
||
|
|
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. |
||
|
|
29b1e7d715 |
fix(panel): redesign World Clock with proper layout and working settings (#710)
The original World Clock panel had broken settings (popover clipped by panel overflow) and plain text dump layout with no visual hierarchy. - Replace broken popover with inline settings view (toggles in content area) - Proper two-column flex layout: city info left, large time display right - Market status with pulsing green dot (OPEN) vs dim dot (CLSD) - Weekend-aware market status (correctly shows CLSD on Sat/Sun) - Day/night theming with dimmed time for nighttime cities - Home city accent with green left border - City selection organized by region in 2-column grid - Event delegation on stable content element (respects setContent debounce) - Removed document-level click listener leak from old popover approach |
||
|
|
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. |
||
|
|
12f481b5b5 |
fix(panel): live news fullscreen now renders above all UI elements (#708)
Move panel element to document.body when entering fullscreen to escape the panels-grid stacking context (z-index: 1) that trapped it below the map legend, DEFCON badge, time selector, and layers panel. |
||
|
|
799ca5dad8 |
Add Czech (cs) translation (#701)
* Add Czech (cs) translation Added full Czech localization based on en.json. * feat(i18n): register Czech locale and backfill 12 missing keys - Add 'cs' to SUPPORTED_LANGUAGES and LANGUAGES array - Add cs-CZ locale mapping in getLocale() - Backfill 12 keys added after original translation: panels.gulfEconomies/Indices/Currencies/Oil, components.insights.sectionPanels/badgeAnimLabel/badgeAnimDesc/ sectionIntelligence/headlineMemoryLabel/headlineMemoryDesc, components.intelligenceFindings.hideFindings, components.webcams.expand --------- Co-authored-by: Elie Habib <elie.habib@gmail.com> |
||
|
|
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. |
||
|
|
001ba79113 |
feat(panel): add World Clock panel with live financial city times (#705)
Zero-API panel showing ticking clocks for major financial centers. Auto-detects user's home city from browser timezone. Settings gear to add/remove from 30 curated cities. Market open/closed status and day/night progress bar. Persists selection to localStorage. |
||
|
|
72557f2d70 |
fix(i18n): add 12 missing translation keys across all 18 locales (#703)
Settings panel showed raw keys (sectionPanels, badgeAnimLabel, etc.) instead of translated text. Audit found 12 total missing keys: - 6 in components.insights (UnifiedSettings toggles/sections) - 4 in panels (GulfEconomiesPanel titles) - 1 in components.intelligenceFindings (hideFindings) - 1 in components.webcams (expand tooltip) |
||
|
|
e4cb1abf17 |
fix(sentry): add noise filters for 7 unresolved issues (#698)
- Add UTItemActionController to Can't-find-variable regex (iOS UIKit internal) - Add webkit(Supports)PresentationMode filter (Safari media API internal) - Add webdriver redefine filter (bot detection scripts) - beforeSend: suppress deck.gl/maplibre crashes with no stack trace - beforeSend: suppress TypeErrors from anonymous/injected scripts |
||
|
|
3e3c884525 |
feat(feeds): reduce default-enabled sources & add locale-aware boost (#699)
Reduce default-enabled RSS sources from 192 to ~101 (Tier 1+2 per panel, ≥8 per panel). One-time localStorage migration disables non-default sources. Locale boost: feeds tagged with the user's browser language are additively enabled without overwriting manual preferences. Runs once per locale. New feeds: Fox News, NBC News, CBS News, Reuters US (us); Yonhap News, Chosun Ilbo (asia/ko); The National (middleeast). Al Arabiya now has multi-URL (en/ar). Removed L'Orient-Le Jour from middleeast. |
||
|
|
78cecf3103 |
fix(oref): sanitize Hebrew Unicode control chars for reliable translation (#694)
OREF API embeds invisible RTL/LTR marks and zero-width chars in Hebrew strings, breaking exact key lookup in both STATIC_TRANSLATIONS and OREF_LOCATIONS maps. Also normalizes en-dash variants to ASCII hyphen and adds bare city entries (Beer Sheva, Ashdod, Ashkelon, Sderot). |
||
|
|
8384049fb9 | fix(live-news): remove LiveNOW from FOX channel (YouTube error 150) (#693) | ||
|
|
237bec0208 |
feat(i18n): backfill missing translations for 17 locales (#692)
Add ~100 missing keys per locale across 5 feature areas:
- Map markers (activeStrikes, aviationDisruptions)
- Tooltips (activeStrikes with {{count}})
- Panel titles (giving, supplyChain)
- Settings (skipSetup, worldMonitor section)
- Popups (iranEvent section)
All translations verified: valid JSON, preserved {{variables}},
idiomatic phrasing per language.
|
||
|
|
7d4eeb74d0 |
fix(web): improve mobile responsiveness — collapsible map, panel sizing, font bump (#688)
* fix(web): improve mobile responsiveness — collapsible map, panel sizing, font bump (#354) - Add Show/Hide Map toggle on mobile (collapsed by default, persisted in localStorage) - Increase panel max-height from 400px to 70vh and add iOS smooth scrolling - Bump mobile font sizes to 13-14px for readability - All changes scoped to ≤768px media query or isMobile guard Closes #354 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(mobile): i18n map toggle, cap panel height, reduce !important - Replace hardcoded "Show Map"/"Hide Map" with t() i18n calls and ▶/▼ chevron prefix for universal clarity - Add components.map.showMap/hideMap keys to all 18 locales (ar, de, el, en, es, fr, it, ja, ko, nl, pl, pt, ru, sv, th, tr, vi, zh) - Clarify localStorage default: explicit null check instead of double-negative !== 'false' - Cap panel max-height at min(70vh, 500px) to prevent single panel from consuming full viewport on tall phones - Reduce !important in collapsed map styles by raising specificity via .main-content ancestor selector --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Elie Habib <elie.habib@gmail.com> |
||
|
|
dcd7050078 |
feat(live-news): add Fox News HLS stream & fullscreen toggle (#689)
* feat(live-news): add Fox News HLS stream & fullscreen toggle for panels/map - Add Fox News HLS direct stream (247preview.foxnews.com) to bypass YouTube embedding restrictions (error 150) - Remove isDesktopRuntime() gate from resolveChannelVideo so direct HLS streams work in browser, not just desktop app - Remove useFallbackOnly from Fox News channel config - Add fullscreen maximize button to Live News, Live Webcams, and Global Situation map panels — click to expand, ESC to exit * fix(map): use map-pin-btn style for map fullscreen button visibility * fix(map): group fullscreen and pin buttons in flex container Wrap the fullscreen and pin buttons in a flex div so they stay together on the right side of the map header instead of being spread apart by justify-content: space-between. |
||
|
|
3bfab2662f |
feat(breaking-news): click banner to scroll to source panel (#690)
Breaking news alerts were not clickable. Now clicking the banner (outside the dismiss button) smooth-scrolls to the panel that contains the story, matching the existing SearchManager pattern. - Add getSourcePanelId() reverse lookup in feeds.ts - Map alert origins to panels (oref→oref-sirens, rss→feed category) - Reuse scrollIntoView + flash-highlight animation |
||
|
|
7e2eae85ad |
feat(i18n): add 104 missing translation keys for Italian and Spanish (#687)
* feat(i18n): add 104 missing translation keys for Italian and Spanish Add translations for recently introduced UI sections: Supply Chain, Global Giving, Gulf Economies, Breaking News severity labels, Iran Attacks layer, World Monitor license/waitlist settings, and various small additions across existing panels. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(i18n): correct ~65 mistranslations and untranslated keys in IT/ES Spanish fixes: - "campo de golf" → "enlaces" (links), "fósforo" → "coincidencia" (match) - "Cerca" → "Cerrar" (close button), "Ahorrar" → "Guardar" (save) - "Precipicio" → "Precip." (precipitation), "Dar un golpe de zoom" → "Acercar" - "Aceite" → "Petróleo" (oil), "Camino" → "Ruta" (URL path) - Military units: "Transportistas" → "Portaaviones", "luchadores" → "Cazas" - Broken timeAgo format: "{{count}}hace m" → "Hace {{count}}m" Italian fixes: - "Vicino" → "Chiudi" (close button), "Tavolo" → "Desktop" (desktop app) - "Sentiero" → "Percorso" (URL path), "spostato" → "sfollati" (displaced) - "Olio" → "Petrolio" (oil), fixed __PH_6/__PH_4 placeholder artifacts - Military units: "Portatori" → "Portaerei", "Navi navali" → "Unità navali" Both languages: - Translated ~20 untranslated regulation keys (stances, counts, source) - Translated ~10 untranslated economic keys (central banks, rates, actions) - Brand names kept as-is: "Product Hunt", "Startup" --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Elie Habib <elie.habib@gmail.com> |
||
|
|
a681b08de6 |
fix(pwa): stop auto-reload on service worker update (#686)
virtual:pwa-register with registerType 'autoUpdate' installs a controllerchange listener that calls window.location.reload() whenever a new SW takes control. Combined with skipWaiting + clientsClaim, every deployment triggered a visible full-page reload on first visit. Replace with manual navigator.serviceWorker.register() that preserves all PWA functionality (precaching, offline, hourly update checks) without the forced reload. chunk-reload.ts still handles stale chunk 404s. |
||
|
|
5a7451c465 |
perf(web): lazy-load DeductionPanel to exclude dompurify from web bundle (#685)
DeductionPanel is desktop-only (guarded by isDesktopApp) but was statically imported, pulling dompurify + marked into the web bundle. Convert to dynamic import() so Vite code-splits them out of web builds. |
||
|
|
2c0b3de2d4 |
feat(feeds): add Asharq News & Business to Middle East (#683)
* feat(live-news): add Asharq News to Middle East live channels * feat(feeds): add Asharq Business & Asharq News RSS feeds to Middle East |
||
|
|
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 |
||
|
|
77b326397f |
fix(sentry): triage 10 unresolved issues — 2 code fixes + 8 noise filters (#681)
- Wrap history.replaceState in try/catch for RTL-mark URL corruption (WORLDMONITOR-7B) - Guard video.play() with optional chaining for browsers returning void (WORLDMONITOR-7A) - Add ignoreErrors for: logMutedMessage, TrustedTypes createPolicy, swbrowser/xbrowser, browser.storage.local quota, play() interrupted by pause(), MutationEvent (Tampermonkey) - Widen existing filter to catch "Unexpected end of JSON input" |
||
|
|
a7efa7dda8 |
feat: implement deduct situation feature (#636) (#642)
* Add Security Advisories panel with government travel alerts (#460) * feat: add Security Advisories panel with government travel advisory feeds Adds a new panel aggregating travel/security advisories from official government foreign affairs agencies (US State Dept, AU DFAT Smartraveller, UK FCDO, NZ MFAT). Advisories are categorized by severity level (Do Not Travel, Reconsider, Caution, Normal) with filter tabs by source country. Includes summary counts, auto-refresh, and persistent caching via the existing data-freshness system. * chore: update package-lock.json * fix: event delegation, localization, and cleanup for SecurityAdvisories panel P1 fixes: - Use event delegation on this.content (bound once in constructor) instead of direct addEventListener after each innerHTML replacement — prevents memory leaks and stale listener issues on re-render - Use setContent() consistently instead of mixing with this.content.innerHTML - Add securityAdvisories translations to all 16 non-English locale files (panels name, component strings, common.all key) - Revert unrelated package-lock.json version bump P2 fixes: - Deduplicate loadSecurityAdvisories — loadIntelligenceData now calls the shared method instead of inlining duplicate fetch+set logic - Add Accept header to fetch calls for better content negotiation * feat(advisories): add US embassy alerts, CDC, ECDC, and WHO health feeds Adds 21 new advisory RSS feeds: - 13 US Embassy per-country security alerts (TH, AE, DE, UA, MX, IN, PK, CO, PL, BD, IT, DO, MM) - CDC Travel Notices - 5 ECDC feeds (epidemiological, threats, risk assessments, avian flu, publications) - 2 WHO feeds (global news, Africa emergencies) Panel gains a Health filter tab for CDC/ECDC/WHO sources. All new domains added to RSS proxy allowlist. i18n "health" key added across all 17 locales. * feat(cache): add negative-result caching to cachedFetchJson (#466) When upstream APIs return errors (HTTP 403, 429, timeout), fetchers return null. Previously null results were not cached, causing repeated request storms against broken APIs every refresh cycle. Now caches a sentinel value ('__WM_NEG__') with a short 2-minute TTL on null results. Subsequent requests within that window get null immediately without hitting upstream. Thrown errors (transient) skip sentinel caching and retry immediately. Also filters sentinels from getCachedJsonBatch pipeline reads and fixes theater posture coalescing test (expected 2 OpenSky fetches for 2 theater query regions, not 1). * feat: convert 52 API endpoints from POST to GET for edge caching (#468) * feat: convert 52 API endpoints from POST to GET for edge caching Convert all cacheable sebuf RPC endpoints to HTTP GET with query/path parameters, enabling CDN edge caching to reduce costs. Flatten nested request types (TimeRange, PaginationRequest, BoundingBox) into scalar query params. Add path params for resource lookups (GetFredSeries, GetHumanitarianSummary, GetCountryStockIndex, GetCountryIntelBrief, GetAircraftDetails). Rewrite router with hybrid static/dynamic matching for path param support. Kept as POST: SummarizeArticle, ClassifyEvent, RecordBaselineSnapshot, GetAircraftDetailsBatch, RegisterInterest. Generated with sebuf v0.9.0 (protoc-gen-ts-client, protoc-gen-ts-server). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add rate_limited field to market response protos The rateLimited field was hand-patched into generated files on main but never declared in the proto definitions. Regenerating wiped it out, breaking the build. Now properly defined in both ListEtfFlowsResponse and ListMarketQuotesResponse protos. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove accidentally committed .planning files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Cloudflare edge caching infrastructure for api.worldmonitor.app (#471) Route web production RPC traffic through api.worldmonitor.app via fetch interceptor (installWebApiRedirect). Add default Cache-Control headers (s-maxage=300, stale-while-revalidate=60) on GET 200 responses, with no-store override for real-time endpoints (vessel snapshot). Update CORS to allow GET method. Skip Vercel bot middleware for API subdomain using hostname check (non-spoofable, replacing CF-Ray header approach). Update desktop cloud fallback to route through api.worldmonitor.app. * fix(beta): eagerly load T5-small model when beta mode is enabled BETA_MODE now couples the badge AND model loading — the summarization-beta model starts loading on startup instead of waiting for the first summarization call. * fix: move 5 path-param endpoints to query params for Vercel routing (#472) Vercel's `api/[domain]/v1/[rpc].ts` captures one dynamic segment. Path params like `/get-humanitarian-summary/SA` add an extra segment that has no matching route file, causing 404 on both OPTIONS preflight and direct requests. These endpoints were broken in production. Changes: - Remove `{param}` from 5 service.proto HTTP paths - Add `(sebuf.http.query)` annotations to request message fields - Update generated client/server code to use URLSearchParams - Update OpenAPI specs (YAML + JSON) to declare query params - Add early-return guards in 4 handlers for missing required params - Add happy.worldmonitor.app to runtime.ts redirect hosts Affected endpoints: - GET /api/conflict/v1/get-humanitarian-summary?country_code=SA - GET /api/economic/v1/get-fred-series?series_id=T10Y2Y&limit=120 - GET /api/market/v1/get-country-stock-index?country_code=US - GET /api/intelligence/v1/get-country-intel-brief?country_code=US - GET /api/military/v1/get-aircraft-details?icao24=a12345 * fix(security-advisories): route feeds through RSS proxy to avoid CORS blocks (#473) - Advisory feeds were fetched directly from the browser, hitting CORS on all 21 feeds (US State Dept, AU Smartraveller, US Embassies, ECDC, CDC, WHO). Route through /api/rss-proxy on web, keep proxyUrl for desktop. - Fix double slash in ECDC Avian Influenza URL (323//feed → 323/feed) - Add feeds.news24.com to RSS proxy allowlist (was returning 403) * feat(cache): tiered edge Cache-Control aligned to upstream TTLs (#474) * fix: move 5 path-param endpoints to query params for Vercel routing Vercel's `api/[domain]/v1/[rpc].ts` captures one dynamic segment. Path params like `/get-humanitarian-summary/SA` add an extra segment that has no matching route file, causing 404 on both OPTIONS preflight and direct requests. These endpoints were broken in production. Changes: - Remove `{param}` from 5 service.proto HTTP paths - Add `(sebuf.http.query)` annotations to request message fields - Update generated client/server code to use URLSearchParams - Update OpenAPI specs (YAML + JSON) to declare query params - Add early-return guards in 4 handlers for missing required params - Add happy.worldmonitor.app to runtime.ts redirect hosts Affected endpoints: - GET /api/conflict/v1/get-humanitarian-summary?country_code=SA - GET /api/economic/v1/get-fred-series?series_id=T10Y2Y&limit=120 - GET /api/market/v1/get-country-stock-index?country_code=US - GET /api/intelligence/v1/get-country-intel-brief?country_code=US - GET /api/military/v1/get-aircraft-details?icao24=a12345 * feat(cache): add tiered edge Cache-Control aligned to upstream TTLs Replace flat s-maxage=300 with 5 tiers (fast/medium/slow/static/no-store) mapped per-endpoint to respect upstream Redis TTLs. Adds stale-if-error resilience headers and X-No-Cache plumbing for future degraded responses. X-Cache-Tier debug header gated behind ?_debug query param. * fix(tech): use rss() for CISA feed, drop build from pre-push hook (#475) - 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 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 * fix(gateway): convert stale POST requests to GET for backwards compat (#477) Stale cached client bundles still send POST to endpoints converted to GET in PR #468, causing 404s. The gateway now parses the POST JSON body into query params and retries the match as GET. * feat(proxy): add Cloudflare edge caching for proxy.worldmonitor.app (#478) Add CDN-Cache-Control headers to all proxy endpoints so Cloudflare can cache responses at the edge independently of browser Cache-Control: - RSS: 600s edge + stale-while-revalidate=300 (browser: 300s) - UCDP: 3600s edge (matches browser) - OpenSky: 15s edge (browser: 30s) for fresher flight data - WorldBank: 1800s/86400s edge (matches browser) - Polymarket: 120s edge (matches browser) - Telegram: 10s edge (matches browser) - AIS snapshot: 2s edge (matches browser) Also fixes: - Vary header merging: sendCompressed/sendPreGzipped now merge existing Vary: Origin instead of overwriting, preventing cross-origin cache poisoning at the edge - Stale fallback responses (OpenSky, WorldBank, Polymarket, RSS) now set Cache-Control: no-store + CDN-Cache-Control: no-store to prevent edge caching of degraded responses - All no-cache branches get CDN-Cache-Control: no-store - /opensky-reset gets no-store (state-changing endpoint) * fix(sentry): add noise filters for 4 unresolved issues (#479) - Tighten AbortError filter to match "AbortError: The operation was aborted" - Filter "The user aborted a request" (normal navigation cancellation) - Filter UltraViewer service worker injection errors (/uv/service/) - Filter Huawei WebView __isInQueue__ injection * feat: configurable VITE_WS_API_URL + harden POST→GET shim (#480) * fix(gateway): harden POST→GET shim with scalar guard and size limit - Only convert string/number/boolean values to query params (skip objects, nested arrays, __proto__ etc.) to prevent prototype pollution vectors - Skip body parsing for Content-Length > 1MB to avoid memory pressure * feat: make API base URL configurable via VITE_WS_API_URL Replace hardcoded api.worldmonitor.app with VITE_WS_API_URL env var. When empty, installWebApiRedirect() is skipped entirely — relative /api/* calls stay on the same domain (local installs). When set, browser fetch is redirected to that URL. Also adds VITE_WS_API_URL and VITE_WS_RELAY_URL hostnames to APP_HOSTS allowlist dynamically. * fix(analytics): use greedy regex in PostHog ingest rewrites (#481) Vercel's :path* wildcard doesn't match trailing slashes that PostHog SDK appends (e.g. /ingest/s/?compression=...), causing 404s. Switch to :path(.*) which matches all path segments including trailing slashes. Ref: PostHog/posthog#17596 * perf(proxy): increase AIS snapshot edge TTL from 2s to 10s (#482) With 20k requests/30min (60% of proxy traffic) and per-PoP caching, a 2s edge TTL expires before the next request from the same PoP arrives, resulting in near-zero cache hits. 10s allows same-PoP dedup while keeping browser TTL at 2s for fresh vessel positions. * fix(markets): commodities panel showing stocks instead of commodities (#483) The shared circuit breaker (cacheTtlMs: 0) cached the stocks response, then the stale-while-revalidate path returned that cached stocks data for the subsequent commodities fetch. Skip SWR when caching is disabled. * feat(gateway): complete edge cache tier coverage + degraded-response policy (#484) - Add 11 missing GET routes to RPC_CACHE_TIER map (8 slow, 3 medium) - Add response-headers side-channel (WeakMap) so handlers can signal X-No-Cache without codegen changes; wire into military-flights and positive-geo-events handlers on upstream failure - Add env-controlled per-endpoint tier override (CACHE_TIER_OVERRIDE_*) for incident response rollback - Add VITE_WS_API_URL hostname allowlist (*.worldmonitor.app + localhost) - Fix fetch.bind(globalThis) in positive-events-geo.ts (deferred lambda) - Add CI test asserting every generated GET route has an explicit cache tier entry (prevents silent default-tier drift) * 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. * fix(rss): remove stale indianewsnetwork.com from proxy allowlist (#486) Feed has no <pubDate> fields and latest content is from April 2022. Not referenced in any feed config — only in the proxy domain allowlist. * feat(i18n): add Korean (한국어) localization (#487) - Add ko.json with all 1606 translation keys matching en.json structure - Register 'ko' in SUPPORTED_LANGUAGES, LANGUAGES display array, and locale map - Korean appears as 🇰🇷 한국어 in the language dropdown * feat: add Polish tv livestreams (#488) * feat(rss): add Axios (api.axios.com/feed) as US news source (#494) Add api.axios.com to proxy allowlist and CSP connect-src, register Axios feed under US category as Tier 2 mainstream source. * perf: bootstrap endpoint + polling optimization (#495) * perf: bootstrap endpoint + polling optimization (phases 3-4) Replace 15+ individual RPC calls on startup with a single /api/bootstrap batch call that fetches pre-cached data from Redis. Consolidate 6 panel setInterval timers into the central RefreshScheduler for hidden-tab awareness (10x multiplier) and adaptive backoff (up to 4x for unchanged data). Convert IntelligenceGapBadge from 10s polling to event-driven updates with 60s safety fallback. * fix(bootstrap): inline Redis + cache keys in edge function Vercel Edge Functions cannot resolve cross-directory TypeScript imports from server/_shared/. Inline getCachedJsonBatch and BOOTSTRAP_CACHE_KEYS directly in api/bootstrap.js. Add sync test to ensure inlined keys stay in sync with the canonical server/_shared/cache-keys.ts registry. * test: add Edge Function module isolation guard for all api/*.js files Prevents any Edge Function from importing from ../server/ or ../src/ which breaks Vercel builds. Scans all 12 non-helper Edge Functions. * fix(bootstrap): read unprefixed cache keys on all environments Preview deploys set VERCEL_ENV=preview which caused getKeyPrefix() to prefix Redis keys with preview:<sha>:, but handlers only write to unprefixed keys on production. Bootstrap is a read-only consumer of production cache — always read unprefixed keys. * fix(bootstrap): wire sectors hydration + add coverage guard - Wire getHydratedData('sectors') in data-loader to skip Yahoo Finance fetch when bootstrap provides sector data - Add test ensuring every bootstrap key has a getHydratedData consumer — prevents adding keys without wiring them * fix(server): resolve 25 TypeScript errors + add server typecheck to CI - _shared.ts: remove unused `delay` variable - list-etf-flows.ts: add missing `rateLimited` field to 3 return literals - list-market-quotes.ts: add missing `rateLimited` field to 4 return literals - get-cable-health.ts: add non-null assertions for regex groups and array access - list-positive-geo-events.ts: add non-null assertion for array index - get-chokepoint-status.ts: add required fields to request objects - CI: run `typecheck:api` (tsconfig.api.json) alongside `typecheck` to catch server/ TS errors before merge * feat(military): server-side military bases 125K + rate limiting (#496) * feat(military): server-side military bases with 125K entries + rate limiting (#485) Migrate military bases from 224 static client-side entries to 125,380 server-side entries stored in Redis GEO sorted sets, served via bbox-filtered GEOSEARCH endpoint with server-side clustering. Data pipeline: - Pizzint/Polyglobe: 79,156 entries (Supabase extraction) - OpenStreetMap: 45,185 entries - MIRTA: 821 entries - Curated strategic: 218 entries - 277 proximity duplicates removed Server: - ListMilitaryBases RPC with GEOSEARCH + HMGET + tier/filter/clustering - Antimeridian handling (split bbox queries) - Blue-green Redis deployment with atomic version pointer switch - geoSearchByBox() + getHashFieldsBatch() helpers in redis.ts Security: - @upstash/ratelimit: 60 req/min sliding window per IP - IP spoofing fix: prioritize x-real-ip (Vercel-injected) over x-forwarded-for - Require API key for non-browser requests (blocks unauthenticated curl/scripts) - Input validation: allowlisted types/kinds, regex country, clamped bbox/zoom Frontend: - Viewport-driven loading with bbox quantization + debounce - Server-side grid clustering at low zoom levels - Enriched popup with kind, category badges (airforce/naval/nuclear/space) - Static 224 bases kept as search fallback + initial render * fix(military): fallback to production Redis keys in preview deployments Preview deployments prefix Redis keys with `preview:{sha}:` but military bases data is seeded to unprefixed (production) keys. When the prefixed `military:bases:active` key is missing, fall back to the unprefixed key and use raw (unprefixed) keys for geo/meta lookups. * fix: remove unused 'remaining' destructure in rate-limit (TS6133) * ci: add typecheck:api to pre-push hook to catch server-side TS errors * debug(military): add X-Bases-Debug response header for preview diagnostics * fix(bases): trigger initial server fetch on map load fetchServerBases() was only called on moveend — if the user never panned/zoomed, the API was never called and only the 224 static fallback bases showed. * perf(military): debounce base fetches + upgrade edge cache to static tier (#497) - Add 300ms debounce on moveend to prevent rapid pan flooding - Fixes stale-bbox bug where pendingFetch returns old viewport data - Upgrade edge cache tier from medium (5min) to static (1hr) — bases are static infrastructure, aligned with server-side cachedFetchJson TTL - Keep error logging in catch blocks for production diagnostics * fix(cyber): make GeoIP centroid fallback jitter deterministic (#498) Replace Math.random() jitter with DJB2 hash seeded by the threat indicator (IP/URL), so the same threat always maps to the same coordinates across requests while different threats from the same country still spread out. Closes #203 Co-authored-by: Chris Chen <fuleinist@users.noreply.github.com> * fix: use cross-env for Windows-compatible npm scripts (#499) Replace direct `VAR=value command` syntax with cross-env/cross-env-shell so dev, build, test, and desktop scripts work on Windows PowerShell/CMD. Co-authored-by: facusturla <facusturla@users.noreply.github.com> * feat(live-news): add CBC News to optional North America channels (#502) YouTube handle @CBCNews with fallback video ID 5vfaDsMhCF4. * fix(bootstrap): harden hydration cache + polling review fixes (#504) - Filter null/undefined values before storing in hydration cache to prevent future consumers using !== undefined from misinterpreting null as valid data - Debounce wm:intelligence-updated event handler via requestAnimationFrame to coalesce rapid alert generation into a single render pass - Include alert IDs in StrategicRiskPanel change fingerprint so content changes are detected even when alert count stays the same - Replace JSON.stringify change detection in ServiceStatusPanel with lightweight name:status fingerprint - Document max effective refresh interval (40x base) in scheduler * fix(geo): tokenization-based keyword matching to prevent false positives (#503) * fix(geo): tokenization-based keyword matching to prevent false positives Replace String.includes() with tokenization-based Set.has() matching across the geo-tagging pipeline. Prevents false positives like "assad" matching inside "ambassador" and "hts" matching inside "rights". - Add src/utils/keyword-match.ts as single source of truth - Decompose possessives/hyphens ("Assad's" → includes "assad") - Support multi-word phrase matching ("white house" as contiguous) - Remove false-positive-prone DC keywords ('house', 'us ') - Update 9 consumer files across geo-hub, map, CII, and asset systems - Add 44 tests covering false positives, true positives, edge cases Co-authored-by: karim <mirakijka@gmail.com> Fixes #324 * fix(geo): add inflection suffix matching + fix test imports Address code review feedback: P1a: Add suffix-aware matching for plurals and demonyms so existing keyword lists don't regress (houthi→houthis, ukraine→ukrainian, iran→iranian, israel→israeli, russia→russian, taiwan→taiwanese). Uses curated suffix list + e-dropping rule to avoid false positives. P1b: Expand conflictTopics arrays in DeckGLMap and Map with demonym forms so "Iranian senate..." correctly registers as conflict topic. P2: Replace inline test functions with real module import via tsx. Tests now exercise the production keyword-match.ts directly. * fix: wire geo-keyword tests into test:data command The .mts test file wasn't covered by `node --test tests/*.test.mjs`. Add `npx tsx --test tests/*.test.mts` so test:data runs both suites. * fix: cross-platform test:data + pin tsx in devDependencies - Use tsx as test runner for both .mjs and .mts (single invocation) - Removes ; separator which breaks on Windows cmd.exe - Add tsx to devDependencies so it works in offline/CI environments * fix(geo): multi-word demonym matching + short-keyword suffix guard - Add wordMatches() for suffix-aware phrase matching so "South Korean" matches keyword "south korea" and "North Korean" matches "north korea" - Add MIN_SUFFIX_KEYWORD_LEN=4 guard so short keywords like "ai", "us", "hts" only do exact-match (prevents "ais"→"ai", "uses"→"us" false positives) - Add 5 new tests covering both fixes (58 total, all passing) * fix(geo): support plural demonyms in keyword matching Add compound suffixes (ians, eans, ans, ns, is) to handle plural demonym forms like "Iranians"→"iran", "Ukrainians"→"ukraine", "Russians"→"russia", "Israelis"→"israel". Adds 5 new tests (63 total). --------- Co-authored-by: karim <mirakijka@gmail.com> * chore: strip 61 debug console.log calls from 20 service files (#501) * chore: strip 61 debug console.log calls from services Remove development/tracing console.log statements from 20 files. These add noise to production browser consoles and increase bundle size. Preserved: all console.error (error handling) and console.warn (warnings). Preserved: debug-gated logs in runtime.ts (controlled by verbose flag). Removed: debugInjectTestEvents() from geo-convergence.ts (test-only code). Removed: logSummary()/logReport() methods that were pure console.log wrappers. * fix: remove orphaned stubs and remaining debug logs from stripped services - Remove empty logReport() method and unused startTime variable (parallel-analysis.ts) - Remove orphaned console.group/console.groupEnd pair (parallel-analysis.ts) - Remove empty logSignalSummary() export (signal-aggregator.ts) - Remove logSignalSummary import/call and 3 remaining console.logs (InsightsPanel.ts) - Remove no-op logDirectFetchBlockedOnce() and dead infrastructure (prediction/index.ts) * fix: generalize Vercel preview origin regex + include filters in bases cache key (#506) - api/_api-key.js: preview URL pattern was user-specific (-elie-), rejecting other collaborators' Vercel preview deployments. Generalized to match any worldmonitor-*.vercel.app origin. - military-bases.ts: client cache key only checked bbox/zoom, ignoring type/kind/country filters. Switching filters without panning returned stale results. Unified into single cacheKey string. * fix(prediction): filter stale/expired markets from Polymarket panel (#507) Prediction panel was showing expired markets (e.g. "Will US strike Iran on Feb 9" at 0%). Root causes: no active/archived API filters, no end_date_min param, no client-side expiry guard, and sub-market selection picking highest volume before filtering expired ones. - Add active=true, archived=false, end_date_min API params to all 3 Gamma API call sites (events, markets, probe) - Pre-filter sub-markets by closed/expired BEFORE volume selection in both fetchPredictions() and fetchCountryMarkets() - Add defense-in-depth isExpired() client-side filter on final results - Propagate endDate through all market object paths including sebuf fallback - Show expiry date in PredictionPanel UI with new .prediction-meta layout - Add "closes" i18n key to all 18 locale files - Add endDate to server handler GammaMarket/GammaEvent interfaces and map to proto closesAt field * fix(relay): guard proxy handlers against ERR_HTTP_HEADERS_SENT crash (#509) Polymarket and World Bank proxy handlers had unguarded res.writeHead() calls in error/timeout callbacks that race with the response callback. When upstream partially responds then times out, both paths write headers → process crash. Replace 5 raw writeHead+end calls with safeEnd() which checks res.headersSent before writing. * feat(breaking-news): add active alert banner with audio for critical/high RSS items (#508) RSS items classified as critical/high threat now trigger a full-width breaking news banner with audio alert, auto-dismiss (60s/30s by severity), visibility-aware timer pause, dedup, and a toggle in the Intelligence Findings dropdown. * fix(sentry): filter Android OEM WebView bridge injection errors (#510) Add ignoreErrors pattern for LIDNotifyId, onWebViewAppeared, and onGetWiFiBSSID — native bridge functions injected by Lenovo/Huawei device SDKs into Chrome Mobile WebView. No stack frames in our code. * chore: add validated telegram channels list (global + ME + Iran + cyber) (#249) * feat(conflict): add Iran Attacks map layer + strip debug logs (#511) * chore: strip 61 debug console.log calls from services Remove development/tracing console.log statements from 20 files. These add noise to production browser consoles and increase bundle size. Preserved: all console.error (error handling) and console.warn (warnings). Preserved: debug-gated logs in runtime.ts (controlled by verbose flag). Removed: debugInjectTestEvents() from geo-convergence.ts (test-only code). Removed: logSummary()/logReport() methods that were pure console.log wrappers. * fix: remove orphaned stubs and remaining debug logs from stripped services - Remove empty logReport() method and unused startTime variable (parallel-analysis.ts) - Remove orphaned console.group/console.groupEnd pair (parallel-analysis.ts) - Remove empty logSignalSummary() export (signal-aggregator.ts) - Remove logSignalSummary import/call and 3 remaining console.logs (InsightsPanel.ts) - Remove no-op logDirectFetchBlockedOnce() and dead infrastructure (prediction/index.ts) * feat(conflict): add Iran Attacks map layer Adds a new Iran-focused conflict events layer that aggregates real-time events, geocodes via 40-city lookup table, caches 15min in Redis, and renders as a toggleable DeckGL ScatterplotLayer with severity coloring. - New proto + codegen for ListIranEvents RPC - Server handler with HTML parsing, city geocoding, category mapping - Frontend service with circuit breaker - DeckGL ScatterplotLayer with severity-based color/size - MapPopup with sanitized source links - iranAttacks toggle across all variants, harnesses, and URL state * fix: resolve bootstrap 401 and 429 rate limiting on page init (#512) Same-origin browser requests don't send Origin header (per CORS spec), causing validateApiKey to reject them. Extract origin from Referer as fallback. Increase rate limit from 60 to 200 req/min to accommodate the ~50 requests fired during page initialization. * fix(relay): prevent Polymarket OOM via request deduplication (#513) Concurrent Polymarket requests for the same cache key each fired independent https.get() calls. With 12 categories × multiple clients, 740 requests piled up in 10s, all buffering response bodies → 4.1GB heap → OOM crash on Railway. Fix: in-flight promise map deduplicates concurrent requests to the same cache key. 429/error responses are negative-cached for 30s to prevent retry storms. * fix(threat-classifier): add military/conflict keyword gaps and news-to-conflict bridge (#514) Breaking news headlines like "Israel's strike on Iran" were classified as info level because the keyword classifier lacked standalone conflict phrases. Additionally, the conflict instability score depended solely on ACLED data (1-7 day lag) with no bridge from real-time breaking news. - Add 3 critical + 18 high contextual military/conflict keywords - Preserve threat classification on semantically merged clusters - Add news-derived conflict floor when ACLED/HAPI report zero signal - Upsert news events by cluster ID to prevent duplicates - Extract newsEventIndex to module-level Map for serialization safety * fix(breaking-news): let critical alerts bypass global cooldown and replace HIGH alerts (#516) Global cooldown (60s) was blocking critical alerts when a less important HIGH alert fired from an earlier RSS batch. Added priority-aware cooldown so critical alerts always break through. Banner now auto-dismisses HIGH alerts when a CRITICAL arrives. Added Iran/strikes keywords to classifier. * fix(rate-limit): increase sliding window to 300 req/min (#515) App init fires many concurrent classify-event, summarize-article, and record-baseline-snapshot calls, exhausting the 200/min limit and causing 429s. Bump to 300 as a temporary measure while client-side batching is implemented. * fix(breaking-news): fix fake pubDate fallback and filter noisy think-tank alerts (#517) Two bugs causing stale CrisisWatch article to fire as breaking alert: 1. Non-standard pubDate format ("Friday, February 27, 2026 - 12:38") failed to parse → fallback was `new Date()` (NOW) → day-old articles appeared as "just now" and passed recency gate on every fetch 2. Tier 3+ sources (think tanks) firing alerts on keyword-only matches like "War" in policy analysis titles — too noisy for breaking alerts Fix: parsePubDate() handles non-standard formats and falls back to epoch (not now). Tier 3+ sources require LLM classification to fire. * fix: make iran-events handler read-only from Redis (#518) Remove server-side LiveUAMap scraper (blocked by Cloudflare 403 on Vercel IPs). Handler now reads pre-populated Redis cache pushed from local browser scraping. Change cache tier from slow to fast to prevent CDN from serving stale empty responses for 30+ minutes. * fix(relay): Polymarket circuit breaker + concurrency limiter (OOM fix) (#519) * fix(rate-limit): increase sliding window to 300 req/min App init fires many concurrent classify-event, summarize-article, and record-baseline-snapshot calls, exhausting the 200/min limit and causing 429s. Bump to 300 as a temporary measure while client-side batching is implemented. * fix(relay): add Polymarket circuit breaker + concurrency limiter to prevent OOM Railway relay OOM crash: 280 Polymarket 429 errors in 8s, heap hit 3.7GB. Multiple unique cache keys bypassed per-key dedup, flooding upstream. - Circuit breaker: trips after 5 consecutive failures, 60s cooldown - Concurrent upstream limiter: max 3 simultaneous requests - Negative cache TTL: 30s → 60s to reduce retry frequency - Upstream slot freed on response.on('end'), not headers, preventing body buffer accumulation past the concurrency cap * fix(relay): guard against double-finalization on Polymarket timeout request.destroy() in timeout handler also fires request.on('error'), causing double decrement of polymarketActiveUpstream (counter goes negative, disabling concurrency cap) and double circuit breaker trip. Add finalized guard so decrement + failure accounting happens exactly once per request regardless of which error path fires first. * fix(threat-classifier): stagger AI classification requests to avoid Groq 429 (#520) flushBatch() fired up to 20 classifyEvent RPCs simultaneously via Promise.all, instantly hitting Groq's ~30 req/min rate limit. - Sequential execution with 2s min-gap between requests (~28 req/min) - waitForGap() enforces hard floor + jitter across batch boundaries - batchInFlight guard prevents concurrent flush loops - 429/5xx: requeue failed job (with retry cap) + remaining untouched jobs - Queue cap at 100 items with warn on overflow * fix(relay): regenerate package-lock.json with telegram dependency The lockfile was missing resolved entries for the telegram package, causing Railway to skip installation despite it being in package.json. * chore: trigger deploy to flush CDN cache for iran-events endpoint * Revert "fix(relay): regenerate package-lock.json with telegram dependency" This reverts commit |
||
|
|
b279e881a2 |
feat(rag): worker-side vector store with opt-in Headline Memory (#675)
* Add Security Advisories panel with government travel alerts (#460) * feat: add Security Advisories panel with government travel advisory feeds Adds a new panel aggregating travel/security advisories from official government foreign affairs agencies (US State Dept, AU DFAT Smartraveller, UK FCDO, NZ MFAT). Advisories are categorized by severity level (Do Not Travel, Reconsider, Caution, Normal) with filter tabs by source country. Includes summary counts, auto-refresh, and persistent caching via the existing data-freshness system. * chore: update package-lock.json * fix: event delegation, localization, and cleanup for SecurityAdvisories panel P1 fixes: - Use event delegation on this.content (bound once in constructor) instead of direct addEventListener after each innerHTML replacement — prevents memory leaks and stale listener issues on re-render - Use setContent() consistently instead of mixing with this.content.innerHTML - Add securityAdvisories translations to all 16 non-English locale files (panels name, component strings, common.all key) - Revert unrelated package-lock.json version bump P2 fixes: - Deduplicate loadSecurityAdvisories — loadIntelligenceData now calls the shared method instead of inlining duplicate fetch+set logic - Add Accept header to fetch calls for better content negotiation * feat(advisories): add US embassy alerts, CDC, ECDC, and WHO health feeds Adds 21 new advisory RSS feeds: - 13 US Embassy per-country security alerts (TH, AE, DE, UA, MX, IN, PK, CO, PL, BD, IT, DO, MM) - CDC Travel Notices - 5 ECDC feeds (epidemiological, threats, risk assessments, avian flu, publications) - 2 WHO feeds (global news, Africa emergencies) Panel gains a Health filter tab for CDC/ECDC/WHO sources. All new domains added to RSS proxy allowlist. i18n "health" key added across all 17 locales. * feat(cache): add negative-result caching to cachedFetchJson (#466) When upstream APIs return errors (HTTP 403, 429, timeout), fetchers return null. Previously null results were not cached, causing repeated request storms against broken APIs every refresh cycle. Now caches a sentinel value ('__WM_NEG__') with a short 2-minute TTL on null results. Subsequent requests within that window get null immediately without hitting upstream. Thrown errors (transient) skip sentinel caching and retry immediately. Also filters sentinels from getCachedJsonBatch pipeline reads and fixes theater posture coalescing test (expected 2 OpenSky fetches for 2 theater query regions, not 1). * feat: convert 52 API endpoints from POST to GET for edge caching (#468) * feat: convert 52 API endpoints from POST to GET for edge caching Convert all cacheable sebuf RPC endpoints to HTTP GET with query/path parameters, enabling CDN edge caching to reduce costs. Flatten nested request types (TimeRange, PaginationRequest, BoundingBox) into scalar query params. Add path params for resource lookups (GetFredSeries, GetHumanitarianSummary, GetCountryStockIndex, GetCountryIntelBrief, GetAircraftDetails). Rewrite router with hybrid static/dynamic matching for path param support. Kept as POST: SummarizeArticle, ClassifyEvent, RecordBaselineSnapshot, GetAircraftDetailsBatch, RegisterInterest. Generated with sebuf v0.9.0 (protoc-gen-ts-client, protoc-gen-ts-server). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add rate_limited field to market response protos The rateLimited field was hand-patched into generated files on main but never declared in the proto definitions. Regenerating wiped it out, breaking the build. Now properly defined in both ListEtfFlowsResponse and ListMarketQuotesResponse protos. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove accidentally committed .planning files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Cloudflare edge caching infrastructure for api.worldmonitor.app (#471) Route web production RPC traffic through api.worldmonitor.app via fetch interceptor (installWebApiRedirect). Add default Cache-Control headers (s-maxage=300, stale-while-revalidate=60) on GET 200 responses, with no-store override for real-time endpoints (vessel snapshot). Update CORS to allow GET method. Skip Vercel bot middleware for API subdomain using hostname check (non-spoofable, replacing CF-Ray header approach). Update desktop cloud fallback to route through api.worldmonitor.app. * fix(beta): eagerly load T5-small model when beta mode is enabled BETA_MODE now couples the badge AND model loading — the summarization-beta model starts loading on startup instead of waiting for the first summarization call. * fix: move 5 path-param endpoints to query params for Vercel routing (#472) Vercel's `api/[domain]/v1/[rpc].ts` captures one dynamic segment. Path params like `/get-humanitarian-summary/SA` add an extra segment that has no matching route file, causing 404 on both OPTIONS preflight and direct requests. These endpoints were broken in production. Changes: - Remove `{param}` from 5 service.proto HTTP paths - Add `(sebuf.http.query)` annotations to request message fields - Update generated client/server code to use URLSearchParams - Update OpenAPI specs (YAML + JSON) to declare query params - Add early-return guards in 4 handlers for missing required params - Add happy.worldmonitor.app to runtime.ts redirect hosts Affected endpoints: - GET /api/conflict/v1/get-humanitarian-summary?country_code=SA - GET /api/economic/v1/get-fred-series?series_id=T10Y2Y&limit=120 - GET /api/market/v1/get-country-stock-index?country_code=US - GET /api/intelligence/v1/get-country-intel-brief?country_code=US - GET /api/military/v1/get-aircraft-details?icao24=a12345 * fix(security-advisories): route feeds through RSS proxy to avoid CORS blocks (#473) - Advisory feeds were fetched directly from the browser, hitting CORS on all 21 feeds (US State Dept, AU Smartraveller, US Embassies, ECDC, CDC, WHO). Route through /api/rss-proxy on web, keep proxyUrl for desktop. - Fix double slash in ECDC Avian Influenza URL (323//feed → 323/feed) - Add feeds.news24.com to RSS proxy allowlist (was returning 403) * feat(cache): tiered edge Cache-Control aligned to upstream TTLs (#474) * fix: move 5 path-param endpoints to query params for Vercel routing Vercel's `api/[domain]/v1/[rpc].ts` captures one dynamic segment. Path params like `/get-humanitarian-summary/SA` add an extra segment that has no matching route file, causing 404 on both OPTIONS preflight and direct requests. These endpoints were broken in production. Changes: - Remove `{param}` from 5 service.proto HTTP paths - Add `(sebuf.http.query)` annotations to request message fields - Update generated client/server code to use URLSearchParams - Update OpenAPI specs (YAML + JSON) to declare query params - Add early-return guards in 4 handlers for missing required params - Add happy.worldmonitor.app to runtime.ts redirect hosts Affected endpoints: - GET /api/conflict/v1/get-humanitarian-summary?country_code=SA - GET /api/economic/v1/get-fred-series?series_id=T10Y2Y&limit=120 - GET /api/market/v1/get-country-stock-index?country_code=US - GET /api/intelligence/v1/get-country-intel-brief?country_code=US - GET /api/military/v1/get-aircraft-details?icao24=a12345 * feat(cache): add tiered edge Cache-Control aligned to upstream TTLs Replace flat s-maxage=300 with 5 tiers (fast/medium/slow/static/no-store) mapped per-endpoint to respect upstream Redis TTLs. Adds stale-if-error resilience headers and X-No-Cache plumbing for future degraded responses. X-Cache-Tier debug header gated behind ?_debug query param. * fix(tech): use rss() for CISA feed, drop build from pre-push hook (#475) - 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 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 * fix(gateway): convert stale POST requests to GET for backwards compat (#477) Stale cached client bundles still send POST to endpoints converted to GET in PR #468, causing 404s. The gateway now parses the POST JSON body into query params and retries the match as GET. * feat(proxy): add Cloudflare edge caching for proxy.worldmonitor.app (#478) Add CDN-Cache-Control headers to all proxy endpoints so Cloudflare can cache responses at the edge independently of browser Cache-Control: - RSS: 600s edge + stale-while-revalidate=300 (browser: 300s) - UCDP: 3600s edge (matches browser) - OpenSky: 15s edge (browser: 30s) for fresher flight data - WorldBank: 1800s/86400s edge (matches browser) - Polymarket: 120s edge (matches browser) - Telegram: 10s edge (matches browser) - AIS snapshot: 2s edge (matches browser) Also fixes: - Vary header merging: sendCompressed/sendPreGzipped now merge existing Vary: Origin instead of overwriting, preventing cross-origin cache poisoning at the edge - Stale fallback responses (OpenSky, WorldBank, Polymarket, RSS) now set Cache-Control: no-store + CDN-Cache-Control: no-store to prevent edge caching of degraded responses - All no-cache branches get CDN-Cache-Control: no-store - /opensky-reset gets no-store (state-changing endpoint) * fix(sentry): add noise filters for 4 unresolved issues (#479) - Tighten AbortError filter to match "AbortError: The operation was aborted" - Filter "The user aborted a request" (normal navigation cancellation) - Filter UltraViewer service worker injection errors (/uv/service/) - Filter Huawei WebView __isInQueue__ injection * feat: configurable VITE_WS_API_URL + harden POST→GET shim (#480) * fix(gateway): harden POST→GET shim with scalar guard and size limit - Only convert string/number/boolean values to query params (skip objects, nested arrays, __proto__ etc.) to prevent prototype pollution vectors - Skip body parsing for Content-Length > 1MB to avoid memory pressure * feat: make API base URL configurable via VITE_WS_API_URL Replace hardcoded api.worldmonitor.app with VITE_WS_API_URL env var. When empty, installWebApiRedirect() is skipped entirely — relative /api/* calls stay on the same domain (local installs). When set, browser fetch is redirected to that URL. Also adds VITE_WS_API_URL and VITE_WS_RELAY_URL hostnames to APP_HOSTS allowlist dynamically. * fix(analytics): use greedy regex in PostHog ingest rewrites (#481) Vercel's :path* wildcard doesn't match trailing slashes that PostHog SDK appends (e.g. /ingest/s/?compression=...), causing 404s. Switch to :path(.*) which matches all path segments including trailing slashes. Ref: PostHog/posthog#17596 * perf(proxy): increase AIS snapshot edge TTL from 2s to 10s (#482) With 20k requests/30min (60% of proxy traffic) and per-PoP caching, a 2s edge TTL expires before the next request from the same PoP arrives, resulting in near-zero cache hits. 10s allows same-PoP dedup while keeping browser TTL at 2s for fresh vessel positions. * fix(markets): commodities panel showing stocks instead of commodities (#483) The shared circuit breaker (cacheTtlMs: 0) cached the stocks response, then the stale-while-revalidate path returned that cached stocks data for the subsequent commodities fetch. Skip SWR when caching is disabled. * feat(gateway): complete edge cache tier coverage + degraded-response policy (#484) - Add 11 missing GET routes to RPC_CACHE_TIER map (8 slow, 3 medium) - Add response-headers side-channel (WeakMap) so handlers can signal X-No-Cache without codegen changes; wire into military-flights and positive-geo-events handlers on upstream failure - Add env-controlled per-endpoint tier override (CACHE_TIER_OVERRIDE_*) for incident response rollback - Add VITE_WS_API_URL hostname allowlist (*.worldmonitor.app + localhost) - Fix fetch.bind(globalThis) in positive-events-geo.ts (deferred lambda) - Add CI test asserting every generated GET route has an explicit cache tier entry (prevents silent default-tier drift) * 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. * fix(rss): remove stale indianewsnetwork.com from proxy allowlist (#486) Feed has no <pubDate> fields and latest content is from April 2022. Not referenced in any feed config — only in the proxy domain allowlist. * feat(i18n): add Korean (한국어) localization (#487) - Add ko.json with all 1606 translation keys matching en.json structure - Register 'ko' in SUPPORTED_LANGUAGES, LANGUAGES display array, and locale map - Korean appears as 🇰🇷 한국어 in the language dropdown * feat: add Polish tv livestreams (#488) * feat(rss): add Axios (api.axios.com/feed) as US news source (#494) Add api.axios.com to proxy allowlist and CSP connect-src, register Axios feed under US category as Tier 2 mainstream source. * perf: bootstrap endpoint + polling optimization (#495) * perf: bootstrap endpoint + polling optimization (phases 3-4) Replace 15+ individual RPC calls on startup with a single /api/bootstrap batch call that fetches pre-cached data from Redis. Consolidate 6 panel setInterval timers into the central RefreshScheduler for hidden-tab awareness (10x multiplier) and adaptive backoff (up to 4x for unchanged data). Convert IntelligenceGapBadge from 10s polling to event-driven updates with 60s safety fallback. * fix(bootstrap): inline Redis + cache keys in edge function Vercel Edge Functions cannot resolve cross-directory TypeScript imports from server/_shared/. Inline getCachedJsonBatch and BOOTSTRAP_CACHE_KEYS directly in api/bootstrap.js. Add sync test to ensure inlined keys stay in sync with the canonical server/_shared/cache-keys.ts registry. * test: add Edge Function module isolation guard for all api/*.js files Prevents any Edge Function from importing from ../server/ or ../src/ which breaks Vercel builds. Scans all 12 non-helper Edge Functions. * fix(bootstrap): read unprefixed cache keys on all environments Preview deploys set VERCEL_ENV=preview which caused getKeyPrefix() to prefix Redis keys with preview:<sha>:, but handlers only write to unprefixed keys on production. Bootstrap is a read-only consumer of production cache — always read unprefixed keys. * fix(bootstrap): wire sectors hydration + add coverage guard - Wire getHydratedData('sectors') in data-loader to skip Yahoo Finance fetch when bootstrap provides sector data - Add test ensuring every bootstrap key has a getHydratedData consumer — prevents adding keys without wiring them * fix(server): resolve 25 TypeScript errors + add server typecheck to CI - _shared.ts: remove unused `delay` variable - list-etf-flows.ts: add missing `rateLimited` field to 3 return literals - list-market-quotes.ts: add missing `rateLimited` field to 4 return literals - get-cable-health.ts: add non-null assertions for regex groups and array access - list-positive-geo-events.ts: add non-null assertion for array index - get-chokepoint-status.ts: add required fields to request objects - CI: run `typecheck:api` (tsconfig.api.json) alongside `typecheck` to catch server/ TS errors before merge * feat(military): server-side military bases 125K + rate limiting (#496) * feat(military): server-side military bases with 125K entries + rate limiting (#485) Migrate military bases from 224 static client-side entries to 125,380 server-side entries stored in Redis GEO sorted sets, served via bbox-filtered GEOSEARCH endpoint with server-side clustering. Data pipeline: - Pizzint/Polyglobe: 79,156 entries (Supabase extraction) - OpenStreetMap: 45,185 entries - MIRTA: 821 entries - Curated strategic: 218 entries - 277 proximity duplicates removed Server: - ListMilitaryBases RPC with GEOSEARCH + HMGET + tier/filter/clustering - Antimeridian handling (split bbox queries) - Blue-green Redis deployment with atomic version pointer switch - geoSearchByBox() + getHashFieldsBatch() helpers in redis.ts Security: - @upstash/ratelimit: 60 req/min sliding window per IP - IP spoofing fix: prioritize x-real-ip (Vercel-injected) over x-forwarded-for - Require API key for non-browser requests (blocks unauthenticated curl/scripts) - Input validation: allowlisted types/kinds, regex country, clamped bbox/zoom Frontend: - Viewport-driven loading with bbox quantization + debounce - Server-side grid clustering at low zoom levels - Enriched popup with kind, category badges (airforce/naval/nuclear/space) - Static 224 bases kept as search fallback + initial render * fix(military): fallback to production Redis keys in preview deployments Preview deployments prefix Redis keys with `preview:{sha}:` but military bases data is seeded to unprefixed (production) keys. When the prefixed `military:bases:active` key is missing, fall back to the unprefixed key and use raw (unprefixed) keys for geo/meta lookups. * fix: remove unused 'remaining' destructure in rate-limit (TS6133) * ci: add typecheck:api to pre-push hook to catch server-side TS errors * debug(military): add X-Bases-Debug response header for preview diagnostics * fix(bases): trigger initial server fetch on map load fetchServerBases() was only called on moveend — if the user never panned/zoomed, the API was never called and only the 224 static fallback bases showed. * perf(military): debounce base fetches + upgrade edge cache to static tier (#497) - Add 300ms debounce on moveend to prevent rapid pan flooding - Fixes stale-bbox bug where pendingFetch returns old viewport data - Upgrade edge cache tier from medium (5min) to static (1hr) — bases are static infrastructure, aligned with server-side cachedFetchJson TTL - Keep error logging in catch blocks for production diagnostics * fix(cyber): make GeoIP centroid fallback jitter deterministic (#498) Replace Math.random() jitter with DJB2 hash seeded by the threat indicator (IP/URL), so the same threat always maps to the same coordinates across requests while different threats from the same country still spread out. Closes #203 Co-authored-by: Chris Chen <fuleinist@users.noreply.github.com> * fix: use cross-env for Windows-compatible npm scripts (#499) Replace direct `VAR=value command` syntax with cross-env/cross-env-shell so dev, build, test, and desktop scripts work on Windows PowerShell/CMD. Co-authored-by: facusturla <facusturla@users.noreply.github.com> * feat(live-news): add CBC News to optional North America channels (#502) YouTube handle @CBCNews with fallback video ID 5vfaDsMhCF4. * fix(bootstrap): harden hydration cache + polling review fixes (#504) - Filter null/undefined values before storing in hydration cache to prevent future consumers using !== undefined from misinterpreting null as valid data - Debounce wm:intelligence-updated event handler via requestAnimationFrame to coalesce rapid alert generation into a single render pass - Include alert IDs in StrategicRiskPanel change fingerprint so content changes are detected even when alert count stays the same - Replace JSON.stringify change detection in ServiceStatusPanel with lightweight name:status fingerprint - Document max effective refresh interval (40x base) in scheduler * fix(geo): tokenization-based keyword matching to prevent false positives (#503) * fix(geo): tokenization-based keyword matching to prevent false positives Replace String.includes() with tokenization-based Set.has() matching across the geo-tagging pipeline. Prevents false positives like "assad" matching inside "ambassador" and "hts" matching inside "rights". - Add src/utils/keyword-match.ts as single source of truth - Decompose possessives/hyphens ("Assad's" → includes "assad") - Support multi-word phrase matching ("white house" as contiguous) - Remove false-positive-prone DC keywords ('house', 'us ') - Update 9 consumer files across geo-hub, map, CII, and asset systems - Add 44 tests covering false positives, true positives, edge cases Co-authored-by: karim <mirakijka@gmail.com> Fixes #324 * fix(geo): add inflection suffix matching + fix test imports Address code review feedback: P1a: Add suffix-aware matching for plurals and demonyms so existing keyword lists don't regress (houthi→houthis, ukraine→ukrainian, iran→iranian, israel→israeli, russia→russian, taiwan→taiwanese). Uses curated suffix list + e-dropping rule to avoid false positives. P1b: Expand conflictTopics arrays in DeckGLMap and Map with demonym forms so "Iranian senate..." correctly registers as conflict topic. P2: Replace inline test functions with real module import via tsx. Tests now exercise the production keyword-match.ts directly. * fix: wire geo-keyword tests into test:data command The .mts test file wasn't covered by `node --test tests/*.test.mjs`. Add `npx tsx --test tests/*.test.mts` so test:data runs both suites. * fix: cross-platform test:data + pin tsx in devDependencies - Use tsx as test runner for both .mjs and .mts (single invocation) - Removes ; separator which breaks on Windows cmd.exe - Add tsx to devDependencies so it works in offline/CI environments * fix(geo): multi-word demonym matching + short-keyword suffix guard - Add wordMatches() for suffix-aware phrase matching so "South Korean" matches keyword "south korea" and "North Korean" matches "north korea" - Add MIN_SUFFIX_KEYWORD_LEN=4 guard so short keywords like "ai", "us", "hts" only do exact-match (prevents "ais"→"ai", "uses"→"us" false positives) - Add 5 new tests covering both fixes (58 total, all passing) * fix(geo): support plural demonyms in keyword matching Add compound suffixes (ians, eans, ans, ns, is) to handle plural demonym forms like "Iranians"→"iran", "Ukrainians"→"ukraine", "Russians"→"russia", "Israelis"→"israel". Adds 5 new tests (63 total). --------- Co-authored-by: karim <mirakijka@gmail.com> * chore: strip 61 debug console.log calls from 20 service files (#501) * chore: strip 61 debug console.log calls from services Remove development/tracing console.log statements from 20 files. These add noise to production browser consoles and increase bundle size. Preserved: all console.error (error handling) and console.warn (warnings). Preserved: debug-gated logs in runtime.ts (controlled by verbose flag). Removed: debugInjectTestEvents() from geo-convergence.ts (test-only code). Removed: logSummary()/logReport() methods that were pure console.log wrappers. * fix: remove orphaned stubs and remaining debug logs from stripped services - Remove empty logReport() method and unused startTime variable (parallel-analysis.ts) - Remove orphaned console.group/console.groupEnd pair (parallel-analysis.ts) - Remove empty logSignalSummary() export (signal-aggregator.ts) - Remove logSignalSummary import/call and 3 remaining console.logs (InsightsPanel.ts) - Remove no-op logDirectFetchBlockedOnce() and dead infrastructure (prediction/index.ts) * fix: generalize Vercel preview origin regex + include filters in bases cache key (#506) - api/_api-key.js: preview URL pattern was user-specific (-elie-), rejecting other collaborators' Vercel preview deployments. Generalized to match any worldmonitor-*.vercel.app origin. - military-bases.ts: client cache key only checked bbox/zoom, ignoring type/kind/country filters. Switching filters without panning returned stale results. Unified into single cacheKey string. * fix(prediction): filter stale/expired markets from Polymarket panel (#507) Prediction panel was showing expired markets (e.g. "Will US strike Iran on Feb 9" at 0%). Root causes: no active/archived API filters, no end_date_min param, no client-side expiry guard, and sub-market selection picking highest volume before filtering expired ones. - Add active=true, archived=false, end_date_min API params to all 3 Gamma API call sites (events, markets, probe) - Pre-filter sub-markets by closed/expired BEFORE volume selection in both fetchPredictions() and fetchCountryMarkets() - Add defense-in-depth isExpired() client-side filter on final results - Propagate endDate through all market object paths including sebuf fallback - Show expiry date in PredictionPanel UI with new .prediction-meta layout - Add "closes" i18n key to all 18 locale files - Add endDate to server handler GammaMarket/GammaEvent interfaces and map to proto closesAt field * fix(relay): guard proxy handlers against ERR_HTTP_HEADERS_SENT crash (#509) Polymarket and World Bank proxy handlers had unguarded res.writeHead() calls in error/timeout callbacks that race with the response callback. When upstream partially responds then times out, both paths write headers → process crash. Replace 5 raw writeHead+end calls with safeEnd() which checks res.headersSent before writing. * feat(breaking-news): add active alert banner with audio for critical/high RSS items (#508) RSS items classified as critical/high threat now trigger a full-width breaking news banner with audio alert, auto-dismiss (60s/30s by severity), visibility-aware timer pause, dedup, and a toggle in the Intelligence Findings dropdown. * fix(sentry): filter Android OEM WebView bridge injection errors (#510) Add ignoreErrors pattern for LIDNotifyId, onWebViewAppeared, and onGetWiFiBSSID — native bridge functions injected by Lenovo/Huawei device SDKs into Chrome Mobile WebView. No stack frames in our code. * chore: add validated telegram channels list (global + ME + Iran + cyber) (#249) * feat(conflict): add Iran Attacks map layer + strip debug logs (#511) * chore: strip 61 debug console.log calls from services Remove development/tracing console.log statements from 20 files. These add noise to production browser consoles and increase bundle size. Preserved: all console.error (error handling) and console.warn (warnings). Preserved: debug-gated logs in runtime.ts (controlled by verbose flag). Removed: debugInjectTestEvents() from geo-convergence.ts (test-only code). Removed: logSummary()/logReport() methods that were pure console.log wrappers. * fix: remove orphaned stubs and remaining debug logs from stripped services - Remove empty logReport() method and unused startTime variable (parallel-analysis.ts) - Remove orphaned console.group/console.groupEnd pair (parallel-analysis.ts) - Remove empty logSignalSummary() export (signal-aggregator.ts) - Remove logSignalSummary import/call and 3 remaining console.logs (InsightsPanel.ts) - Remove no-op logDirectFetchBlockedOnce() and dead infrastructure (prediction/index.ts) * feat(conflict): add Iran Attacks map layer Adds a new Iran-focused conflict events layer that aggregates real-time events, geocodes via 40-city lookup table, caches 15min in Redis, and renders as a toggleable DeckGL ScatterplotLayer with severity coloring. - New proto + codegen for ListIranEvents RPC - Server handler with HTML parsing, city geocoding, category mapping - Frontend service with circuit breaker - DeckGL ScatterplotLayer with severity-based color/size - MapPopup with sanitized source links - iranAttacks toggle across all variants, harnesses, and URL state * fix: resolve bootstrap 401 and 429 rate limiting on page init (#512) Same-origin browser requests don't send Origin header (per CORS spec), causing validateApiKey to reject them. Extract origin from Referer as fallback. Increase rate limit from 60 to 200 req/min to accommodate the ~50 requests fired during page initialization. * fix(relay): prevent Polymarket OOM via request deduplication (#513) Concurrent Polymarket requests for the same cache key each fired independent https.get() calls. With 12 categories × multiple clients, 740 requests piled up in 10s, all buffering response bodies → 4.1GB heap → OOM crash on Railway. Fix: in-flight promise map deduplicates concurrent requests to the same cache key. 429/error responses are negative-cached for 30s to prevent retry storms. * fix(threat-classifier): add military/conflict keyword gaps and news-to-conflict bridge (#514) Breaking news headlines like "Israel's strike on Iran" were classified as info level because the keyword classifier lacked standalone conflict phrases. Additionally, the conflict instability score depended solely on ACLED data (1-7 day lag) with no bridge from real-time breaking news. - Add 3 critical + 18 high contextual military/conflict keywords - Preserve threat classification on semantically merged clusters - Add news-derived conflict floor when ACLED/HAPI report zero signal - Upsert news events by cluster ID to prevent duplicates - Extract newsEventIndex to module-level Map for serialization safety * fix(breaking-news): let critical alerts bypass global cooldown and replace HIGH alerts (#516) Global cooldown (60s) was blocking critical alerts when a less important HIGH alert fired from an earlier RSS batch. Added priority-aware cooldown so critical alerts always break through. Banner now auto-dismisses HIGH alerts when a CRITICAL arrives. Added Iran/strikes keywords to classifier. * fix(rate-limit): increase sliding window to 300 req/min (#515) App init fires many concurrent classify-event, summarize-article, and record-baseline-snapshot calls, exhausting the 200/min limit and causing 429s. Bump to 300 as a temporary measure while client-side batching is implemented. * fix(breaking-news): fix fake pubDate fallback and filter noisy think-tank alerts (#517) Two bugs causing stale CrisisWatch article to fire as breaking alert: 1. Non-standard pubDate format ("Friday, February 27, 2026 - 12:38") failed to parse → fallback was `new Date()` (NOW) → day-old articles appeared as "just now" and passed recency gate on every fetch 2. Tier 3+ sources (think tanks) firing alerts on keyword-only matches like "War" in policy analysis titles — too noisy for breaking alerts Fix: parsePubDate() handles non-standard formats and falls back to epoch (not now). Tier 3+ sources require LLM classification to fire. * fix: make iran-events handler read-only from Redis (#518) Remove server-side LiveUAMap scraper (blocked by Cloudflare 403 on Vercel IPs). Handler now reads pre-populated Redis cache pushed from local browser scraping. Change cache tier from slow to fast to prevent CDN from serving stale empty responses for 30+ minutes. * fix(relay): Polymarket circuit breaker + concurrency limiter (OOM fix) (#519) * fix(rate-limit): increase sliding window to 300 req/min App init fires many concurrent classify-event, summarize-article, and record-baseline-snapshot calls, exhausting the 200/min limit and causing 429s. Bump to 300 as a temporary measure while client-side batching is implemented. * fix(relay): add Polymarket circuit breaker + concurrency limiter to prevent OOM Railway relay OOM crash: 280 Polymarket 429 errors in 8s, heap hit 3.7GB. Multiple unique cache keys bypassed per-key dedup, flooding upstream. - Circuit breaker: trips after 5 consecutive failures, 60s cooldown - Concurrent upstream limiter: max 3 simultaneous requests - Negative cache TTL: 30s → 60s to reduce retry frequency - Upstream slot freed on response.on('end'), not headers, preventing body buffer accumulation past the concurrency cap * fix(relay): guard against double-finalization on Polymarket timeout request.destroy() in timeout handler also fires request.on('error'), causing double decrement of polymarketActiveUpstream (counter goes negative, disabling concurrency cap) and double circuit breaker trip. Add finalized guard so decrement + failure accounting happens exactly once per request regardless of which error path fires first. * fix(threat-classifier): stagger AI classification requests to avoid Groq 429 (#520) flushBatch() fired up to 20 classifyEvent RPCs simultaneously via Promise.all, instantly hitting Groq's ~30 req/min rate limit. - Sequential execution with 2s min-gap between requests (~28 req/min) - waitForGap() enforces hard floor + jitter across batch boundaries - batchInFlight guard prevents concurrent flush loops - 429/5xx: requeue failed job (with retry cap) + remaining untouched jobs - Queue cap at 100 items with warn on overflow * fix(relay): regenerate package-lock.json with telegram dependency The lockfile was missing resolved entries for the telegram package, causing Railway to skip installation despite it being in package.json. * chore: trigger deploy to flush CDN cache for iran-events endpoint * Revert "fix(relay): regenerate package-lock.json with telegram dependency" This reverts commit |
||
|
|
18c03b0839 |
fix(country-intel): align strike/aviation matching with CII bounds fallback (#677)
* fix(country-intel): align strike/aviation matching with CII bounds fallback
isInCountry() returned false when GeoJSON polygon precision missed coastal
coords (e.g. Dubai), with no bounding-box fallback — unlike the CII path
which uses coordsToBoundsCountry(). Now falls through to COUNTRY_BOUNDS
when precise geometry returns false.
Aviation disruption signals used raw string match ("uae" !== "united arab
emirates"). Switched to coordinate-based isInCountry(d.lat, d.lon, code).
Also gitignore seed-iran-events.mjs and iran-events-latest.json.
* chore: bump iran-events cache-bust to v6 after data reimport
|
||
|
|
45b00e9d7d |
feat(settings): badge pulse animation with settings toggle (#676)
* feat: animate panel count badge on new data arrival * feat(settings): gate badge pulse animation behind toggle (off by default) PR #671 adds CSS pulse on panel count badges. This commit gates it behind a new `badgeAnimation` setting in UnifiedSettings so users opt-in. Adds i18n keys for all 18 locales. --------- Co-authored-by: jeronlxj <jeronliaw@u.nus.edu> |
||
|
|
98150d639d |
feat: persist OREF history to Redis + retry bootstrap (#674)
* feat: persist OREF history to Redis + retry bootstrap on failure OREF history was lost on every container restart — single curl call with no retry, no persistence. Panel showed "0 alerts" until history re-accumulated over hours. Changes to scripts/ais-relay.cjs: - Add Upstash Redis REST helpers (upstashGet/upstashSet) using https.request with HTTPS-only validation, 5s timeout, never-throw semantics - Persist history to Redis after each poll mutation (version-deduped, concurrent-guarded, 200-wave hard cap, 7d TTL matching purge window) - Bootstrap from Redis first on startup (schema validation, 7d purge filter, 24h count recompute, lastAlertsJson seeding to prevent duplicate waves) - Fall through to upstream retry if Redis data is empty or all stale - Upstream retry: 3 attempts with exponential backoff + jitter (~70s budget) - Expose redisEnabled + bootstrapSource in /health endpoint - Preserves main's totalHistoryCount field and 7-day history retention Failure matrix: - UP/UP: Redis first (instant), poll refreshes + persists - UP/DOWN: Bootstrap from upstream, persist fails silently - DOWN/UP: Bootstrap from Redis cache - DOWN/DOWN: 3 retries then empty history * fix: increment persist version after upstream bootstrap to seed Redis Without this, _persistVersion stays at 0 after bootstrap, matching _lastPersistedVersion — orefPersistHistory() skips the write. A restart before any new alerts would lose all bootstrapped history. |
||
|
|
bb686afe16 |
fix(market): replace dead Yahoo Finance Gulf index tickers (#672)
5 of 7 original tickers returned 404 or null prices. Updated to: - ^TASI.SR (real Tadawul index) - DFMGI.AE (real Dubai index) - UAE (iShares MSCI UAE ETF — ADX index unavailable on Yahoo) - QAT (iShares MSCI Qatar ETF — QSE index unavailable) - GULF (WisdomTree ME Dividend — Kuwait index unavailable) - ^MSM (real Muscat index) Dropped Bahrain index (no Yahoo ticker or ETF proxy available). Currencies and oil tickers unchanged (all working). |
||
|
|
e45a19937b |
feat(market): add Gulf Economies panel with GCC indices, currencies & oil (#667)
* feat(market): add Gulf Economies panel with GCC indices, currencies & oil
Add a new panel tracking 15 Gulf/GCC financial symbols via Yahoo Finance:
- 7 stock indices (TASI, DFMGI, ADX, QSI, Bahrain, Kuwait, MSM)
- 6 currencies (SAR, AED, QAR, KWD, BHD, OMR vs USD)
- 2 oil benchmarks (WTI, Brent)
Server: new ListGulfQuotes RPC with 3-tier caching (in-memory + Redis
via cachedFetchJson + stale fallback). Uses fetchYahooQuotesBatch for
rate-limit-safe sequential fetching.
Frontend: GulfEconomiesPanel with 3 collapsible sections, sparklines,
8s delayed start, 60s polling. Registered in both full and finance
variants.
Inspired by #641 (credit: @aa5064, commit
|
||
|
|
4f3755a30f |
fix(ci): strip bundled GPU/Wayland libs from AppImage to fix black screen on non-Ubuntu distros (#666)
Tauri's `bundleMediaFramework: true` causes linuxdeploy to bundle Ubuntu's Mesa libEGL, libGLESv2, libwayland-*, and DRI drivers. On Arch-based distros with NVIDIA GPUs on Wayland, these override the host GPU drivers via LD_LIBRARY_PATH, causing EGL_BAD_ALLOC / EGL_BAD_PARAMETER → black screen. Post-build step extracts the AppImage, strips 14 GPU/Wayland lib patterns plus Mesa DRI drivers (all on the official AppImage excludelist), repackages with appimagetool 1.9.1 (SHA256-verified), verifies no banned libs remain, and re-uploads the stripped AppImage to the GitHub Release. |
||
|
|
4fa7bb09c9 |
fix(map): stabilize deck.gl layer IDs to prevent interleaved-mode null crash (#664)
Commit
|
||
|
|
e745fe0b3d |
ui(investments): redesign panel with card layout and collapsible filters (#663)
Replace table with 2-line card rows (flag+name+entity+value / country+sector+status+year). Move filters behind collapsible toggle button, add sort pill buttons. Add full .fdi-* CSS block with dark theme support, hover states, and sector badges. |
||
|
|
cae3c08436 |
feat(oref): add 1,478 Hebrew→English location translations + wire sirens into breaking news banner (#661)
- Generate static location map from eladnava/pikud-haoref-api cities.json (1,478 entries) - Lazy-load translations in oref-alerts.ts with retry on failure - Add dispatchOrefBreakingAlert() with stable dedupe key and global cooldown bypass - Wire oref siren alerts into breaking news banner on initial fetch and polling updates |
||
|
|
af4eba0711 |
feat: expand live channels with HLS support, Oceania region, and YouTube fallbacks (#660)
Add new channels: Tagesschau24, TV5 Monde Info, NRK1, Al Jazeera Balkans (Europe), Arirang News, India Today, ABP News (Asia), Kan 11 (Middle East), Arise News (Africa), ABC News Australia (Oceania). Add 15 HLS direct streams for desktop playback. Add verified YouTube fallback IDs for abc-news-au, arise-news, nhk-world. Add regionOceania translations to all 18 locale files. Remove n1-bih (credential-leaking HLS URL). |
||
|
|
068c0e0d80 |
feat(api): add cache-purge edge function for admin Redis cache invalidation (#657)
After deploying fixes, Vercel CDN edge cache can serve stale responses for 15-30 min. This endpoint provides programmatic Redis cache purge so the origin data is fresh when CDN TTL expires or on redeploy. Auth: Bearer RELAY_SHARED_SECRET Methods: POST (purge), OPTIONS (CORS preflight) Safety: blocklist (rl:, __), durable-data guard for patterns, max 200 deletions, timing-safe token comparison, audit logging |
||
|
|
a6372796a6 |
fix(oref): add static Hebrew→English translations for common alert types (#655)
AI translation via summarize-article hits 429 rate limits, leaving history waves in Hebrew. Adds a static lookup map for ~20 common OREF phrases (alert types, instructions, categories) that applies instantly without any API call. AI translation is still attempted for unknown strings as a second pass. |
||
|
|
f0feec86cc |
fix(finance): restore 6 missing news categories + add finance favicons (#654)
PR #622 (server-side feed aggregation) only ported 8 of 14 finance categories to VARIANT_FEEDS. The 6 omitted categories (derivatives, fintech, regulation, institutional, analysis, gccNews) caused "UNAVAILABLE" panels on finance.worldmonitor.app. Restores all feeds to match the pre-migration frontend config exactly. Also adds missing public/favico/finance/ directory (404s on favicon). |
||
|
|
c6b94a55bf |
fix(oref): grab newest history records and preserve bootstrap data (#653)
OREF AlertsHistory.json returns records newest-first, but the bootstrap used .slice(-500) which took the oldest 500 — all outside the 24h window. The poll loop then purged them all, leaving historyCount24h = 0. Three fixes: - Use .slice(0, 500) to take the newest 500 records from OREF history - Extend history purge from 24h to 7 days so bootstrap data persists - Add totalHistoryCount field for badge fallback when 24h count is zero |
||
|
|
fce5f336e1 |
fix(server): cache hardening across 27 RPC handlers (#651)
* fix(server): cache hardening across 27 RPC handlers Stop stale data promotion, fix cache keys, add error guards and fallbacks. Critical fixes: - C1: Add Redis caching to positive-events (was uncached, 3 GDELT fetches per request) - C2: Bucket unrest cache keys by date instead of raw epoch ms - C3: Move giving summary slicing outside fetcher (was caching first caller's limits) - C4-C6: Stop stale promotion in etf-flows, stablecoin-markets, market-quotes - C7: Remove early stale return in macro-signals computeMacroSignals() High fixes: - H1: Remove slice params from displacement cache key, apply post-cache - H2: Clamp cyber pageSize before cache key construction - H3: Don't cache chokepoint failure state (upstreamUnavailable: true) - H4: Add outer try-catch to aircraft-details-batch - H5-H6: Await setCachedJson in theater-posture and usni-fleet-report - H7: Redesign UCDP cache flow — single writer, no stale promotion, post-cache country filter Medium fixes: - M1-M5: Add outer try-catch to classify-event, country-intel-brief, pizzint-status, climate-anomalies, fire-detections - M7-M9: Add in-memory stale fallback to sector-summary, internet-outages, service-statuses - M10-M14: Add keyed in-memory fallback Maps to commodity-quotes, crypto-quotes, country-stock-index, feed-digest, acled-events * fix(server): clone cached giving result + apply filters on fallback paths - giving: spread summary before slicing platforms/categories to avoid mutating the shared object returned by cachedFetchJson's inflight map - outages: extract filterOutages() and apply on catch fallback path - statuses: extract filterAndSortStatuses() and apply on catch fallback path * fix(server): enforce fallback TTL in UCDP handler Gate fallback cache usage with timestamp + ttlMs check and clear expired entries, preventing indefinitely stale data on warm instances. |
||
|
|
87f4004872 |
feat: add Oman Observer and NDTV feeds + NDTV live TV (#650)
* feat: add Oman Observer and NDTV RSS feeds + NDTV 24x7 live TV - Add Oman Observer RSS to Middle East feeds and proxy allowlist - Add NDTV Top Stories RSS to Asia feeds (client + server) - Add NDTV 24x7 YouTube live channel to Asia region * fix: move NDTV to existing asia key in server feeds (TS1117) |
||
|
|
461ece0940 |
perf(map): optimize DeckGLMap pan/zoom by deferring work off hot path (#620)
- Guard filterByTime() with layer enablement checks (skip 9/11 calls when layers disabled) - Defer getLeaves() from viewport change to click time (lazy-load cluster items on demand) - Pre-aggregate riotTimeMs in supercluster map/reduce to avoid getLeaves for riot pulse - Hoist iconMapping objects to module-level constants (stable references prevent atlas rebuild) - Precompute conflict zones GeoJSON at module level (constant data, no per-render allocation) - Remove 10 of 11 redundant ghost pick layers (pickingRadius:10 provides sufficient tolerance) - Cache datacenterSCSource to avoid redundant filter on every click/viewport change |
||
|
|
dfdc149e91 |
fix(oref): show history count in badge and stop swallowing fetch errors (#648)
Badge always showed 0 when no active sirens, even with 500+ history records from the relay. Three fixes: - Badge count falls back to historyCount24h when no active alerts - History fetch errors are now logged instead of silently swallowed - Shows immediate history summary while full wave data loads |
||
|
|
88215cb517 | feat: add Redis caching for GPS jamming data (#646) | ||
|
|
17bbc49467 | fix: remove accidental intelhq submodule entry (#640) | ||
|
|
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 |
||
|
|
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 |
||
|
|
6cea10d16c |
fix(sentry): null guards for classList teardown crashes + noise filters + regex fix (#637)
- Guard document.body?.classList in event handlers that fire during page teardown (visibilitychange, resize, mouseup) — fixes WORLDMONITOR-40/6Y/6Z - Add 7 ignoreErrors patterns for third-party noise: Safari videoTrack, deck.gl setProps, Facebook/WeChat injections, media abort, regex injection - Fix beforeSend chunk-hash regex to include underscores ([A-Za-z0-9_-]) so deck.gl errors from hashes like BPZ27_H4 are properly suppressed |