* feat(analytics): add Umami analytics via self-hosted instance
Adds Umami analytics script from abacus.worldmonitor.app and updates
CSP headers in both index.html and vercel.json to allow the script.
* feat(analytics): complete Umami integration with event tracking
- Add data-domains to index.html script to exclude dev traffic
- Add Umami script to /pro page and blog (Base.astro)
- Add TypeScript Window.umami shim to vite-env.d.ts
- Wire analytics.ts facade to Umami (replaces PostHog no-ops):
search, country clicks, map layers, panels, LLM usage, theme,
language, variant switch, webcam, download, findings, deeplinks
- Add direct callsite tracking for: settings-open, mcp-connect-attempt,
mcp-connect-success, mcp-panel-add, widget-ai-open/generate/success,
news-summarize, news-sort-toggle, live-news-fullscreen,
webcam-fullscreen, search-open (desktop/mobile/fab)
* fix(analytics): add Tauri CSP allowlist for Umami + skip programmatic layer events
- Add abacus.worldmonitor.app to Tauri CSP script-src and connect-src
so Umami loads in the desktop WebView (analytics exception to the
no-cloud-data rule — needed to know if desktop is used)
- Filter trackMapLayerToggle to user-initiated events only to avoid
inflating counts with programmatic toggles on page load
riskScores and serviceStatuses have data but permanently stale seed-meta
(no longer written by cachedFetchJson after PR #1649). ON_DEMAND_KEYS
only affects EMPTY status, not STALE_SEED. Removing their SEED_META
entries so health doesn't check freshness for keys that can't update it.
Also bumps version to 2.6.5.
* fix(csp): add commodity variant to CSP and fix iframe variant navigation
- Add commodity.worldmonitor.app to frame-src and frame-ancestors in
vercel.json and index.html CSP — was missing while all other variants
were listed
- Open variant links in new tab when app runs inside an iframe to prevent
sandbox navigation errors ("This content is blocked")
- Add allow-popups and allow-popups-to-escape-sandbox to pro page iframe
sandbox attribute
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(csp): add missing variant subdomains to tauri.conf.json frame-src
Sync tauri.conf.json CSP with index.html and vercel.json by adding
finance, commodity, and happy worldmonitor.app subdomains to frame-src.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add PR screenshots for CSP fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Elie Habib <elie.habib@gmail.com>
* feat(desktop): compile domain handlers + add in-memory sidecar cache
The sidecar was broken for all 23 sebuf/RPC domain routes because
the build script (build-sidecar-handlers.mjs) never existed on main
while package.json already referenced it. This adds the missing script
and an in-memory TTL+LRU cache so the sidecar doesn't need Upstash Redis.
- Add scripts/build-sidecar-handlers.mjs (esbuild multi-entry, 23 domains)
- Add server/_shared/sidecar-cache.ts (500 entries, 50MB max, lazy sweep)
- Modify redis.ts getCachedJson/setCachedJson to use dynamic import for
sidecar cache when LOCAL_API_MODE=tauri-sidecar (zero cost on Vercel Edge)
- Update tauri.conf.json beforeDevCommand to compile handlers
- Add gitignore pattern for compiled api/*/v1/[rpc].js
* fix(desktop): gate premium panel fetches and open footer links in browser
Skip oref-sirens and telegram-intel HTTP requests on desktop when
WORLDMONITOR_API_KEY is not present. Use absolute URLs for footer
links on desktop so the Tauri external link handler opens them in
the system browser instead of navigating within the webview.
* fix(desktop): cloud proxy, bootstrap timeouts, and panel data fixes
- Set Origin header on cloud proxy requests (fixes 401 from API key validator)
- Strip If-None-Match/If-Modified-Since headers (fixes stale 304 responses)
- Add cloud-preferred routing for market/economic/news/infrastructure/research
- Enable cloud fallback via LOCAL_API_CLOUD_FALLBACK env var in main.rs
- Increase bootstrap timeouts on desktop (8s/12s vs 3s/5s) for sidecar proxy hops
- Force per-feed RSS fallback on desktop (server digest has fewer categories)
- Add finance feeds to commodity variant (client + server)
- Remove desktop diagnostics from ServiceStatusPanel (show cloud statuses only)
- Restore DeductionPanel CSS from PR #1162
- Deduplicate repeated sidecar error logs
* feat: premium panel gating, code cleanup, and backend simplifications
Recovered stranded changes from fix/desktop-premium-error-unification.
Premium gating:
- Add premium field ('locked'|'enhanced') to PanelConfig and LayerDefinition
- Panel.showLocked() with lock icon, CTA button, and _locked guard
- PRO badge for enhanced panels when no WM API key
- Exponential backoff auto-retry on showError() (15s→30s→60s→180s cap)
- Gate oref-sirens and telegram-intel panels behind WM API key
- Lock gpsJamming and iranAttacks layer toggles, badge ciiChoropleth
- Add tauri-titlebar drag region for custom titlebar
Code cleanup:
- Extract inline CSS from AirlineIntelPanel, WorldClockPanel to panels.css
- Remove unused showGeoError() from CountryBriefPage
- Remove dead geocodeFailed/retryBtn/closeBtn locale keys (20 files)
- Clean up var names and inline styles across 6 components
Backend:
- Remove seed-meta throttle from redis.ts (unnecessary complexity)
- Risk scores: call handler functions directly instead of raw Redis reads
- Update OpenRouter model to gpt-oss-safeguard-20b:nitro
- Add direct UCDP API fetching with version probing
Config:
- Remove titleBarStyle: Overlay from tauri.conf.json
- Add build:pro and build-sidecar-handlers to build:desktop
- Remove DXB/RUH from default aviation watchlist
- Simplify reverse-geocode (remove AbortController wrapper)
* fix: cast handler requests to any for API tsconfig compat
* fix: revert stale changes that conflict with merged PRs
Reverts files to main versions where old branch changes would
overwrite intentional fixes from PRs #1134, #1138, #1144, #1154:
- news/_shared.ts: keep gemini-2.5-flash model (not stale gpt-oss)
- redis.ts: keep seed-meta throttle from PR #1138
- reverse-geocode.ts: keep AbortController timeout from PR #1134
- CountryBriefPage.ts: keep showGeoError() from PR #1134
- country-intel.ts: keep showGeoError usage from PR #1134
- get-risk-scores.ts: revert non-existent imports
- watchlist.ts: keep DXB/RUH airports from PR #1144
- locales: restore geocodeFailed/retryBtn/closeBtn keys
* fix: neutralize language, parallel override loading, fetch timeout
- Rename conflict zone from "War" to "Border Conflict", intensity high→medium
- Rewrite description to factual language (no "open war" claim)
- Load country boundary overrides in parallel with main GeoJSON
- Neutralize comments/docs: reference Natural Earth source, remove political terms
- Add 60s timeout to Natural Earth fetch script (~24MB download)
- Add trailing newline to GeoJSON override file
* fix: restore caller messages in Panel errors and vessel expansion in popups
- Move UCDP direct-fetch cooldown after successful fetch to avoid
suppressing all data for 10 minutes on a single failure
- Use caller-provided messages in showError/showRetrying instead of
discarding them; respect autoRetrySeconds parameter
- Restore cluster-toggle click handler and expandable vessel list
in military cluster popups
- Remove emrldco.com analytics script and CSP entries from index.html,
vercel.json, and tauri.conf.json
- Replace setStyle() basemap fallback with full map recreation — setStyle()
after a failed initial style load leaves MapLibre in a broken state
- Add 403/Forbidden to error detection patterns for basemap failures
- Scope fallback to pre-style-load errors only (post-load tile errors
don't warrant destroying a working map)
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.
- 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.
- 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
* 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
Linux users with NVIDIA proprietary drivers on Wayland report crashes:
"Could not create surfaceless EGL display: EGL_BAD_ALLOC. Aborting..."
WebKitGTK's web process calls eglGetPlatformDisplay with the
EGL_PLATFORM_SURFACELESS_MESA platform, which fails with NVIDIA's EGL
implementation and triggers abort(). WEBKIT_DISABLE_DMABUF_RENDERER=1
(already set) only controls buffer sharing, not EGL initialization.
Detect NVIDIA via /proc/driver/nvidia and:
- Set __NV_DISABLE_EXPLICIT_SYNC=1 to prevent Wayland flickering
- Force GDK_BACKEND=x11 on NVIDIA+Wayland (user can override)
Also bumps version to 2.5.19.
Refs: tauri-apps/tauri#9394, gitbutlerapp/gitbutler#5282
The /api/youtube/live validation endpoint may return 429 or non-JSON
responses (Vercel WAF, YouTube rate limiting). Previously this caused
res.json() to parse HTML → either throw (caught, channel added) or
return channelExists:false (blocked add with red border).
Now only blocks when the API explicitly returns 200 OK with
channelExists:false — any non-OK status or error allows the add.
Also bumps version to 2.5.13.
* chore: bump v2.5.12
## Changelog
- fix(linux): enable keyring persistence via Secret Service + keyutils (#419)
- fix(ci): use weston+XWayland for Linux smoke test (#417)
- ci: add standalone Test Linux App workflow (#414)
- ci: skip Typecheck and Lint on fork PRs (#415)
- perf: optimize Wingbits API usage and reduce unnecessary polling (#416)
* fix(linux): append host GStreamer plugins to AppImage search path
The linuxdeploy GStreamer hook force-overrides GST_PLUGIN_PATH_1_0 and
GST_PLUGIN_SYSTEM_PATH_1_0 to only contain bundled plugins from the CI
build system (Ubuntu 24.04, GStreamer 1.24). On hosts with newer
GStreamer (e.g. Arch 1.28), codec plugins like gst-libav and
fakevideosink from gst-plugins-bad are invisible — WebKit can't play
video.
Append common host GStreamer plugin directories as fallback so the
system's codec plugins are discoverable while bundled plugins retain
priority.
Also fixes:
- tauri.conf.json devUrl port mismatch (5173 → 3000) breaking desktop:dev
- live-channels-window YouTube validation allowing add on non-OK responses
* fix: sort tariff datapoints newest-first in trade policy panel
* fix: update tests broken by cachedFetchJson migration
- Restore "Strip unterminated" comment in summarize-article.ts that
tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
getCachedJson/setCachedJson patterns
* chore: bump version to 2.5.9 and make pre-push hook executable
* docs: update README with supply chain intel, universal CII, Happy Monitor, security hardening, and recent features
* feat: dynamic sidecar port with EADDRINUSE fallback
Rust probes port 46123 via TcpListener::bind; if busy, binds port 0 for
an OS-assigned ephemeral port. The actual port is stored in LocalApiState,
passed to sidecar via LOCAL_API_PORT env, and exposed to frontend via
get_local_api_port IPC command.
Frontend resolves the port lazily on first API call (with retry-on-failure
semantics) and caches it. All hardcoded 46123 references replaced with
dynamic getApiBaseUrl()/getLocalApiPort() accessors. CSP connect-src
broadened to http://127.0.0.1:* (frame-src unchanged).
* fix: scope CSP to desktop builds and eliminate port TOCTOU race
P1: Remove http://127.0.0.1:* from index.html (web build CSP). The
wildcard allowed web app JS to probe arbitrary localhost services.
Vite's htmlVariantPlugin now injects localhost CSP only when
VITE_DESKTOP_RUNTIME=1 (desktop builds).
P2: Replace Rust probe_available_port() (bind→release→spawn race)
with a confirmed port handshake. Sidecar now handles EADDRINUSE
fallback internally and writes the actual bound port to a file.
Rust polls the port file (up to 5s) to store only the confirmed port.
* fix: isSafeUrl ReferenceError — addresses scoped inside try block
`let addresses = []` was declared inside the outer `try` block but
referenced after the `catch` on line 200. `let` is block-scoped so
every request through isSafeUrl crashed with:
ReferenceError: addresses is not defined
Move the declaration before the `try` so it's in scope for the return.
Update data layer count to 36+, add Happy Monitor variant to Live Demos,
expand Cmd+K command palette description, and add trade routes to
Infrastructure section.
* feat: make intelligence alert popup opt-in via dropdown toggle
Auto-popup was interrupting users every 10s refresh cycle. Badge still
counts and pulses silently. New toggle in dropdown (default OFF) lets
users explicitly opt in to auto-popup behavior.
* chore: bump version to 2.5.5
## Changelog
### Features
- Intelligence alert popup is now opt-in (default OFF) — badge counts silently, toggle in dropdown to enable auto-popup
### Bug Fixes
- Linux: disable DMA-BUF renderer on WebKitGTK to prevent blank white screen (NVIDIA/immutable distros)
- Linux: add DejaVu Sans Mono + Liberation Mono font fallbacks for monospace rendering
- Consolidate monospace font stacks into --font-mono CSS variable (fixes undefined var bug)
- Reduce dedup coordinate rounding from 0.5° to 0.1° (~10km precision)
- Vercel build: handle missing previous deploy SHA
- Panel base class: add missing showRetrying method
- Vercel ignoreCommand shortened to fit 256-char limit
### Infrastructure
- Upstash Redis shared caching for all RPC handlers + cache key contamination fix
- Format Rust code and fix Windows focus handling
### Docs
- Community guidelines: contributing, code of conduct, security policy
- Updated .env.example
* chore: track Cargo.lock for reproducible Rust builds
* fix: update layer help popup with all current map layers
Added missing layers to the ? help popup across all 3 variants:
- Full: UCDP Events, Displacement, Spaceports, Cyber Threats, Fires,
Climate Anomalies, Critical Minerals; renamed Shipping→Ship Traffic
- Tech: Tech Events, Cyber Threats, Fires
- Finance: GCC Investments
* docs: update README with crypto prices, analytics, typography, and dedup grid fix
* fix: add /ingest to service worker NetworkOnly routes
The SW was intercepting PostHog /ingest/* requests and returning
no-response (404) because no cache match existed. Adding NetworkOnly
ensures analytics requests pass through to Vercel's rewrite proxy.
* chore: update Cargo.lock for v2.5.5
* fix: use explicit colors for findings toggle switch visibility
Rebuild the World Monitor settings tab with hero banner, license key
input, waitlist registration, and BYOK footer. Only validate API key
panels that have pending changes on save. Add local RSS proxy handler
to sidecar so desktop fetches feeds directly without cloud fallback.
Bump version to 2.5.3.
* feat: API key gating for desktop cloud fallback + registration system
Gate desktop cloud fallback behind WORLDMONITOR_API_KEY — desktop users
need a valid key for cloud access, otherwise operate local-only (sidecar).
Add email registration system via Convex DB for future key distribution.
Client-side: installRuntimeFetchPatch() checks key presence before
allowing cloud fallback, with secretsReady promise + 2s timeout.
Server-side: origin-aware validation in sebuf gateway — desktop origins
require key, web origins pass through.
- Add WORLDMONITOR_API_KEY to 3-place secret system (Rust, TS, sidecar)
- New "World Monitor" settings tab with key input + registration form
- New api/_api-key.js server-side validation (origin-aware)
- New api/register-interest.js edge function with rate limiting
- Convex DB schema + mutation for email registration storage
- CORS headers updated for X-WorldMonitor-Key + Authorization
- E2E tests for key gate (blocked without key, allowed with key)
- Deployment docs (API_KEY_DEPLOYMENT.md) + updated desktop config docs
* fix: harden worldmonitor key + registration input handling
* fix: show invalid WorldMonitor API key status
* fix: simplify key validation, trim registration checks, add env example vars
- Inline getValidKeys() in _api-key.js
- Remove redundant type checks in register-interest.js
- Simplify WorldMonitorTab status to present/missing
- Add WORLDMONITOR_VALID_KEYS and CONVEX_URL to .env.example
* feat(sidecar): integrate proto gateway bundle into desktop build
The sidecar's buildRouteTable() only discovers .js files, so the proto
gateway at api/[domain]/v1/[rpc].ts was invisible — all 45 sebuf RPCs
returned 404 in the desktop app. Wire the existing build script into
Tauri's build commands and add esbuild as an explicit devDependency.
- Ollama/LM Studio integration with auto model discovery and 4-tier fallback chain
- Settings window split into LLMs, API Keys, and Debug tabs
- Consolidated keychain vault (1 OS prompt instead of 20+)
- README expanded with privacy architecture, summarization chain docs
- CHANGELOG updated with full v2.5.0 release notes
- 5 new defense/intel RSS feeds, Koeberg nuclear plant added