* feat(aviation): add NOTAM closure detection via ICAO API
Adds international airport closure detection via ICAO NOTAMs:
- New fetchNotamClosures() queries ICAO realtime-notams endpoint
- Detects closures via Q-codes (FA/AH/AL/AW/AC/AM) and text patterns
- Batches airports in groups of 20 per API call
- 4-hour cache TTL via cachedFetchJson (stampede-safe)
- NOTAM closures override existing AviationStack alerts for same airport
- Graceful: no ICAO_API_KEY env var = silently skipped
To activate: set ICAO_API_KEY env var (register at dataservices.icao.int)
* feat(settings): add ICAO_API_KEY to desktop app settings
Adds ICAO NOTAM API key to the desktop settings UI:
- Rust: SUPPORTED_SECRET_KEYS [23→24]
- TypeScript: RuntimeSecretKey + RuntimeFeatureId unions
- Feature definition: 'icaoNotams' in Tracking & Sensing category
- Settings UI: label, signup URL, analytics name
* feat(aviation): limit NOTAM queries to MENA airports only
Per user request, ICAO NOTAM closure detection is scoped to
Middle East airports only (region='mena', ~35 airports).
This reduces API calls (2 batches vs 5) and focuses on the
region where closures are most relevant.
* fix(aviation): align NOTAM cache TTL to 30 min (matching FAA/intl)
Register the key in Rust keychain (SUPPORTED_SECRET_KEYS), frontend
RuntimeSecretKey type, feature toggle, and settings UI under
"Tracking & Sensing" category.
* 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(feeds): replace 25 dead/stale RSS URLs and add feed validation script
- Replace 16 dead feeds (404/403/timeout) with working alternatives
(Google News proxies or corrected direct RSS endpoints)
- Replace 6 empty feeds with correct RSS paths (VnExpress, Tuoi Tre,
Live Science, Greater Good, News24, ScienceDaily)
- Replace 3 stale feeds (CNN World, TVN24, Layoffs.fyi) with active sources
- Remove Disrupt Africa (inactive since Jan 2024)
- Add scripts/validate-rss-feeds.mjs to check all 420 feeds
- Add test:feeds npm script
* feat(live-news): use stable CDN HLS feeds for desktop native playback
Direct HLS feeds bypass YouTube's expiring tokenized URLs and iframe
cookie issues on WKWebView. 10 channels (Sky, DW, France24, Euronews,
Al Arabiya, Al Jazeera, CBS News, TRT World, Sky News Arabia, Al Hadath)
now play via native <video> on desktop with automatic YouTube fallback
when CDN feeds are down (5-min cooldown).
Also:
- Fix euronews handle typo (@euabortnews → @euronews)
- Fix TRT World handle (@taborrtworld → @TRTWorld)
- Add fallbackVideoId to CBS News, Sky News Arabia, TRT World
- Extract hlsManifestUrl from YouTube API for non-mapped channels
- Add sidecar /api/youtube-embed endpoint (auth-exempt for iframes)
- Switch webcam/embed iframes from cloud to local sidecar origin
- CSP: allow frame-src http://127.0.0.1:* for sidecar embeds
- Remove legacy WEBKIT_FORCE_SANDBOX env var (deprecated in WebKitGTK)
- Add 37 tests covering HLS map integrity, decision tree ordering,
cooldown logic, race safety, service layer, sidecar endpoint, and CSP
* fix(summarization): pass panelId as geoContext to prevent Redis cache key collision
When breaking news appeared across multiple panels (World, US, Europe,
Middle East), all panels generated identical cache keys because geoContext
was always undefined. The first panel's summary was served to all others.
* fix(desktop): sidecar embed autoplay, webcam fullscreen, optional channel fallbacks
- Sidecar YouTube embed: use mute param (not hardcoded), add play overlay
for WKWebView autoplay fallback, add postMessage bridge for play/pause/
mute/unmute commands matching the cloud embed handler
- Webcam iframes: only set allowFullscreen on web to prevent grid-breaking
fullscreen on desktop click
- Optional channels: add fallbackVideoId + useFallbackOnly for livenow-fox,
abc-news, nbc-news, wion so they play instead of showing "not currently live"
- Tests: 9 new assertions covering mute param, postMessage bridge, play
overlay, yt-ready message, and optional channel fallback coverage (46 total)
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 AppImage bundles GStreamer from CI (Ubuntu 24.04, GStreamer 1.24).
Previously, host plugin directories (/usr/lib/gstreamer-1.0/) were
appended as fallback. This caused ABI version mismatches — host plugins
compiled against a different GStreamer version fail with undefined symbol
errors (gst_util_floor_log2, mpg123_open_handle64, etc.), leaving WebKit
without usable codecs for YouTube playback.
Since PR #434 installs the full GStreamer codec suite on CI, the AppImage
is fully self-contained. Remove the host fallback and block host plugin
scanning to prevent ABI conflicts across distro GStreamer versions.
WebKitGTK promotes iframes, <video>, and canvas elements to GPU-textured
compositing layers. In VMs (Apple Virtualization.framework, QEMU, VMware,
etc.) the virtio-gpu driver often only supports 2D or limited GL, so GBM
buffer allocation for compositing layers fails silently — rendering
iframe/video content as black while the main page works fine.
Detect VM environments via /proc/cpuinfo hypervisor flag and sys_vendor
strings, then set WEBKIT_DISABLE_COMPOSITING_MODE=1 and
LIBGL_ALWAYS_SOFTWARE=1 to force software rendering for all content.
Native Linux machines with real GPUs are unaffected.
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
Linux had no keyring backend feature enabled — keyring v3 fell back to
in-memory mock store. Secrets appeared to save but were lost on restart.
Added `linux-native-sync-persistent` (kernel keyutils + D-Bus Secret
Service combo) and `crypto-rust` for Secret Service encryption. This
uses GNOME Keyring or KDE Wallet on desktop Linux for persistent storage.
- Show rate-limited message instead of generic "Failed to load" on Markets,
ETF, Commodities, and Sector panels when Yahoo returns 429
- fetchYahooQuotesBatch returns rateLimited flag; early-exit after 3 misses
- ETF panel skips retry loop when rate-limited, shows specific i18n message
- Fallback Finnhub symbols through Yahoo when API key missing
- 401-retry in runtime fetch patch for stale sidecar token after restart
- diagFetch auth helper for settings window diagnostic endpoints
- Verbose toggle writes to writable dataDir instead of read-only app bundle
* 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.
* feat: add WTO trade policy service with 4 RPC endpoints and TradePolicyPanel
Adds a new `trade` RPC domain backed by the WTO API (apiportal.wto.org) for
trade policy intelligence: quantitative restrictions, tariff timeseries,
bilateral trade flows, and SPS/TBT barrier notifications.
New files: 6 protos, generated server/client, 4 server handlers + shared WTO
fetch utility, client service with circuit breakers, TradePolicyPanel (4 tabs),
and full API key infrastructure (Rust keychain, sidecar, runtime config).
Panel registered for FULL and FINANCE variants with data loader integration,
command palette entry, status panel tracking, data freshness monitoring, and
i18n across all 17 locale files.
https://claude.ai/code/session_01HZXyoQp6xK3TX8obDzv6Ye
* chore: update package-lock.json
https://claude.ai/code/session_01HZXyoQp6xK3TX8obDzv6Ye
* fix: move tab click listener to constructor to prevent leak
The delegated click handler was added inside render(), which runs
on every data update (4× per load cycle). Since the listener targets
this.content (a persistent container), each call stacked a duplicate
handler. Moving it to the constructor binds it exactly once.
https://claude.ai/code/session_01HZXyoQp6xK3TX8obDzv6Ye
---------
Co-authored-by: Claude <noreply@anthropic.com>
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.
- Defer YouTube player init via IntersectionObserver + requestIdleCallback
gate with clickable placeholder (no eager iframe_api load)
- Stagger loadAllData() into 3 priority tiers: critical (immediate),
important (after rAF yield), deferred (requestIdleCallback fire-and-forget)
- Move DeckGL supercluster rebuilds into map 'load' callback
- Cancel deferred tier-3 callbacks on App destroy (prevents post-teardown work)
- Add bot-check detection with YouTube sign-in window for desktop (Tauri)
- Safe DOM construction for all new UI paths (no innerHTML with user data)
* security: block SSRF and enforce global auth on sidecar endpoints
Addresses trust boundary vulnerabilities in the desktop sidecar's
locally-exposed API server (127.0.0.1:46123) reported in
"Breaking the Trust Boundary in a 14k Star OSINT Dashboard":
- SSRF protection on /api/rss-proxy: block private/reserved IPs
(127.x, 10.x, 172.16-31.x, 192.168.x, 169.254.x, multicast),
validate DNS resolution to prevent rebinding, reject non-http(s)
protocols and URLs with embedded credentials
- Global auth gate: move LOCAL_API_TOKEN check above ALL endpoints
so /api/rss-proxy, /api/local-status, /api/local-traffic-log,
/api/local-debug-toggle, and /api/register-interest now require
authentication (only /api/service-status health check is exempt)
- Cryptographic token generation: replace RandomState-based token
in main.rs with getrandom crate (OS-backed CSPRNG, 32 bytes)
- Traffic log privacy: strip query strings from logged paths to
prevent leaking feed URLs and user research patterns
- CORS hardening: tighten worldmonitor.app origin regex from
(.*\.)? to ([a-z0-9-]+\.)? to block multi-level subdomain spoofing
- 10 new security tests covering auth enforcement on every endpoint,
SSRF blocking for private IPs/localhost/non-http/credentials,
health check exemption, and traffic log sanitization
https://claude.ai/code/session_018vNVfwPh25tbZmtiX66KxP
* security: pin resolved IP in rss-proxy to close TOCTOU DNS rebinding window
isSafeUrl() now returns the resolved addresses, and fetchWithTimeout()
accepts a resolvedAddress option that bypasses runtime DNS via a custom
lookup callback (HTTPS) or URL rewrite with Host header (HTTP).
The rss-proxy handler threads the first validated IPv4 through, so the
TCP connection is guaranteed to reach the same IP that passed the
private-range check.
https://claude.ai/code/session_018vNVfwPh25tbZmtiX66KxP
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: add User-Agent and Cloudflare 403 detection to all secret validation probes
Sidecar validation probes were missing User-Agent headers, causing
Cloudflare-fronted APIs (e.g. Wingbits) to return 403 which was
incorrectly treated as an auth rejection. Added CHROME_UA to all 13
probes and isCloudflare403() helper to soft-pass CDN blocks.
* fix: open external links in system browser on Tauri desktop
Tauri WKWebView/WebView2 traps target="_blank" navigation, so news
links and other external URLs silently fail to open. Added a global
capture-phase click interceptor that routes cross-origin links through
the existing open_url Tauri command, falling back to window.open.
* feat(live): add 35 optional channels across 5 regions with tab-based browse UI
Users can now browse and add live YouTube news channels from North America,
Europe, Latin America, Asia, and Africa via a tab-based region card grid
in the Manage Channels modal. Cards are add-only (removal via existing
edit/confirm flow). All dynamic text uses DOM API for XSS safety.
* feat(live): custom channel management — add/remove/reorder, standalone window, i18n
- Standalone channel management window (?live-channels=1) with list, add form, restore defaults
- LIVE panel: gear icon opens channel management; channel tabs reorderable via DnD
- Row click to edit; custom modal for delete confirmation (no window.confirm)
- i18n for all locales (manage, addChannel, youtubeHandle, displayName, etc.)
- UI: margin between channel list and add form in management window
- settings-window: panel display settings comment in English
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(tauri): channel management in desktop app, dev base_url fix
- Add live-channels.html and live-channels-main.ts for standalone window
- Tauri: open_live_channels_window_command, close_live_channels_window, open live-channels window (WebviewUrl::App or External from base_url)
- LiveNewsPanel: in desktop runtime invoke Tauri command with base_url (window.location.origin) so dev works when Vite runs on a different port than devUrl
- Vite: add liveChannels entry to build input
- capabilities: add live-channels window
- tauri.conf: devUrl 3000 to match vite server.port
- docs: PR_LIVE_CHANNEL_MANAGEMENT.md for PR #276
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: address review issues in live channel management PR
- Revert settings button to open modal (not window.open popup)
- Revert devUrl from localhost:3000 to localhost:5173
- Guard activeChannel against empty channels (fall back to defaults)
- Escape i18n strings in innerHTML with escapeHtml() to prevent XSS
- Only store displayNameOverrides for actually renamed channels
- Use URL constructor for live-channels window URL
- Add CSP meta tag to live-channels.html
- Remove unused i18n keys (edit, editMode, done) from all locales
- Remove unused CSS classes (live-news-manage-btn/panel/wrap)
- Delete PR instruction doc (PR_LIVE_CHANNEL_MANAGEMENT.md)
---------
Co-authored-by: Masaki <yukkurihakutaku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: persist circuit breaker cache to IndexedDB across page reloads
On page reload, all 28+ circuit breaker in-memory caches are lost,
triggering 20-30 simultaneous POST requests to Vercel edge functions.
Wire the existing persistent-cache.ts (IndexedDB + localStorage +
Tauri fallback) into CircuitBreaker so every breaker automatically:
- Hydrates from IndexedDB on first execute() call (~1-5ms read)
- Writes to IndexedDB fire-and-forget on every recordSuccess()
- Falls back to stale persistent data on network failure
- Auto-disables for breakers with cacheTtlMs=0 (live pricing)
Zero consumer code changes -- all 28+ breaker call sites untouched.
Reloads within the cache TTL (default 10min) serve instantly from
IndexedDB with zero network calls.
Also adds deletePersistentCache() to persistent-cache.ts for clean
cache invalidation via clearCache().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add Playwright e2e tests for circuit breaker persistent cache
7 tests covering: IndexedDB persistence on success, hydration on new
instance, TTL expiry forcing fresh fetch, 24h stale ceiling rejection,
clearCache cleanup, cacheTtlMs=0 auto-disable, and network failure
fallback to stale persistent data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: desktop cache deletion + clearCache race condition
P1: deletePersistentCache sent empty string to write_cache_entry,
which fails Rust's serde_json::from_str (not valid JSON). Add
dedicated delete_cache_entry Tauri command that removes the key
from the in-memory HashMap and flushes to disk.
P2: clearCache() set persistentLoaded=false, allowing a concurrent
execute() to re-hydrate stale data from IndexedDB before the async
delete completed. Remove the reset — after explicit clear there is
no reason to re-hydrate from persistent storage.
* fix: default persistCache to false, fix falsy data guard
P1b: 6 breakers store Date objects (weather, aviation, ACLED,
military-flights, military-vessels, GDACS) which become strings
after JSON round-trip. Callers like MapPopup.getTimeUntil() call
date.getTime() on hydrated strings → TypeError. Change default
to false (opt-in) so persistence requires explicit confirmation
that the payload is JSON-safe.
P2: `if (!entry?.data) return` drops valid falsy payloads (0,
false, empty string). Use explicit null/undefined check instead.
* fix: address blocking review issues on circuit breaker persistence
- clearCache() nulls persistentLoadPromise to orphan in-flight hydration
- delete_cache_entry defers disk flush to exit handler (avoids 14MB sync write)
- hydratePersistentCache checks TTL before setting lastDataState to 'cached'
- deletePersistentCache resets cacheDbPromise on IDB error + logs warning
- hydration catch logs warning instead of silently swallowing
- deletePersistentCache respects isStorageQuotaExceeded() for localStorage
---------
Co-authored-by: Elias El Khoury <efk@anghami.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve AppImage crash on Ubuntu 25.10+ (GLib symbol mismatch)
The AppImage bundles GLib from the build system, but host GIO modules
(e.g. GVFS libgvfsdbus.so) compiled against a newer GLib reference
symbols like g_task_set_static_name that don't exist in the older
bundled copy, causing "undefined symbol" errors and WebKit crashes.
Set GIO_MODULE_DIR="" when running as AppImage to prevent host GIO
modules from loading against the incompatible bundled GLib. GVFS
features (network mounts, trash, MTP) are unused by this app.
Note: the CI should also be upgraded from ubuntu-22.04 to ubuntu-24.04
in .github/workflows/build-desktop.yml to ship GLib 2.80+ and extend
forward-compatibility. This requires workflows permission to push.
https://claude.ai/code/session_01J8HBrfb26GJm22MFCeGoAA
* fix(appimage): keep bundled GIO modules for Ubuntu 25.10
---------
Co-authored-by: Claude <noreply@anthropic.com>
On Windows, Tauri webviews send requests with origin
`http://tauri.localhost` (HTTP), but the CORS allowlist only permitted
`https://tauri.localhost` (HTTPS). This caused every sidecar API
request to be blocked by CORS, making the app non-functional on
Windows with a "sidecar not reachable" error.
Change the regex from `^https:` to `^https?:` so both HTTP and HTTPS
origins from tauri.localhost are accepted.
https://claude.ai/code/session_016XMWtTPfE81bitu3QEoUwy
Co-authored-by: Claude <noreply@anthropic.com>
* 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
* fix: resolve AppImage blank white screen and font crash on Linux (#238)
Disable WebKitGTK DMA-BUF renderer by default on Linux to prevent blank
white screens caused by GPU buffer allocation failures (common with
NVIDIA drivers and immutable distros like Bazzite). Add Linux-native
monospace font fallbacks (DejaVu Sans Mono, Liberation Mono) to all font
stacks so WebKitGTK font resolution doesn't hit out-of-bounds vector
access when macOS-only fonts (SF Mono, Monaco) are unavailable.
https://claude.ai/code/session_01TF2NPgSSjgenmLT2XuR5b9
* fix: consolidate monospace font stacks into --font-mono variable
- Define --font-mono in :root (main.css) and .settings-shell (settings-window.css)
- Align font stack: SF Mono, Monaco, Cascadia Code, Fira Code, DejaVu Sans Mono, Liberation Mono
- Replace 3 hardcoded JetBrains Mono stacks with var(--font-mono)
- Replace 4 hardcoded settings-window stacks with var(--font-mono)
- Fix pre-existing bug: var(--font-mono) used in 4 places but never defined
- Match index.html skeleton font stack to --font-mono
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(sentry): add noise filters for 5 non-actionable error patterns
Filter dynamic import alt phrasing, script parse errors, maplibre
style/WebGL crashes, and CustomEvent promise rejections. Also fix
beforeSend to catch short Firefox null messages like "E is null".
* fix: cache write race, settings stale key status, yahoo gate concurrency
P1: Replace async background thread cache write with synchronous fs::write
to prevent out-of-order writes and dirty flag cleared before persistence.
P2: Add WorldMonitorTab.refresh() called after loadDesktopSecrets() so
the API key badge reflects actual keychain state.
P3: Replace timestamp-based Yahoo gate with promise queue to ensure
sequential execution under concurrent callers.
* feat: add Upstash Redis shared caching to all RPC handlers + fix cache key contamination
- Add Redis L2 cache (getCachedJson/setCachedJson) to 28 RPC handlers
across all service domains (market, conflict, cyber, economic, etc.)
- Fix 10 P1 cache key contamination bugs where under-specified keys
caused cross-request data pollution (e.g. filtered requests returning
unfiltered cached data)
- Restructure list-internet-outages to cache-then-filter pattern so
country/timeRange filters always apply after cache read
- Add write_lock mutex to PersistentCache in main.rs to prevent
desktop cache write-race conditions
- Document FMP (Financial Modeling Prep) as Yahoo Finance fallback TODO
in market/v1/_shared.ts
* fix: cache-key contamination and PizzINT/GDELT partial-failure regression
- tech-events: fetch with limit=0 and cache full result, apply limit
slice after cache read to prevent low-limit requests poisoning cache
- pizzint: restore try-catch around PizzINT fetch so GDELT tension
pairs are still returned when PizzINT API is down
* fix: remove extra closing brace in pizzint try-catch
* fix: recompute conferenceCount/mappableCount after limit slice
* fix: bypass WM API key gate for registration endpoint
/api/register-interest must reach cloud without a WorldMonitor API key,
otherwise desktop users can never register (circular dependency).
* chore: apply cargo fmt formatting to main.rs
Pure formatting normalization with no logic changes. Separated from
the behavioral fix to keep git blame clean.
https://claude.ai/code/session_01RPQ1PEqxTSEG6rB5XadzEz
* fix: restrict settings-window re-focus to macOS to avoid Windows focus churn
On Windows, the Focused(true) handler on the main window calls
show()+set_focus() on the settings window, which steals focus back,
retriggering the event in a tight loop and presenting as a UI hang.
Gate the match arm with #[cfg(target_os = "macos")] (compile-time
attribute) instead of cfg!() (runtime macro) to match the convention
used by the adjacent macOS-only handlers and eliminate dead code on
non-macOS builds entirely.
https://claude.ai/code/session_01RPQ1PEqxTSEG6rB5XadzEz
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Sidecar calls Convex HTTP API directly (Vercel Attack Challenge Mode
blocks server-side proxy). CONVEX_URL read from env, not hardcoded.
- Rust injects CONVEX_URL into sidecar via option_env! (CI) / env var (dev)
- GitHub Actions passes CONVEX_URL secret to all 4 build steps
- Tighten WM tab CSS spacing so all content fits in one viewport
- Move World Monitor tab to first position in settings.html
- Add registration proxy in sidecar to bypass Vercel bot protection
- Fix sidecar RSS/registration handlers to use response.text()
- Skip empty values in loadDesktopSecrets (NO LICENSE vs LICENSED)
- Add skip-setup text to desktop config alert panel
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.
Cache inbound request body once so local handler dispatch and cloud fallback can both access the same payload. Adds regression coverage for POST fallback after a local non-OK response.