[sync] Merge upstream worldmonitor v2.5.8 (72 commits)#3
Merged
Conversation
…a73#262) 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>
…ching (koala73#266) * perf: optimize WebSocket relay to prevent Railway crashes under load Key fixes: - Fix crash: guard all res.writeHead() calls against "headers already sent" (the ais-relay.cjs:740 crash from error/timeout race conditions) - Pre-serialize + pre-gzip AIS snapshots — eliminates JSON.stringify and zlib.gzip on every HTTP request to /ais/snapshot - Add upstream WebSocket backpressure: pause/resume at 4MB/512KB watermarks to prevent unbounded buffer growth and OOM - Replace O(chokepoints × vessels) disruption detection with O(chokepoints) spatial bucket lookup populated at ingest time - Cap all Maps (vessels: 50K, history: 50K, density: 5K, candidates: 1.5K) to prevent unbounded memory growth between cleanup cycles - Reduce WS fanout from every 10th to every 50th message with per-client backpressure (skip clients with >1MB buffered) - Hoist all require('https'/'http'/'zlib') to top-level (6 inline calls removed from hot request paths) - Add memory monitoring: 60s interval logs RSS/heap, emergency cache flush at 400MB RSS threshold - Health endpoint now reports memory, droppedMessages, upstreamPaused state - Delete errored WS clients from Set immediately https://claude.ai/code/session_01WFQkHftPgni8oaWtznv1v5 * fix: harden AIS relay backpressure and chokepoint accuracy --------- Co-authored-by: Claude <noreply@anthropic.com>
…oala73#263) * 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>
* Add Brasil Paralelo source One of the biggest independent media company in Brazil * Add Brasil Paralelo domain to rss-proxy.js * fix: add missing comma and https:// protocol for Brasil Paralelo feed --------- Co-authored-by: Elie Habib <elie.habib@gmail.com>
…la73#270) rsshub.app was returning non-2xx responses (likely 429 rate-limit) which were never cached, causing a thundering herd: 874 requests in 5 minutes to the same URL instead of 1. The in-flight dedup also cascaded — when waiters woke up and found no cache, they started their own fetches. - Cache all RSS responses (non-2xx with 60s TTL vs 5min for success) - Dedup waiters serve 502 on failure instead of cascading to new fetches - Log upstream error status codes for future diagnosis - Raise memory cleanup threshold to 450MB, only clear OpenSky cache
- Add 5 Nigeria news sources to Africa section (Premium Times, Vanguard, Channels TV, Daily Trust, ThisDay) - Add 5 Greek feeds with lang: 'el' for locale-aware filtering (Kathimerini, Naftemporiki, in.gr, iefimerida, Proto Thema) - Add source tiers for all new outlets - Allowlist 8 new domains in RSS proxy
…73#277) The existing scheduler skips fetches while the tab is hidden and reschedules with a 4x delay. When the user returns, they could wait up to interval x 4 (e.g. 20 min for a 5-min service) before seeing fresh data. Add flushStaleRefreshes() -- on visibilitychange, cancel pending timeouts for any service whose hidden duration exceeded its refresh interval and re-trigger them with 150ms stagger to avoid thundering herd. Services that are not stale yet keep their existing schedule. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.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>
* 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 koala73#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>
…atching (koala73#283) * fix: add request coalescing to Redis cache layer Concurrent cache misses for the same key now share a single upstream fetch instead of each triggering redundant API calls. This eliminates duplicate work within Edge Function invocations under burst traffic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: reduce AIS polling frequency from 10s to 30s Vessel positions do not change meaningfully in 10 seconds at sea. Reduces Railway relay requests by 66% with negligible UX impact. Stale threshold bumped to 45s to match the new interval. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: quantize military flights bbox cache keys to 1-degree grid Precise bounding box coordinates caused near-zero cache hit rate since every map pan/zoom produced a unique key. Snapping to a 1-degree grid lets nearby viewports share cache entries, dramatically reducing redundant OpenSky API calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: parallelize ETF chart fetches instead of sequential await loop The loop awaited each ETF chart fetch individually, blocking on every Yahoo gate delay. Using Promise.allSettled lets all 10 fetches queue concurrently through the Yahoo gate, cutting wall time from ~12s to ~6s. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add Redis pipeline batch GET to reduce round-trips Add getCachedJsonBatch() using the Upstash pipeline API to fetch multiple keys in a single HTTP call. Refactor aircraft details batch handler from 20 sequential GETs to 1 pipelined request. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add structural tests for Redis caching optimizations 18 tests covering: cachedFetchJson request coalescing (in-flight dedup, cache-before-fetch ordering, cleanup), getCachedJsonBatch pipeline API, aircraft batch handler pipeline usage, bbox grid quantization (1-degree step, expanded fetch bbox), and ETF parallel fetch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: enforce military bbox contract and add behavioral cache tests --------- Co-authored-by: Elias El Khoury <efk@anghami.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…oala73#284) * fix: add request coalescing to Redis cache layer Concurrent cache misses for the same key now share a single upstream fetch instead of each triggering redundant API calls. This eliminates duplicate work within Edge Function invocations under burst traffic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: reduce AIS polling frequency from 10s to 30s Vessel positions do not change meaningfully in 10 seconds at sea. Reduces Railway relay requests by 66% with negligible UX impact. Stale threshold bumped to 45s to match the new interval. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: quantize military flights bbox cache keys to 1-degree grid Precise bounding box coordinates caused near-zero cache hit rate since every map pan/zoom produced a unique key. Snapping to a 1-degree grid lets nearby viewports share cache entries, dramatically reducing redundant OpenSky API calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: parallelize ETF chart fetches instead of sequential await loop The loop awaited each ETF chart fetch individually, blocking on every Yahoo gate delay. Using Promise.allSettled lets all 10 fetches queue concurrently through the Yahoo gate, cutting wall time from ~12s to ~6s. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add Redis pipeline batch GET to reduce round-trips Add getCachedJsonBatch() using the Upstash pipeline API to fetch multiple keys in a single HTTP call. Refactor aircraft details batch handler from 20 sequential GETs to 1 pipelined request. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add structural tests for Redis caching optimizations 18 tests covering: cachedFetchJson request coalescing (in-flight dedup, cache-before-fetch ordering, cleanup), getCachedJsonBatch pipeline API, aircraft batch handler pipeline usage, bbox grid quantization (1-degree step, expanded fetch bbox), and ETF parallel fetch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: enforce military bbox contract and add behavioral cache tests --------- Co-authored-by: Elias El Khoury <efk@anghami.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…oala73#275) * fix: skip AIS polling when browser tab is backgrounded Add two layers of defence to avoid wasting Railway relay bandwidth on hidden tabs: 1. document.hidden guard in pollSnapshot() — skips fetch when tab is not visible (interval keeps ticking so first tick after focus polls immediately) 2. visibilitychange listener — pauses/resumes the interval timer entirely so no callbacks fire while backgrounded Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: align cache TTLs with upstream data source refresh rates Climate anomalies: 30min → 3h Source: Open-Meteo Archive API uses ERA5 reanalysis data with a 2–7 day lag (https://open-meteo.com/en/docs/historical-weather-api). 30-min polling gains nothing against week-old data. Fire detections: 30min → 1h Source: NASA FIRMS VIIRS_SNPP_NRT refreshes approximately every 3 hours (https://firms.modaps.eosdis.nasa.gov/usfs/active_fire/). Polling twice per refresh cycle still catches every update. Combined: ~66% fewer Redis reads on these two endpoints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: deduplicate ACLED API calls via shared cached fetch layer Three RPC handlers (conflict, unrest, intelligence) each made independent ACLED API calls with their own auth/timeout/parsing logic. Extract a shared fetchAcledCached() in server/_shared/acled.ts that: - Centralises auth, timeout, and error handling - Caches by query params so overlapping date ranges share results - Eliminates ~120 lines of duplicated fetch boilerplate - Reduces ACLED API calls when multiple handlers run concurrently Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add structural tests for round 2 infra optimizations 17 tests covering: TTL alignment (climate 3h, fire 1h), ACLED shared cache layer (fetchAcledCached, cache key derivation, consumer imports, no duplicated ACLED_API_URL), and maritime AIS visibility guard (document.hidden check, pausePolling, resumePolling, visibilitychange listener wiring). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: rename round 2 test file to ttl-acled-ais-guards Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR koala73#275 review findings --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Elie Habib <elie.habib@gmail.com>
…3#295) * 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.
…tion probes (koala73#296) 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.
…ries (koala73#299) Models like DeepSeek-R1 and QwQ output chain-of-thought as plain text even with think:false. This caused summaries like "We need to summarize the top story..." instead of actual news content. - Remove message.reasoning fallback that used thinking tokens as summary - Extend tag stripping to <|thinking|>, <reasoning>, <reflection> formats - Add hasReasoningPreamble() to reject task narration and prompt echoes - Gate reasoning detection to brief/analysis modes (translate unaffected) - Bump CACHE_VERSION v3→v4 to invalidate polluted cached summaries - Add 28 unit tests covering all edge cases
…oala73#285) * fix: sync YouTube live panel mute state with native player controls * fix: harden YouTube embed mute sync (postMessage origin, interval cleanup, DRY destroy) --------- Co-authored-by: Elie Habib <elie.habib@gmail.com>
* test: add Playwright e2e tests for flushStaleRefreshes 4 tests covering: stale services flushed on tab focus (hidden > interval), no-op when hiddenSince is 0, skips non-stale services (hidden < interval), and 150ms stagger between re-triggered services. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: convert flushStaleRefreshes to fast unit test, fix timeout leaks and timing flakiness - Move from Playwright e2e to Node.js unit test (tests/ dir) - Add source contract tests to detect if App.ts method signature drifts - Clean up all timeouts in afterEach to prevent leaks - Assert ordering + minimum gaps instead of absolute time windows (CI-safe) - Add assertions for refreshTimeoutIds state after flush - Add test for non-stale service timeout preservation * test: make flush stale refresh tests deterministic --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Elie Habib <elie.habib@gmail.com>
…koala73#302) * fix: harden desktop embed messaging and secret validation * fix: harden embed postMessage origin check and add custom channel validation Security: - Block wildcard parentOrigin from query params (server-side sanitizer) - Validate e.origin on incoming postMessage commands in embed - Remove misleading asset: protocol from allowed list - Require 2+ markers for Cloudflare challenge detection (drop overly broad 'cloudflare' marker) - Add ordering comment on isAuthFailure vs isCloudflareChallenge403 - Strengthen embed test assertions with regex + wildcard rejection test Channel validation: - Validate YouTube handle format (@<3-30 chars>) before adding - Verify channel exists on YouTube via /api/youtube/live before adding - Show "Verifying…" loading state, red border on invalid, offline tolerance - Return channelExists flag from /api/youtube/live endpoint
* Simplify RSS freshness update to static import * Refine vendor chunking for map stack in Vite build * Patch transitive XML parser vulnerability via npm override * Shim Node child_process for browser bundle warnings * Filter known onnxruntime eval warning in Vite build * test: add loaders XML/WMS parser regression coverage * chore: align fast-xml-parser override with merged dependency set --------- Co-authored-by: Elie Habib <elie.habib@gmail.com>
…73#306) - Add levels, trends, fallback keys to top-level countryBrief in en/el/th/vi locales (fixes raw key display in intelligence brief and header badge) - Add Export PDF option to country brief dropdown using scoped print dialog - Add exportPdf i18n key to all 17 locale files
…73#308) - Add levels, trends, fallback keys to top-level countryBrief in en/el/th/vi locales (fixes raw key display in intelligence brief and header badge) - Add Export PDF option to country brief dropdown using scoped print dialog - Add exportPdf i18n key to all 17 locale files
…lity (koala73#313) WKWebView (Tauri macOS) doesn't support HTML5 Drag and Drop API. Replace draggable/dragstart/dragover with mousedown/mousemove/mouseup across panel grid reorder, live channel tabs, and channel settings. Uses elementFromPoint with same-row detection for accurate horizontal and vertical drag positioning.
…ala73#315) - Add panelDragCleanupHandlers to remove document listeners on destroy - Suppress channel click/edit after drag-end to prevent accidental actions
…g time (koala73#287) (koala73#345) - 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)
…oala73#346) * fix: suppress notification sound when popup alerts are disabled Badge playSound() was firing on new findings regardless of the "Pop up new alerts" toggle. Gate sound on popupEnabled so both the modal and audio respect the user preference. * chore: bump version to 2.5.7 with changelog ## What's Changed ### Performance - perf: defer YouTube/map init and stagger data loads (koala73#287) ### Features - feat: universal country detection — CII scoring for all countries (koala73#344) - feat: add Mexico as CII hotspot (koala73#327) - feat: add Mexico and LatAm security feeds (koala73#325) - feat: add category pills and search filter to Panels tab (koala73#322) - feat: consolidate settings into unified tabbed modal (koala73#319) - feat: optional channels with tab-based region browse UI (koala73#295) - feat: custom channel management (koala73#282) ### Bug Fixes - fix: suppress notification sound when popup alerts are disabled - fix: prevent entity conflation in pane summarization (koala73#341) - fix: add Mexico to COUNTRY_BOUNDS and COUNTRY_ALIASES (koala73#338) - fix: OpenSky cache TTLs, serialization, and auth resilience (koala73#329-koala73#333) - fix: replace RSSHub feeds with native/Google News alternatives (koala73#331) - fix: replace HTML5 drag API with mouse events for WKWebView (koala73#313) - fix: sync YouTube mute state with native player controls (koala73#285) - fix: strip Ollama reasoning tokens from summaries (koala73#299) - fix: infra cost optimizations (koala73#275, koala73#283) - fix: circuit breaker persistent cache (koala73#281) - fix: immediately refresh stale services on tab focus (koala73#277) ### Security - Security hardening: SSRF protection, auth gating, token generation (koala73#343) - Harden Railway relay auth, caching, and proxy routing (koala73#320) - Build/runtime hardening and dependency security updates (koala73#286)
…koala73#229) * chore: add project config * docs: add domain research (stack, features, architecture, pitfalls) * docs: define v1 requirements * docs: create roadmap (9 phases) * docs(01): capture phase context * docs(state): record phase 1 context session * docs(01): research phase domain * docs(01): create phase plan * fix(01): revise plans based on checker feedback * feat(01-01): register happy variant in config system and build tooling - Add 'happy' to allowed stored variants in variant.ts - Create variants/happy.ts with panels, map layers, and VariantConfig - Add HAPPY_PANELS, HAPPY_MAP_LAYERS, HAPPY_MOBILE_MAP_LAYERS inline in panels.ts - Update ternary export chains to select happy config when SITE_VARIANT === 'happy' - Add happy entry to VARIANT_META in vite.config.ts - Add dev:happy and build:happy scripts to package.json Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(01-01): update index.html for variant detection, CSP, and Google Fonts - Add happy.worldmonitor.app to CSP frame-src directive - Extend inline script to detect variant from hostname (happy/tech/finance) and localStorage - Set data-variant attribute on html element before first paint to prevent FOUC - Add Google Fonts preconnect and Nunito stylesheet links - Add favicon variant path replacement in htmlVariantPlugin for non-full variants Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(01-01): create happy variant favicon assets - Create SVG globe favicon in sage green (#6B8F5E) and warm gold (#C4A35A) - Generate PNG favicons at all required sizes (16, 32, 180, 192, 512) - Generate favicon.ico with PNG-in-ICO wrapper - Create branded OG image (1200x630) with cream background, sage/gold scheme Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(01-01): complete variant registration plan - Create 01-01-SUMMARY.md documenting variant registration - Update STATE.md with plan 1 completion, metrics, decisions - Update ROADMAP.md with phase 01 progress (1/3 plans) - Mark INFRA-01, INFRA-02, INFRA-03 requirements complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(01-02): create happy variant CSS theme with warm palette and semantic overrides - Complete happy-theme.css with light mode (cream/sage), dark mode (navy/warm), and semantic colors - 179 lines covering all CSS custom properties: backgrounds, text, borders, overlays, map, panels - Nunito typography and 14px panel border radius for soft rounded aesthetic - Semantic colors remapped: gold (critical), sage (growth), blue (hope), pink (kindness) - Dark mode uses warm navy/sage tones, never pure black - Import added to main.css after panels.css Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(01-02): add happy variant skeleton shell overrides and theme-color meta - Inline skeleton styles for happy variant light mode (cream bg, Nunito font, sage dot, warm shimmer) - Inline skeleton styles for happy variant dark mode (navy bg, warm borders, sage tones) - Rounded corners (14px) on skeleton panels and map for soft aesthetic - Softer pill border-radius (8px) in happy variant - htmlVariantPlugin: theme-color meta updated to #FAFAF5 for happy variant mobile chrome Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(01-02): complete happy theme CSS plan - SUMMARY.md with execution results and self-check - STATE.md advanced to plan 2/3, decisions logged - ROADMAP.md progress updated (2/3 plans complete) - REQUIREMENTS.md: THEME-01, THEME-03, THEME-04 marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(01-03): create warm basemap styles and wire variant-aware map selection - Add happy-light.json: sage land, cream background, light blue ocean (forked from CARTO Voyager) - Add happy-dark.json: dark sage land, navy background, dark navy ocean (forked from CARTO Dark Matter) - Both styles preserve CARTO CDN source/sprite/glyph URLs for tile loading - DeckGLMap.ts selects happy basemap URLs when SITE_VARIANT is 'happy' Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(01-03): style panel chrome, empty states, and loading for happy variant - Panels get 14px rounded corners with subtle warm shadows - Panel titles use normal casing (no uppercase) for friendlier feel - Empty states (.panel-empty, .empty-state) show nature-themed sprout SVG icon - Loading radar animation softened to 3s rotation with sage-green glow - Status dots use gentle happy-pulse animation (2.5s ease-in-out) - Error states use warm gold tones instead of harsh red - Map controls, tabs, badges all get rounded corners - Severity badges use warm semantic colors - Download banner and posture radar adapted to warm theme Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(01-03): bridge SITE_VARIANT to data-variant attribute on <html> The CSS theme overrides rely on [data-variant="happy"] on the document root, but the inline script only detects variant from hostname/localStorage. This leaves local dev (VITE_VARIANT=happy) and Vercel deployments without the attribute set. Two fixes: 1. main.ts sets document.documentElement.dataset.variant from SITE_VARIANT 2. Vite htmlVariantPlugin injects build-time variant fallback into inline script Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(01-03): boost CSS specificity so happy theme wins over :root The happy-theme.css was imported before :root in main.css, and both [data-variant="happy"] and :root have equal specificity (0-1-0), so :root variables won after in the cascade. Fix by using :root[data-variant="happy"] (specificity 0-2-0) which always beats :root (0-1-0). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(01): fix CSS cascade — import happy-theme after main.css in main.ts The root cause: happy-theme.css was @imported inside main.css (line 4), which meant Vite loaded it BEFORE the :root block (line 9+). With equal specificity, the later :root variables always won. Fix: remove @import from main.css, import happy-theme.css directly in main.ts after main.css. This ensures cascade order is correct — happy theme variables come last and win. No !important needed. Also consolidated semantic color variables into the same selector blocks to reduce redundancy. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(01): fix CSS cascade with @layer base and theme toggle for happy variant - Wrap main.css in @layer base via base-layer.css so happy-theme.css (unlayered) always wins the cascade for custom properties - Remove duplicate <link> stylesheet from index.html (was double-loading) - Default happy variant to light theme (data-theme="light") so the theme toggle works on first click instead of requiring two clicks - Force build-time variant in inline script — stale localStorage can no longer override the deployment variant - Prioritize VITE_VARIANT env over localStorage in variant.ts so variant-specific builds are deterministic Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(01-03): complete map basemap & panel chrome plan — Phase 1 done - Add 01-03-SUMMARY.md with task commits, deviations, and self-check - Update STATE.md: Phase 1 complete, advance to ready for Phase 2 - Update ROADMAP.md: mark Phase 1 plans 3/3 complete - Update REQUIREMENTS.md: mark THEME-02 and THEME-05 complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(phase-01): complete phase execution * docs(phase-02): research curated content pipeline * docs(02): create phase plan — curated content pipeline * feat(02-01): add positive RSS feeds for happy variant - Add HAPPY_FEEDS record with 8 feeds across 5 categories (positive, science, nature, health, inspiring) - Update FEEDS export ternary to route happy variant to HAPPY_FEEDS - Add happy source tiers to SOURCE_TIERS (Tier 2 for main sources, Tier 3 for category feeds) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(02-01): extend GDELT with tone filtering and positive topic queries - Add tone_filter (field 4) and sort (field 5) to SearchGdeltDocumentsRequest proto - Regenerate TypeScript client/server types via buf generate - Handler appends toneFilter to GDELT query string, uses req.sort for sort param - Add POSITIVE_GDELT_TOPICS array with 5 positive topic queries - Add fetchPositiveGdeltArticles() with tone>5 and ToneDesc defaults - Add fetchPositiveTopicIntelligence() and fetchAllPositiveTopicIntelligence() helpers - Existing fetchGdeltArticles() backward compatible (empty toneFilter/sort = no change) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(02-01): complete positive feeds & GDELT tone filtering plan - Create 02-01-SUMMARY.md with execution results - Update STATE.md: phase 2, plan 1 of 2, decisions, metrics - Update ROADMAP.md: phase 02 progress (1/2 plans) - Mark FEED-01 and FEED-03 requirements complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(02-02): add positive content classifier and extend NewsItem type - Create positive-classifier.ts with 6 content categories (science-health, nature-wildlife, humanity-kindness, innovation-tech, climate-wins, culture-community) - Source-based pre-mapping for GNN category feeds (fast path) - Priority-ordered keyword classification for general positive feeds (slow path) - Add happyCategory optional field to NewsItem interface - Export HAPPY_CATEGORY_LABELS and HAPPY_CATEGORY_ALL for downstream UI use Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(02-02): clean up happy variant config and verify feed wiring - Remove dead FEEDS placeholder from happy.ts (now handled by HAPPY_FEEDS in feeds.ts) - Remove unused Feed type import - Verified SOURCE_TIERS has all 8 happy feed entries (Tier 2: GNN/Positive.News/RTBC/Optimist, Tier 3: GNN category feeds) - Verified FEEDS export routes to HAPPY_FEEDS when SITE_VARIANT=happy - Verified App.ts loadNews() dynamically iterates FEEDS keys - Happy variant builds successfully Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(02-02): complete content category classifier plan - SUMMARY.md documenting classifier implementation and feed wiring cleanup - STATE.md updated: Phase 2 complete, 5 total plans done, 56% progress - ROADMAP.md updated: Phase 02 marked complete (2/2 plans) - REQUIREMENTS.md: FEED-04 marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(02-03): create gap closure plan for classifier wiring * feat(02-03): wire classifyNewsItem into happy variant news ingestion - Import classifyNewsItem from positive-classifier service - Add classification step in loadNewsCategory() after fetchCategoryFeeds - Guard with SITE_VARIANT === 'happy' to avoid impact on other variants - In-place mutation via for..of loop sets happyCategory on every NewsItem Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(02-03): complete classifier wiring gap closure plan - Add 02-03-SUMMARY.md documenting classifier wiring completion - Update STATE.md with plan 3/3 position and decisions - Update ROADMAP.md with completed plan checkboxes - Include 02-VERIFICATION.md phase verification document Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(phase-2): complete phase execution * test(02): complete UAT - 1 passed, 1 blocker diagnosed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(phase-3): research positive news feed & quality pipeline Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(03): create phase plan for positive news feed and quality pipeline * fix(03): revise plans based on checker feedback * feat(03-02): add imageUrl to NewsItem and extract images from RSS - Add optional imageUrl field to NewsItem interface - Add extractImageUrl() helper to rss.ts with 4-strategy image extraction (media:content, media:thumbnail, enclosure, img-in-description) - Wire image extraction into fetchFeed() for happy variant only * feat(03-01): add happy variant guards to all App.ts code paths - Skip DEFCON/PizzInt indicator for happy variant - Add happy variant link (sun icon) to variant switcher header - Show 'Good News Map' title for happy variant map section - Skip LiveNewsPanel, LiveWebcams, TechEvents, ServiceStatus, TechReadiness, MacroSignals, ETFFlows, Stablecoin panels for happy - Gate live-news first-position logic with happy exclusion - Only load 'news' data for happy variant (skip markets, predictions, pizzint, fred, oil, spending, intelligence, military layers) - Only schedule 'news' refresh interval for happy (skip all geopolitical/financial refreshes) - Add happy-specific search modal with positive placeholder and no military/geopolitical sources Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(03-02): create PositiveNewsFeedPanel with filter bar and card rendering - New PositiveNewsFeedPanel component extending Panel with: - Category filter bar (All + 6 positive categories) - Rich card rendering with image, title, source, category badge, time - Filter state preserved across data refreshes - Proper cleanup in destroy() - Add CSS styles to happy-theme.css for cards and filter bar - Category-specific badge colors using theme variables - Scoped under [data-variant="happy"] to avoid affecting other variants * feat(03-01): return empty channels for happy variant in LiveNewsPanel - Defense-in-depth: LIVE_CHANNELS returns empty array for happy variant - Ensures zero Bloomberg/war streams even if panel is somehow instantiated - Combined with createPanels() guard from Task 1 for belt-and-suspenders safety Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(03-02): complete positive news feed panel plan - Created 03-02-SUMMARY.md with execution results - Updated STATE.md with position, decisions, and metrics - Updated ROADMAP.md with phase 03 progress (2/3 plans) - Marked NEWS-01, NEWS-02 requirements as complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(03-01): complete Happy Variant App.ts Integration plan - SUMMARY.md with execution results and decisions - STATE.md updated with 03-01 decisions and session info - ROADMAP.md progress updated (2/3 phase 3 plans) - NEWS-03 requirement marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(03-03): create sentiment gate service for ML-based filtering - Exports filterBySentiment() wrapping mlWorker.classifySentiment() - Default threshold 0.85 with localStorage override for tuning - Graceful degradation: returns all items if ML unavailable - Batches titles at 20 items per call (ML_THRESHOLDS.maxTextsPerBatch) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(03-03): wire multi-stage quality pipeline and positive-feed panel into App.ts - Register 'positive-feed' in HAPPY_PANELS replacing 'live-news' - Import PositiveNewsFeedPanel, filterBySentiment, fetchAllPositiveTopicIntelligence - Add positivePanel + happyAllItems class properties - Create PositiveNewsFeedPanel in createPanels() for happy variant - Accumulate curated items in loadNewsCategory() for happy variant - Implement loadHappySupplementaryAndRender() 4-stage pipeline: 1. Curated feeds render immediately (non-blocking UX) 2. GDELT positive articles fetched as supplementary 3. Sentiment-filtered via DistilBERT-SST2 (filterBySentiment) 4. Merged + sorted by date, re-rendered - Auto-refresh on REFRESH_INTERVALS.feeds re-runs full pipeline - ML failure degrades gracefully to curated-only display Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(03-03): complete quality pipeline plan - phase 3 done - Summary: multi-stage positive news pipeline with ML sentiment gate - STATE.md: phase 3 complete (3/3), 89% progress - ROADMAP.md: phase 03 marked complete - REQUIREMENTS.md: FEED-02, FEED-05 marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(03): wire positive-feed panel key in panels.ts and add happy map layer/legend config The executor updated happy.ts but the actual HAPPY_PANELS export comes from panels.ts — it still had 'live-news' instead of 'positive-feed', so the panel never rendered. Also adds happyLayers (natural only) and happy legend to Map.ts to hide military layer toggles and geopolitical legend items. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(phase-3): complete phase execution * docs(phase-4): research global map & positive events Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(04): create phase plan — global map & positive events * fix(04): revise plans based on checker feedback * feat(04-01): add positiveEvents and kindness keys to MapLayers interface and all variant configs - Add positiveEvents and kindness boolean keys to MapLayers interface - Update all 10 variant layer configs (8 in panels.ts + 2 in happy.ts) - Happy variant: positiveEvents=true, kindness=true; all others: false - Fix variant config files (full, tech, finance) and e2e harnesses for compilation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(04-01): add happy variant layer toggles and legend in DeckGLMap - Add happy branch to createLayerToggles with 3 toggles: Positive Events, Acts of Kindness, Natural Events - Add happy branch to createLegend with 4 items: Positive Event (green), Breakthrough (gold), Act of Kindness (light green), Natural Event (orange) - Non-happy variants unchanged Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(04-01): complete map layer config & happy variant toggles plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(04-02): add positive events geocoding pipeline and map layer - Proto service PositiveEventsService with ListPositiveGeoEvents RPC - Server-side GDELT GEO fetch with positive topic queries, dedup, classification - Client-side service calling server RPC + RSS geocoding via inferGeoHubsFromTitle - DeckGLMap green/gold ScatterplotLayer with pulse animation for significant events - Tooltip shows event name, category, and report count - Routes registered in api gateway and vite dev server Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(04-02): wire positive events loading into App.ts happy variant pipeline - Import fetchPositiveGeoEvents and geocodePositiveNewsItems - Load positive events in loadAllData() for happy variant with positiveEvents toggle - loadPositiveEvents() merges GDELT GEO RPC + geocoded RSS items, deduplicates by name - loadDataForLayer switch case for toggling positiveEvents layer on/off - MapContainer.setPositiveEvents() delegates to DeckGLMap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(04-02): complete positive events geocoding pipeline plan - SUMMARY.md with task commits, decisions, deviations - STATE.md updated with position, metrics, decisions - ROADMAP.md and REQUIREMENTS.md updated Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(04-03): create kindness-data service with baseline generator and curated events - Add KindnessPoint interface for map visualization data - Add MAJOR_CITIES constant with ~60 cities worldwide (population-weighted) - Implement generateBaselineKindness() producing 50-80 synthetic points per cycle - Implement extractKindnessEvents() for real kindness items from curated news - Export fetchKindnessData() merging baseline + real events * feat(04-03): add kindness layer to DeckGLMap and wire into App.ts pipeline - Add createKindnessLayers() with solid green fill + gentle pulse ring for real events - Add kindness-layer tooltip showing city name and description - Add setKindnessData() setter in DeckGLMap and MapContainer - Wire loadKindnessData() into App.ts loadAllData and loadDataForLayer - Kindness layer gated by mapLayers.kindness toggle (happy variant only) - Pulse animation triggers when real kindness events are present * docs(04-03): complete kindness data pipeline & map layer plan - Create 04-03-SUMMARY.md documenting kindness layer implementation - Update STATE.md: phase 04 complete (3/3 plans), advance position - Update ROADMAP.md: phase 04 marked complete - Mark KIND-01 and KIND-02 requirements as complete * docs(phase-4): complete phase execution * docs(phase-5): research humanity data panels domain Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(05-humanity-data-panels): create phase plan * feat(05-01): create humanity counters service with metric definitions and rate calculations - Define 6 positive global metrics with annual totals from UN/WHO/World Bank/UNESCO - Calculate per-second rates from annual totals / 31,536,000 seconds - Absolute-time getCounterValue() avoids drift across tabs/throttling - Locale-aware formatCounterValue() using Intl.NumberFormat * feat(05-02): install papaparse and create progress data service - Install papaparse + @types/papaparse for potential OWID CSV fallback - Create src/services/progress-data.ts with 4 World Bank indicators - Export PROGRESS_INDICATORS (life expectancy, literacy, child mortality, poverty) - Export fetchProgressData() using existing getIndicatorData() RPC - Null value filtering, year sorting, invertTrend-aware change calculation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(05-01): create CountersPanel component with 60fps animated ticking numbers - Extend Panel base class with counters-grid of 6 counter cards - requestAnimationFrame loop updates all values at 60fps - Absolute-time calculation via getCounterValue() prevents drift - textContent updates (not innerHTML) avoid layout thrashing - startTicking() / destroy() lifecycle methods for App.ts integration * feat(05-02): create ProgressChartsPanel with D3.js area charts - Extend Panel base class with id 'progress', title 'Human Progress' - Render 4 stacked D3 area charts (life expectancy, literacy, child mortality, poverty) - Warm happy-theme colors: sage green, soft blue, warm gold, muted rose - d3.area() with curveMonotoneX for smooth filled curves - Header with label, change badge (e.g., "+58.0% since 1960"), and unit - Hover tooltip with bisector-based nearest data point detection - ResizeObserver with 200ms debounce for responsive re-rendering - Clean destroy() lifecycle with observer disconnection Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(05-01): complete ticking counters service & panel plan - SUMMARY.md with execution results and self-check - STATE.md updated to phase 5, plan 1/3 - ROADMAP.md progress updated - Requirements COUNT-01, COUNT-02, COUNT-03 marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(05-02): complete progress charts panel plan - Create 05-02-SUMMARY.md with execution results - Update STATE.md: plan 2/3, decisions, metrics - Update ROADMAP.md: phase 05 progress (2/3 plans) - Mark PROG-01, PROG-02, PROG-03 complete in REQUIREMENTS.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(05-03): wire CountersPanel and ProgressChartsPanel into App.ts lifecycle - Import CountersPanel, ProgressChartsPanel, and fetchProgressData - Add class properties for both new panels - Instantiate both panels in createPanels() gated by SITE_VARIANT === 'happy' - Add progress data loading task in refreshAll() for happy variant - Add loadProgressData() private method calling fetchProgressData + setData - Add destroy() cleanup for both panels (stops rAF loop and ResizeObserver) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(05-03): add counter and progress chart CSS styles to happy-theme.css - Counters grid: responsive 3-column layout (3/2/1 at 900px/500px breakpoints) - Counter cards: hover lift, tabular-nums for jitter-free 60fps updates - Counter icon/value/label/source typography hierarchy - Progress chart containers: stacked with border dividers - Chart header with label, badge, and unit display - D3 SVG axis styling (tick text fill, domain stroke) - Hover tooltip with absolute positioning and shadow - Dark mode adjustments for card hover shadow and tooltip shadow - All selectors scoped under [data-variant='happy'] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(05-03): complete panel wiring & CSS plan - Create 05-03-SUMMARY.md with execution results - Update STATE.md: phase 5 complete (3/3 plans), decisions, metrics - Update ROADMAP.md: phase 05 progress (3/3 summaries, Complete) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(phase-5): complete phase execution * docs(06): research phase 6 content spotlight panels * docs(phase-6): create phase plan * feat(06-01): add science RSS feeds and BreakthroughsTickerPanel - Expand HAPPY_FEEDS.science from 1 to 5 feeds (ScienceDaily, Nature News, Live Science, New Scientist) - Create BreakthroughsTickerPanel extending Panel with horizontal scrolling ticker - Doubled content rendering for seamless infinite CSS scroll animation - Sanitized HTML output using escapeHtml/sanitizeUrl Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(06-01): create HeroSpotlightPanel with photo, map location, and hero card - Create HeroSpotlightPanel extending Panel for daily hero spotlight - Render hero card with image, source, title, time, and optional map button - Conditionally show "Show on map" button only when both lat and lon exist - Expose onLocationRequest callback for App.ts map integration wiring - Sanitized HTML output using escapeHtml/sanitizeUrl Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(06-02): add GoodThingsDigestPanel with progressive AI summarization - Panel extends Panel base class with id 'digest', title '5 Good Things' - Renders numbered story cards with titles immediately (progressive rendering) - Summarizes each story in parallel via generateSummary() with Promise.allSettled - AbortController cancels in-flight summaries on re-render or destroy - Graceful fallback to truncated title on summarization failure - Passes [title, source] to satisfy generateSummary's 2-headline minimum Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(06-02): complete Good Things Digest Panel plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(06-01): complete content spotlight panels plan - Add 06-01-SUMMARY.md with execution results - Update STATE.md with position, decisions, metrics - Update ROADMAP.md and REQUIREMENTS.md progress Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(06-03): wire Phase 6 panels into App.ts lifecycle and update happy.ts config - Import and instantiate BreakthroughsTickerPanel, HeroSpotlightPanel, GoodThingsDigestPanel in createPanels() - Wire heroPanel.onLocationRequest callback to map.setCenter + map.flashLocation - Distribute data to all three panels after content pipeline in loadHappySupplementaryAndRender() - Add destroy calls for all three panels in App.destroy() - Add digest key to DEFAULT_PANELS in happy.ts config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(06-03): add CSS styles for ticker, hero card, and digest panels - Add happy-ticker-scroll keyframe animation for infinite horizontal scroll - Add breakthroughs ticker styles (wrapper, track, items with hover pause) - Add hero spotlight card styles (image, body, source, title, location button) - Add digest list styles (numbered cards, titles, sources, progressive summaries) - Add dark mode overrides for all three panel types - All selectors scoped under [data-variant="happy"] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(06-03): complete panel wiring & CSS plan - Create 06-03-SUMMARY.md with execution results - Update STATE.md: phase 6 complete, 18 plans done, 78% progress - Update ROADMAP.md: phase 06 marked complete (3/3 plans) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(phase-6): complete phase execution * docs(07): research conservation & energy trackers phase * docs(07-conservation-energy-trackers): create phase plan * feat(07-02): add renewable energy data service - Fetch World Bank EG.ELC.RNEW.ZS indicator (IEA-sourced) for global + 7 regions - Return global percentage, historical time-series, and regional breakdown - Graceful degradation: individual region failures skipped, complete failure returns zeroed data - Follow proven progress-data.ts pattern for getIndicatorData() RPC usage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(07-01): add conservation wins dataset and data service - Create conservation-wins.json with 10 species recovery stories and population timelines - Create conservation-data.ts with SpeciesRecovery interface and fetchConservationWins() loader - Species data sourced from USFWS, IUCN, NOAA, WWF, and other published reports * feat(07-02): add RenewableEnergyPanel with D3 arc gauge and regional breakdown - Animated D3 arc gauge showing global renewable electricity % with 1.5s easeCubicOut - Historical trend sparkline using d3.area() + curveMonotoneX below gauge - Regional breakdown with horizontal bars sorted by percentage descending - All colors use getCSSColor() for theme-aware rendering - Empty state handling when no data available Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(07-01): add SpeciesComebackPanel with D3 sparklines and species cards - Create SpeciesComebackPanel extending Panel base class - Render species cards with photo (lazy loading + error fallback), info badges, D3 sparkline, and summary - D3 sparklines use area + line with curveMonotoneX and viewBox for responsive sizing - Recovery status badges (recovered/recovering/stabilized) and IUCN category badges - Population values formatted with Intl.NumberFormat for readability * docs(07-02): complete renewable energy panel plan - SUMMARY.md with task commits, decisions, self-check - STATE.md updated to phase 7 plan 2, 83% progress - ROADMAP.md phase 07 progress updated - REQUIREMENTS.md: ENERGY-01, ENERGY-02, ENERGY-03 marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(07-01): complete species comeback panel plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(07-03): wire species and renewable panels into App.ts lifecycle - Add imports for SpeciesComebackPanel, RenewableEnergyPanel, and data services - Add class properties for speciesPanel and renewablePanel - Instantiate both panels in createPanels() gated by SITE_VARIANT === 'happy' - Add loadSpeciesData() and loadRenewableData() tasks in refreshAll() - Add destroy cleanup for both panels before map cleanup - Add species and renewable entries to happy.ts DEFAULT_PANELS config * feat(07-03): add CSS styles for species cards and renewable energy gauge - Species card grid layout with 2-column responsive grid - Photo, info, badges (recovered/recovering/stabilized/IUCN), sparkline, summary styles - Renewable energy gauge section, historical sparkline, and regional bar chart styles - Dark mode overrides for species card hover shadow and IUCN badge background - All styles scoped with [data-variant='happy'] using existing CSS variables * docs(07-03): complete panel wiring & CSS plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(happy): add missing panel entries and RSS proxy for dev mode HAPPY_PANELS in panels.ts was missing digest, species, and renewable entries — panels were constructed but never appended to the grid because the panelOrder loop only iterated the 6 original keys. Also adds RSS proxy middleware for Vite dev server, fixes sebuf route regex to match hyphenated domains (positive-events), and adds happy feed domains to the rss-proxy allowlist. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: progress data lookup, ticker speed, ultrawide layout gap 1. Progress/renewable data: World Bank API returns countryiso3code "WLD" for world aggregate, but services were looking up by request code "1W". Changed lookups to use "WLD". 2. Breakthroughs ticker: slowed animation from 30s to 60s duration. 3. Ultrawide layout (>2000px): replaced float-based layout with CSS grid. Map stays in left column (60%), panels grid in right column (40%). Eliminates dead space under the map where panels used to wrap below. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: UI polish — counter overflow, ticker speed, monitors panel, filter tabs - Counter values: responsive font-size with clamp(), overflow protection, tighter card padding to prevent large numbers from overflowing - Breakthroughs ticker: slowed from 60s to 120s animation duration - My Monitors panel: gate monitors from panel order in happy variant (was unconditionally pushed into panelOrder regardless of variant) - Filter tabs: smaller padding/font, flex-shrink:0, fade mask on right edge to hint at scrollable overflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(happy): exclude APT groups layer from happy variant map The APT groups layer (cyber threat actors like Fancy Bear, Cozy Bear) was only excluded for the tech variant. Now also excluded for happy, since cyber threat data has no place on a Good News Map. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(happy-map): labeled markers, remove fake baseline, fix APT leak - Positive events now show category emoji + location name as colored text labels (TextLayer) instead of bare dots. Labels filter by zoom level to avoid clutter at global view. - Removed synthetic kindness baseline (50-80 fake "Volunteers at work" dots in random cities). Only real kindness events from news remain. - Kindness events also get labeled dots with headlines. - Improved tooltips with proper category names and source counts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(happy-map): disable earthquakes, fix GDELT query syntax - Disable natural events layer (earthquakes) for happy variant — not positive news - Fix GDELT GEO positive queries: OR terms require parentheses per GDELT API syntax, added third query for charity/volunteer news - Updated both desktop and mobile happy map layer configs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(happy): ultrawide grid overflow, panel text polish Ultrawide: set min-height:0 on map/panels grid children so they respect 1fr row constraint and scroll independently instead of pushing content below the viewport. Panel CSS: softer word-break on counters, line-clamp on digest and species summaries, ticker title max-width, consistent text-dim color instead of opacity hacks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(08-map-data-overlays): research phase domain Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(08-map-data-overlays): create phase plan * Add Global Giving Activity Index with multi-platform aggregation (#255) * feat(08-01): add static data for happiness scores, renewable installations, and recovery zones - Create world-happiness.json with 152 country scores from WHR 2025 - Create renewable-installations.json with 92 global entries (solar/wind/hydro/geothermal) - Extend conservation-wins.json with recoveryZone lat/lon for all 10 species * feat(08-01): add service loaders, extend MapLayers with happiness/species/energy keys - Create happiness-data.ts with fetchHappinessScores() returning Map<ISO2, score> - Create renewable-installations.ts with fetchRenewableInstallations() returning typed array - Extend SpeciesRecovery interface with optional recoveryZone field - Add happiness, speciesRecovery, renewableInstallations to MapLayers interface - Update all 8 variant MapLayers configs (happiness=true in happy, false elsewhere) - Update e2e harness files with new layer keys * docs(08-01): complete data foundation plan summary and state updates - Create 08-01-SUMMARY.md with execution results - Update STATE.md to phase 8, plan 1/2 - Update ROADMAP.md progress for phase 08 - Mark requirements MAP-03, MAP-04, MAP-05 complete * feat(08-02): add happiness choropleth, species recovery, and renewable installation overlay layers - Add three Deck.gl layer creation methods with color-coded rendering - Add public data setters for happiness scores, species recovery zones, and renewable installations - Wire layers into buildLayers() gated by MapLayers keys - Add tooltip cases for all three new layer types - Extend happy variant layer toggles (World Happiness, Species Recovery, Clean Energy) - Extend happy variant legend with choropleth, species, and renewable entries - Cache country GeoJSON reference in loadCountryBoundaries() for choropleth reuse Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(08-02): wire MapContainer delegation and App.ts data loading for map overlays - Add MapContainer delegation methods for happiness, species recovery, and renewable installations - Add happiness scores and renewable installations map data loading in App.ts refreshAll() - Chain species recovery zone data to map from existing loadSpeciesData() - All three overlay datasets flow from App.ts through MapContainer to DeckGLMap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(08-02): complete map overlay layers plan - Create 08-02-SUMMARY.md with execution results - Update STATE.md: phase 8 complete (2/2 plans), 22 total plans, decisions logged - Update ROADMAP.md: phase 08 marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(phase-8): complete phase execution * docs(roadmap): add Phase 7.1 gap closure for renewable energy installation & coal data Addresses Phase 7 verification gaps (ENERGY-01, ENERGY-03): renewable panel lacks solar/wind installation growth and coal plant closure visualizations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(7.1): research renewable energy installation & coal retirement data * docs(71): create phase plans for renewable energy installation & coal retirement data * feat(71-01): add GetEnergyCapacity RPC proto and server handler - Create get_energy_capacity.proto with request/response messages - Add GetEnergyCapacity RPC to EconomicService in service.proto - Implement server handler with EIA capability API integration - Coal code fallback (COL -> BIT/SUB/LIG/RC) for sub-type support - Redis cache with 24h TTL for annual capacity data - Register handler in economic service handler Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(71-01): add client-side fetchEnergyCapacity with circuit breaker - Add GetEnergyCapacityResponse import and capacityBreaker to economic service - Export fetchEnergyCapacityRpc() with energyEia feature gating - Add CapacitySeries/CapacityDataPoint types to renewable-energy-data.ts - Export fetchEnergyCapacity() that transforms proto types to domain types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(71-01): complete EIA energy capacity data pipeline plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(71-02): add setCapacityData() with D3 stacked area chart to RenewableEnergyPanel - setCapacityData() renders D3 stacked area (solar yellow + wind blue) with coal decline (red) - Chart labeled 'US Installed Capacity (EIA)' with compact inline legend - Appends below existing gauge/sparkline/regions without replacing content - CSS styles for capacity section, header, legend in happy-theme.css Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(71-02): wire EIA capacity data loading in App.ts loadRenewableData() - Import fetchEnergyCapacity from renewable-energy-data service - Call fetchEnergyCapacity() after World Bank gauge data, pass to setCapacityData() - Wrapped in try/catch so EIA failure does not break existing gauge Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(71-02): complete EIA capacity visualization plan - SUMMARY.md documenting D3 stacked area chart implementation - STATE.md updated: Phase 7.1 complete (2/2 plans), progress 100% - ROADMAP.md updated with plan progress Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(phase-71): complete phase execution * docs(phase-09): research sharing, TV mode & polish domain Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(09): create phase plan for sharing, TV mode & polish * docs(phase-09): plan Sharing, TV Mode & Polish 3 plans in 2 waves covering share cards (Canvas 2D renderer), TV/ambient mode (fullscreen panel cycling + CSS particles), and celebration animations (canvas-confetti milestones). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(09-01): create Canvas 2D renderer for happy share cards - 1080x1080 branded PNG with warm gradient per category - Category badge, headline word-wrap, source, date, HappyMonitor branding - shareHappyCard() with Web Share API -> clipboard -> download fallback - wrapText() helper for Canvas 2D manual line breaking Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(09-02): create TvModeController and TV mode CSS - TvModeController class manages fullscreen, panel cycling with configurable 30s-2min interval - CSS [data-tv-mode] attribute drives larger typography, hidden interactive elements, smooth panel transitions - Ambient floating particles (CSS-only, opacity 0.04) with reduced motion support - TV exit button appears on hover, hidden by default outside TV mode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(09-02): wire TV mode into App.ts header and lifecycle - TV mode button with monitor icon in happy variant header - TV exit button at page level, visible on hover in TV mode - Shift+T keyboard shortcut toggles TV mode - TvModeController instantiated lazily on first toggle - Proper cleanup in destroy() method Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(09-01): add share button to positive news cards with handler - Share button (SVG upload icon) appears on card hover, top-right - Delegated click handler prevents link navigation, calls shareHappyCard - Brief .shared visual feedback (green, scale) for 1.5s on click - Dark mode support for share button background - Fix: tv-mode.ts panelKeys index guard (pre-existing build blocker) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(09-02): complete TV Mode plan - SUMMARY.md with task commits, deviations, decisions - STATE.md updated: position, metrics, decisions, session - ROADMAP.md updated: phase 09 progress (2/3 plans) - REQUIREMENTS.md updated: TV-01, TV-02, TV-03 marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(09-01): complete positive news share cards plan - SUMMARY.md with Canvas 2D renderer and share button accomplishments - STATE.md updated with decisions and session continuity - ROADMAP.md progress updated (2/3 plans in phase 09) - REQUIREMENTS.md: SHARE-01, SHARE-02, SHARE-03 marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(09-03): add celebration service with canvas-confetti - Install canvas-confetti + @types/canvas-confetti - Create src/services/celebration.ts with warm nature-inspired palette - Session-level dedup (Set<string>) prevents repeat celebrations - Respects prefers-reduced-motion media query - Milestone detection for species recovery + renewable energy records - Moderate particle counts (40-80) for "warm, not birthday party" feel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(09-03): wire milestone celebrations into App.ts data pipelines - Import checkMilestones in App.ts - Call checkMilestones after species data loads with recovery statuses - Call checkMilestones after renewable energy data loads with global percentage - All celebration calls gated behind SITE_VARIANT === 'happy' - Placed after panel setData() so data is visible before confetti fires Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(09-03): complete celebration animations plan - 09-03-SUMMARY.md with execution results - STATE.md updated: phase 09 complete, 26 plans total, 100% progress - ROADMAP.md updated with phase 09 completion - REQUIREMENTS.md: THEME-06 marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(phase-09): complete phase execution * fix(happy): remove natural events layer from happy variant Natural events (earthquakes, volcanoes, storms) were leaking into the happy variant through stale localStorage and the layer toggle UI. Force all non-happy layers off regardless of localStorage state, and remove the natural events toggle from both DeckGL and SVG map layer configs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(phase-7.1): complete phase execution — mark all phases done Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(v1): complete milestone audit — 49/49 requirements satisfied Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(happy): close audit tech debt — map layer defaults, theme-color meta - Enable speciesRecovery and renewableInstallations layers by default in HAPPY_MAP_LAYERS (panels.ts + happy.ts) so MAP-04/MAP-05 are visible on first load - Use happy-specific theme-color meta values (#FAFAF5 light, #1A2332 dark) in setTheme() and applyStoredTheme() instead of generic colors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add checkpoint for giving integration handoff Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(giving): integrate Global Giving Activity Index from PR #254 Cherry-pick the giving feature that was left behind when PR #255 batch-merged without including #254's proto/handler/panel files. Adds: - Proto definitions (GivingService, GivingSummary, PlatformGiving, etc.) - Server handler: GoFundMe/GlobalGiving/JustGiving/crypto/OECD aggregation - Client service with circuit breaker - GivingPanel with tabs (platforms, categories, crypto, institutional) - Full wiring: API routes, vite dev server, data freshness, panel config - Happy variant panel config entry Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(giving): move panel init and data fetch out of full-variant-only blocks The GivingPanel was instantiated inside `if (SITE_VARIANT === 'full')` and the data fetch was inside `loadIntelligenceSignals()` (also full-only). Moved both to variant-agnostic scope so the panel works on happy variant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(giving): bypass debounced setContent so tab buttons are clickable Panel.setContent() is debounced (150ms), so event listeners attached immediately after it were binding to DOM elements that got replaced by the deferred innerHTML write. Write directly to this.content.innerHTML like other interactive panels do. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove .planning/ from repo and gitignore it Planning files served their purpose during happy monitor development. They remain on disk for reference but no longer tracked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge new panels into saved panelSettings so they aren't hidden When panelSettings is loaded from localStorage, any panels added since the user last saved settings would be missing from the config. The applyPanelSettings loop wouldn't touch them, but without a config entry they also wouldn't appear in the settings toggle UI correctly. Now merges DEFAULT_PANELS entries into loaded settings for any keys that don't exist yet, so new panels are visible by default. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: giving data baselines, theme toggle persistence, and client caching - Replace broken GoFundMe (301→404) and GlobalGiving (401) API calls with hardcoded baselines from published annual reports. Activity index rises from 42 to 56 as all 3 platforms now report non-zero volumes. - Fix happy variant theme toggle not persisting across page reloads: applyStoredTheme() couldn't distinguish "no preference" from "user chose dark" — both returned DEFAULT_THEME. Now checks raw localStorage. - Fix inline script in index.html not setting data-theme="dark" for happy variant, causing CSS :root[data-variant="happy"] (light) to win over :root[data-variant="happy"][data-theme="dark"]. - Add client-side caching to giving service: persistCache on circuit breaker, 30min in-memory TTL, and request deduplication. - Add Playwright E2E tests for theme toggle (8 tests, all passing). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * perf: add persistent cache to all 29 circuit breakers across 19 services Enable persistCache and set appropriate cacheTtlMs on every circuit breaker that lacked them. Data survives page reloads via IndexedDB fallback and reduces redundant API calls on navigation. TTLs matched to data freshness: 5min for real-time feeds (weather, earthquakes, wildfires, aviation), 10min for event data (conflict, cyber, unrest, climate, research), 15-30min for slow-moving data (economic indicators, energy capacity, population exposure). Market quotes breaker intentionally left at cacheTtlMs: 0 (real-time). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: expand map labels progressively as user zooms in Labels now show more text at higher zoom levels instead of always truncating at 30 chars. Zoom <3: 20 chars, <5: 35, <7: 60, 7+: full. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: keep 30-char baseline for map labels, expand to full text at zoom 6+ Previous change was too aggressive with low-zoom truncation (20 chars). Now keeps original 30-char limit at global view, progressively expands to 50/80/200 chars as user zooms in. Also scales font size with zoom. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Revert "fix: keep 30-char baseline for map labels, expand to full text at zoom 6+" This reverts commit b10456851fb0e34cd3e999955543498956a0b18a. * Revert "feat: expand map labels progressively as user zooms in" This reverts commit a9cd2493740b94617efacae586ddffe2896615bc. * perf: stale-while-revalidate for instant page load Circuit breaker now returns stale cached data immediately and refreshes in the background, instead of blocking on API calls when cache exceeds TTL. Also persists happyAllItems to IndexedDB so Hero, Digest, and Breakthroughs panels render instantly from cache on page reload. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR #229 review — 4 issues from koala 1. P1: Fix duplicate event listeners in PositiveNewsFeedPanel.renderCards() — remove listener before re-adding to prevent stacking on re-renders 2. P1: Fix TV mode cycling hidden panels causing blank screen — filter out user-disabled panels from cycle list, rebuild keys on toggle 3. P2: Fix positive classifier false positives for short keywords — "ai" and "art" now use space-delimited matching to avoid substring hits (e.g. "aid", "rain", "said", "start", "part") 4. P3: Fix CSP blocking Google Fonts stylesheet for Nunito — add https://fonts.googleapis.com to style-src directive Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: decompose App.ts into focused modules under src/app/ Break the 4,597-line monolithic App class into 7 focused modules plus a ~460-line thin orchestrator. Each module implements the AppModule lifecycle (init/destroy) and communicates via a shared AppContext state object with narrow callback interfaces — no circular dependencies. Modules extracted: - app-context.ts: shared state types (AppContext, AppModule, etc.) - desktop-updater.ts: desktop version checking + update badge - country-intel.ts: country briefs, timeline, CII signals - search-manager.ts: search modal, result routing, index updates - refresh-scheduler.ts: periodic data refresh with jitter/backoff - panel-layout.ts: panel creation, grid layout, drag-drop - data-loader.ts: all 36 data loading methods - event-handlers.ts: DOM events, shortcuts, idle detection, URL sync Verified: tsc --noEmit (zero errors), all 3 variant builds pass (full, tech, finance), runtime smoke test confirms no regressions. * fix: resolve test failures and missing CSS token from PR review 1. flushStaleRefreshes test now reads from refresh-scheduler.ts (moved during App.ts modularization) 2. e2e runtime tests updated to import DesktopUpdater and DataLoaderManager instead of App.prototype for resolveUpdateDownloadUrl and loadMarkets 3. Add --semantic-positive CSS variable to main.css and happy-theme.css (both light and dark variants) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: hide happy variant button from other variants The button is only visible when already on the happy variant. This allows merging the modularized App.ts without exposing the unfinished happy layout to users — layout work continues in a follow-up PR. 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>
… /api/version (koala73#348) * security: harden IPC commands, gate DevTools, and isolate external windows - Remove devtools from default Tauri features; gate behind opt-in Cargo feature so production builds never expose DevTools - Add IPC origin validation (require_trusted_window) to 9 sensitive commands: get_secret, get_all_secrets, set_secret, delete_secret, get_local_api_token, read/write/delete_cache_entry, fetch_polymarket - Isolate youtube-login window into restricted capability (core:window only) — prevents external-origin webview from invoking app commands - Add 5-minute TTL to cached sidecar auth token in fetch patch closure - Document renderer trust boundary threat model in runtime.ts * docs: add contributors, security acknowledgments, and desktop security policy - Add Contributors section to README with all 16 GitHub contributors - Add Security Acknowledgments crediting Cody Richard for 3 disclosures - Update SECURITY.md with desktop runtime security model (Tauri IPC origin validation, DevTools gating, sidecar auth, capability isolation, fetch patch trust boundary) - Add Tauri-specific items to security report scope - Correct API key storage description to cover both web and desktop * fix: exempt /api/version from bot-blocking middleware The desktop update check and sidecar requests were getting 403'd by the middleware's bot UA filter (curl/) and short UA check.
* Fix markdown lint scope and add regression test * Exclude non-product markdown trees from lint scope
* feat: add command palette to Cmd+K search modal
Extend SearchModal with 60 executable commands (nav, layers, panels,
view, time range) rendered above entity search results. Commands are
matched by keyword with scored ranking and dispatched via SearchManager.
* fix: command palette time commands and variant-aware panel filtering
P1: time:* commands used getElementById('timeRangeSelect') which doesn't
exist — replaced with ctx.map.setTimeRange() API.
P2: panel commands now filtered against active panels per variant,
preventing no-op commands (19 ghost commands on tech, 18 on finance).
* fix: replace inline update badge with top-right toast notification The tiny green pill badge in the header was easily missed. Replace with a proper toast that slides down from the top-right with download icon, version detail, CTA button, and animated dismiss. * fix(security): escape version string in update toast innerHTML Version from /api/version was interpolated raw into innerHTML. A compromised or malformed response (e.g. `2.6.0<img onerror=...>`) could execute arbitrary markup. Now escaped via escapeHtml().
…oala73#352) Merge 4 hotspot-based bounding boxes into 2 query regions (PACIFIC + WESTERN) to halve relay load, Vercel invocations, and rate-limit pressure. MILITARY_HOTSPOTS remains unchanged for flight labeling and clustering. Adds per-region stale cache (10min TTL) for partial-failure resilience and a dev-only assertion that all hotspots are covered.
Add isDestroyed checks to runGuarded(), waitForAisData() polling, and loadDataForLayer() so in-flight retries and setTimeout chains bail out after teardown instead of touching stale UI/map refs.
* chore: remove .claudedocs from repo and add to gitignore Sentry triage log was accidentally committed in v2.4.0. These are ephemeral Claude session artifacts, not project documentation. * fix: tighten Sentry noise filters for maplibre/deck.gl and third-party errors The beforeSend filename regex missed `maplibre-*` chunks and hashes containing `-`, letting ~3700 map-internal crashes through. Also widen imageManager filter for Safari wording and add filters for YouTube widget API, Sentry SDK logs.js, Facebook WebView, and TDZ errors. * feat: add country brief commands for all ISO countries to command palette Generates country commands from all ~200 ISO 3166-1 codes using Intl.DisplayNames for name resolution. Curated countries (30) retain rich searchAliases for keyword matching (e.g. "kremlin" → Russia). * feat: update command palette empty state and translate all 16 locales Update placeholder, hint, and empty state text to reflect command palette capabilities (countries, layers, panels, navigation, settings). Add example commands row with styled kbd tags. Translate all strings across ar, de, el, es, fr, it, ja, nl, pl, pt, ru, sv, th, tr, vi, zh.
* chore: remove .claudedocs from repo and add to gitignore Sentry triage log was accidentally committed in v2.4.0. These are ephemeral Claude session artifacts, not project documentation. * fix: tighten Sentry noise filters for maplibre/deck.gl and third-party errors The beforeSend filename regex missed `maplibre-*` chunks and hashes containing `-`, letting ~3700 map-internal crashes through. Also widen imageManager filter for Safari wording and add filters for YouTube widget API, Sentry SDK logs.js, Facebook WebView, and TDZ errors.
HuffPost and Nature feeds redirect to different hostnames (chaski.huffpost.com, www.nature.com) that weren't in ALLOWED_DOMAINS, causing the proxy to reject the redirect and fall through to the Railway relay (which is currently down). Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Static layer showing 19 major global shipping lanes as multi-segment arcs through strategic chokepoints, with amber dots highlighting active waterway passages. Routes are waypointed through STRATEGIC_WATERWAYS coordinates so arcs visually pass through Suez, Malacca, Hormuz, etc. rather than taking misleading great-circle shortcuts. - New src/config/trade-routes.ts with 19 routes, segment resolver - ArcLayer (greatCircle per segment) + ScatterplotLayer for chokepoints - Status-based coloring (active/disrupted/high_risk) with light theme support - Finance variant ON by default, all others OFF - Toggle UI, layer help, command palette, URL state persistence - Translated layer labels across all 17 locales
…oala73#359) 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.
* fix: add redirect target domains to RSS proxy allowlist HuffPost and Nature feeds redirect to different hostnames (chaski.huffpost.com, www.nature.com) that weren't in ALLOWED_DOMAINS, causing the proxy to reject the redirect and fall through to the Railway relay (which is currently down). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove dead RSS feeds and add 9 new positive news sources Remove Sunny Skyz (old URL dead) and HuffPost Good News (feed discontinued). Replace with verified working feeds: - Positive: Upworthy, DailyGood, Good Good Good, GOOD Magazine, Sunny Skyz (new URL), The Better India - Science: Singularity Hub, Human Progress, Greater Good (Berkeley) Also add redirect target domains (www.nature.com) to proxy allowlist and remove dead HuffPost domains. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add fallback data for Progress/Renewable panels + expand Breakthroughs sources World Bank API is intermittent (some indicators time out with 0 bytes). Both Human Progress and Renewable Energy panels silently returned empty when API was unavailable and no IndexedDB cache existed. - Add hardcoded fallback datasets (verified from World Bank, Feb 2026) for all 4 progress indicators and renewable energy global/regional data - Use fallback instead of empty on any API failure or empty response - Add Singularity Hub, Human Progress, Greater Good (Berkeley) to Breakthroughs panel source filter so new science feeds show up Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…#361) Move the barely-visible ↗ arrow into an in-input "Get key" button that only appears when the key is missing. Much clearer affordance.
Add /^Uint8Array$/ to ignoreErrors — deck.gl readPixelsToArrayWebGL crash on Windows Edge when GPU context is stressed. Not actionable.
…credit) (koala73#363) * feat: add BIS Central Bank Policy Tracker to EconomicPanel Add a new "Central Banks" tab to the Economic panel powered by the BIS (Bank for International Settlements) free SDMX REST API. Displays policy rates, effective exchange rates, and credit-to-GDP ratios for 12 major central banks (Fed, ECB, BOE, BOJ, SNB, MAS, RBI, RBA, PBOC, BOC, BOK, BCB). - Proto: 4 new files (bis_data, get_bis_policy_rates, get_bis_exchange_rates, get_bis_credit) + 3 RPCs added to EconomicService - Server: shared CSV fetch/parse helper using papaparse, 3 handlers with Redis caching (6h/12h TTL), batched single-request per dataset - Frontend: 3 separate circuit breakers, fetchBisData() with Promise.all - UI: Central Banks tab with policy rates (sorted by rate, colored by cut/hike/hold), real EER indices, and credit-to-GDP ratios - Integration: 'bis' DataSourceId, BIS in StatusPanel WORLD_APIS, 60min refresh interval, data loader wiring - i18n: BIS keys added to all 17 locale files No API key required — BIS is free/public. All BIS-derived text rendered via escapeHtml() to prevent XSS. https://claude.ai/code/session_01N73WokR4NPuSg8JpPz7nYZ * chore: update package-lock.json https://claude.ai/code/session_01N73WokR4NPuSg8JpPz7nYZ * fix: BIS policy tracker - URL bug, error logging, UX, and data cleanup - Fix double '?' in fetchBisCSV URL construction (format=csv was silently ignored) - Add error logging to all 3 BIS server handlers (previously silent catch blocks) - Fix misleading "BIS data loading..." empty state to "temporarily unavailable - will retry" - Remove unused nominal EER fetch to save bandwidth (only real EER is displayed) - Fix previousRatio rounding inconsistency in credit-to-GDP fallback path https://claude.ai/code/session_01N73WokR4NPuSg8JpPz7nYZ --------- Co-authored-by: Claude <noreply@anthropic.com>
…rriers (koala73#364) * 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>
…ule (koala73#373) Switch all 4 WTO trade endpoints from manual getCachedJson/setCachedJson to cachedFetchJson, which coalesces concurrent cache misses into a single upstream WTO API call. This prevents hammering the WTO API when multiple requests arrive during a cache miss window. Also register tradePolicy with the RefreshScheduler at a 10-minute interval (full/finance variants) — previously it was only fetched once at startup with no periodic refresh. https://claude.ai/code/session_01FdvFhpLALL9iJaF8iXMjtu Co-authored-by: Claude <noreply@anthropic.com>
…la73#376) wtoFetch() silently returned null on missing key, HTTP errors, and exceptions — making it impossible to diagnose why trade data shows "temporarily unavailable". Now logs the specific failure reason.
…story Upstream: 72 commits from koala73/worldmonitor (v2.5.5 → v2.5.8) Notable: WTO trade intelligence, BIS central bank data, trade route visualization, command palette, universal CII scoring, security hardening, performance optimizations. Local additions carried forward: - @vercel/analytics dependency (already integrated in upstream main.ts) - Story B.1: ACLED OAuth2 migration (backlog, unprioritized)
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Re-scan all 7 generated docs + index + state file after 72-commit upstream merge (v2.5.5 → v2.5.8). Key changes: - 17 → 20 service domains (trade, giving, positive-events) - 46 → 57 RPCs, 52 → 62 components, 3 → 4 build variants (happy) - App.ts decomposition: 4,629 LOC monolith → 499 LOC shell + src/app/ - New integration flows: Trade Policy, Happy Monitor, Command Palette - New section K (Security Hardening) in integration-architecture.md - New section L (v2.5.8 changes summary) - Circuit breaker IndexedDB persistence, AIS relay spatial indexing - Master index updated with change log and v2.5.8 stats - State file updated to full_rescan mode with scan history
- ARCH-35/36: App.ts God Object decomposed to 498 LOC shell + 8 src/app/ modules; merge risk map updated (panel-layout.ts and data-loader.ts now critical surfaces) - ARCH-59/60: Renamed section to 'Architectural Modularity', updated descriptions - ARCH-4: Updated from 1 to 4 build variants (full, tech, finance, happy) - FR3: Updated from 17 to 20 intelligence domains (added trade, giving, positive_events) - Story 0.1: Component count 52→62; added spike gate for src/app/ modular hook targets - Story 6.1: Merge risk map acceptance criteria updated - Epic 6: Added note about ARCH-35/36/59/60 updates
architecture.md (14 edits): - App.ts God Object → modular architecture (498 LOC shell + 8 src/app/ modules) - Merge Risk Map: Critical surfaces now panel-layout.ts and data-loader.ts - Source tree: added src/app/ directory with all 8 module files and LOC counts - Domain counts: 17→20 (added trade, giving, positive_events) - Component counts: 52→62, service counts: 79→~95 - RPC counts: 46→57 - Build variants: 1→4 (full, tech, finance, happy) - Cross-cutting concerns and constraints sections updated ux-design-specification.md (6 edits): - God Object UX ceiling → Modular architecture opportunity - Domain counts: 17→20 in user profiles, flow optimization, success metrics - Component inventory: 52→62 (26,696 LOC) - Panel coverage: 17→20 domains
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Upstream Sync: v2.5.5 → v2.5.8
Merges 72 commits from
koala73/worldmonitorinto our fork. Zero merge conflicts.Notable Upstream Changes
New Features:
Security:
Performance:
Fixes:
Local Changes Carried Forward
@vercel/analyticsdependency added topackage.json_spec/planning-artifacts/epics.mdVerification
tsc --noEmit) — pass