mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
0169245f45063d9d49ce38ca3f0d87f57f2be3ce
47 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0169245f45 |
feat(seo): BlogPosting schema, FAQPage JSON-LD, extensible author system (#2284)
* feat(seo): BlogPosting schema, FAQPage JSON-LD, author system, AI crawler welcome Blog structured data: - Change @type Article to BlogPosting for all blog posts - Author: Organization to Person with extensible default (Elie Habib) - Add per-post author/authorUrl/authorBio/modifiedDate frontmatter fields - Auto-extract FAQPage JSON-LD from FAQ sections in all 17 posts - Show Updated date when modifiedDate differs from pubDate - Add author bio section with GitHub avatar and fallback Main app: - Add commodity variant to middleware VARIANT_HOST_MAP and VARIANT_OG - Add commodity.worldmonitor.app to sitemap.xml - Shorten index.html meta description to 136 chars (was 161) - Remove worksFor block from index.html author JSON-LD - Welcome all bots in robots.txt (removed per-bot blocks, global allows) - Update llms.txt: five variants listed, all 17 blog post URLs added * fix(seo): scope FAQ regex to section boundary, use author-aware avatar - extractFaqLd now slices only to the next ## heading (was: to end of body) preventing bold text in post-FAQ sections from being mistakenly extracted - Avatar src now derived from DEFAULT_AUTHOR_GITHUB constant (koala73) only when using the default author; custom authors fall back to favicon so multi-author posts show a correct image instead of the wrong profile |
||
|
|
32ca22d69f |
feat(analytics): add Umami analytics via self-hosted instance (#1914)
* feat(analytics): add Umami analytics via self-hosted instance Adds Umami analytics script from abacus.worldmonitor.app and updates CSP headers in both index.html and vercel.json to allow the script. * feat(analytics): complete Umami integration with event tracking - Add data-domains to index.html script to exclude dev traffic - Add Umami script to /pro page and blog (Base.astro) - Add TypeScript Window.umami shim to vite-env.d.ts - Wire analytics.ts facade to Umami (replaces PostHog no-ops): search, country clicks, map layers, panels, LLM usage, theme, language, variant switch, webcam, download, findings, deeplinks - Add direct callsite tracking for: settings-open, mcp-connect-attempt, mcp-connect-success, mcp-panel-add, widget-ai-open/generate/success, news-summarize, news-sort-toggle, live-news-fullscreen, webcam-fullscreen, search-open (desktop/mobile/fab) * fix(analytics): add Tauri CSP allowlist for Umami + skip programmatic layer events - Add abacus.worldmonitor.app to Tauri CSP script-src and connect-src so Umami loads in the desktop WebView (analytics exception to the no-cloud-data rule — needed to know if desktop is used) - Filter trackMapLayerToggle to user-initiated events only to avoid inflating counts with programmatic toggles on page load |
||
|
|
c4a76b2c4a |
fix(security): allow vercel.live and *.vercel.app in CSP for preview deployments (#1887)
Vercel preview toolbar and preview URLs were blocked by missing CSP entries. Both the meta tag (index.html) and HTTP header (vercel.json) must be in sync since browsers enforce all active policies simultaneously. - Add sha256-903UI9... hash to index.html script-src (was in vercel.json only, causing production inline script blocks from the meta tag policy) - Add https://vercel.live and https://*.vercel.app to frame-src in both files - Add https://vercel.live and https://*.vercel.app to frame-ancestors in vercel.json |
||
|
|
d101c03009 |
fix: unblock geolocation and fix stale CSP hash (#1709)
* fix: unblock geolocation and fix stale CSP hash for SW nuke script Permissions-Policy had geolocation=() which blocked navigator.geolocation used by user-location.ts. Changed to geolocation=(self). CSP script-src had a stale SHA-256 hash (903UI9my...) that didn't match the current SW nuke script content. The script was silently blocked in production, preventing recovery from stale service workers after deploys. Replaced with the correct hash (4Z2xtr1B...) in both vercel.json and index.html meta tag. * test: update permissions-policy test for geolocation=(self) Move geolocation from "disabled" list to "delegated" assertions since it now allows self-origin access for user-location.ts. |
||
|
|
987ed03f5d |
feat(webcams): add webcam map layer with Windy API integration (#1540) (#1540)
- Webcam markers on flat, globe, and DeckGL maps with category-based icons - Server-side spatial queries via Redis GEOSEARCH with quantized bbox caching - Pinned webcams panel with localStorage persistence - Seed script for Windy API with regional bounding boxes and adaptive splitting - Input validation (webcamId regex + encodeURIComponent) and NaN projection guards - Bandwidth optimizations: zoom threshold, bbox overlap check, 1s cooldown - Client-side image cache with 200-entry FIFO eviction - Globe altitude-based viewport estimation for webcam loading - CSP updates for webcam iframe sources - Seed-meta key for health.js freshness tracking |
||
|
|
59cd313e16 |
fix(csp): add commodity variant to CSP and fix iframe variant navigation (#1506)
* fix(csp): add commodity variant to CSP and fix iframe variant navigation
- Add commodity.worldmonitor.app to frame-src and frame-ancestors in
vercel.json and index.html CSP — was missing while all other variants
were listed
- Open variant links in new tab when app runs inside an iframe to prevent
sandbox navigation errors ("This content is blocked")
- Add allow-popups and allow-popups-to-escape-sandbox to pro page iframe
sandbox attribute
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(csp): add missing variant subdomains to tauri.conf.json frame-src
Sync tauri.conf.json CSP with index.html and vercel.json by adding
finance, commodity, and happy worldmonitor.app subdomains to frame-src.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add PR screenshots for CSP fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Elie Habib <elie.habib@gmail.com>
|
||
|
|
2a7d7fc3fe |
fix: standardize brand name to "World Monitor" with space (#1463)
Replace "WorldMonitor" with "World Monitor" in all user-facing display text across blog posts, docs, layouts, structured data, footer, offline page, and X-Title headers. Technical identifiers (User-Agent strings, X-WorldMonitor-Key headers, @WorldMonitorApp handle, function names) are preserved unchanged. Also adds anchors color to Mintlify docs config to fix blue link color in dark mode. |
||
|
|
6b2550ff49 |
fix(csp): allow cross-subdomain framing for Pro page variant switcher (#1332)
* fix(csp): allow cross-subdomain framing and add finance to frame-src frame-ancestors 'self' blocked tech/finance variants from rendering inside the Pro landing page iframe. Widen to *.worldmonitor.app. Also adds missing finance.worldmonitor.app to frame-src. Closes #1322 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(csp): remove conflicting X-Frame-Options and tighten frame-ancestors X-Frame-Options: SAMEORIGIN contradicts the new frame-ancestors directive that allows cross-subdomain framing. Modern browsers prioritize frame-ancestors over X-Frame-Options, but sending both is contradictory and gets flagged by security scanners. Remove X-Frame-Options entirely. Also replace wildcard *.worldmonitor.app with explicit subdomain list to limit the framing scope to known variants only. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Elie Habib <elie.habib@gmail.com> |
||
|
|
4306322b67 |
fix(seo): comprehensive SEO improvements for /pro and main pages (#1271)
Pro page (/pro): - Shorten title to ≤60 chars for SERP visibility - Fix canonical + all URLs to www.worldmonitor.app - Fix multiple H1 (Enterprise modal H1→H2) - Fix heading hierarchy (H2→H4 jumps → proper H2→H3→H4) - Sync FAQPage JSON-LD with all 8 visible FAQs - Add twitter:title, twitter:description, og:locale, og:image:alt - Add hreflang tags for all 21 supported languages - Add <noscript> fallback with key SEO content - Add SSG prerender script (injects text into built HTML) - Self-host WIRED logo SVG (was loading from Wikipedia) - Add aria-labels on footer links and CTAs - Fix i18n to read ?lang= query parameter (was only localStorage + navigator) Main page: - Fix canonical + OG/Twitter URLs to www.worldmonitor.app - Update twitter:site/creator to @worldmonitorai - Add <noscript> with H1, description, features, and /pro link - Add hreflang tags for all 21 languages - Add og:image:alt meta tag - Add @worldmonitorai to JSON-LD sameAs - Align title with variant-meta.ts Shared: - Update sitemap.xml URLs to www.worldmonitor.app - Update robots.txt sitemap reference to www - Update variant-meta.ts full variant URL to www |
||
|
|
dfc175023a |
feat(pro): Pro waitlist landing page with referral system (#1140)
* fix(desktop): settings UI redesign, IPC security hardening, release profile Settings window: - Add titlebar drag region (macOS traffic light clearance) - Move Export/Import from Overview to Debug & Logs section - Category cards grid changed to 3-column layout Security (IPC trust boundary): - Add require_trusted_window() to get_desktop_runtime_info, open_url, open_live_channels_window_command, open_youtube_login - Validate base_url in open_live_channels_window_command (localhost-only http) Performance: - Add [profile.release] with fat LTO, codegen-units=1, strip, panic=abort - Reuse reqwest::Client via app state with connection pooling - Debounce window resize handler (150ms) in EventHandlerManager * feat(pro): add Pro waitlist landing page with referral system - React 19 + Vite 6 + Tailwind v4 landing page at /pro - Cloudflare Turnstile + honeypot bot protection - Resend transactional confirmation emails with branded template - Viral referral system: unique codes, position tracking, social share - Convex schema: referralCode, referredBy, referralCount fields + counters table - O(1) position counter pattern instead of O(n) collection scan - SEO: structured data, sitemap, scrolling source marquee - Vercel routing: /pro rewrite + cache headers + SPA exclusion - XSS-safe DOM rendering (no innerHTML with user data) |
||
|
|
fce836039b |
feat(map): migrate basemap from CARTO to self-hosted PMTiles on R2 (#1064)
* feat(map): migrate basemap from CARTO to self-hosted PMTiles on Cloudflare R2 Replace CARTO tile provider (frequent 403 errors) with self-hosted PMTiles served from Cloudflare R2. Uses @protomaps/basemaps for style generation with OpenFreeMap as automatic fallback when VITE_PMTILES_URL is unset. - Add pmtiles and @protomaps/basemaps dependencies - Create src/config/basemap.ts for PMTiles protocol registration and style building - Update DeckGLMap.ts to use PMTiles styles (non-happy variants) - Fix fallback detection using data event instead of style.load - Update SW cache rules: replace CARTO/MapTiler with PMTiles NetworkFirst - Add Protomaps preconnect hints in index.html - Bundle pmtiles + @protomaps/basemaps in maplibre chunk - Upload 3.4GB world tiles (zoom 0-10) to R2 bucket worldmonitor-maps * fix(map): use CDN custom domain maps.worldmonitor.app for PMTiles Replace r2.dev URL with custom domain backed by Cloudflare CDN edge. Update preconnect hint and .env.example with production URL. * fix(map): harden PMTiles fallback detection to prevent false triggers - Require 2+ network errors before triggering OpenFreeMap fallback - Use persistent data listener instead of once (clears timeout on first tile load) - Increase fallback timeout to 10s for PMTiles header + initial tile fetch - Add console.warn for map errors to aid debugging - Remove redundant style.load listener (fires immediately for inline styles) * feat(settings): add Map Tile Provider selector in settings Add dropdown in Settings → Map section to switch between: - Auto (PMTiles → OpenFreeMap fallback) - PMTiles (self-hosted) - OpenFreeMap - CARTO Choice persists in localStorage and reloads basemap instantly. * fix(map): make OSS-friendly — default to free OpenFreeMap, hide PMTiles when unconfigured - Default to OpenFreeMap when VITE_PMTILES_URL is unset (zero config for OSS users) - Hide PMTiles/Auto options from settings dropdown when no PMTiles URL configured - If user previously selected PMTiles but env var is removed, gracefully fall back - Remove production URL from .env.example to avoid exposing hosted tiles - Add docs link for self-hosting PMTiles in .env.example * docs: add map tile provider documentation to README and MAP_ENGINE.md Document the tile provider system (OpenFreeMap, CARTO, PMTiles) in MAP_ENGINE.md with self-hosting instructions, fallback behavior, and OSS-friendly defaults. Update README to reference tile providers in the feature list, tech stack, and environment variables table. * fix: resolve rebase conflicts and fix markdown lint errors - Restore OSS-friendly basemap defaults (MAP_PROVIDER_OPTIONS as IIFE, getMapProvider with hasTilesUrl check) - Fix markdown lint: add blank lines after ### headings in README - Reconcile UnifiedSettings import with MAP_PROVIDER_OPTIONS constant |
||
|
|
34d23c651d |
fix: force-clear stale SW on bundle 404 + swap basemap to OpenFreeMap (#1073)
* fix: auto-reload page when new service worker activates When a new SW takes control (after deploy), automatically reload the page so users get fresh HTML + correct bundle hashes. Prevents the one-time 404 on first load after deploy where old SW serves stale HTML. * fix: force-clear stale SW on bundle 404 + swap basemap to OpenFreeMap - Add inline script to index.html that detects 404s on /assets/ scripts, unregisters all service workers, clears all caches, and reloads once. Uses sessionStorage guard to prevent infinite reload loops. - Swap default basemap from Carto (CORS 403 blocking) to OpenFreeMap. Carto demoted to fallback. Attribution updated. - Add CSP hash for the new inline script. * fix: catch link preload 404s in stale SW detector Expand the inline error handler to detect both <script> and <link> (modulepreload) 404s on /assets/. Vite injects modulepreload links in <head> that fire errors before module scripts. |
||
|
|
fccfa79a29 |
fix: remove emrldco analytics and improve basemap fallback reliability (#1052)
- Remove emrldco.com analytics script and CSP entries from index.html, vercel.json, and tauri.conf.json - Replace setStyle() basemap fallback with full map recreation — setStyle() after a failed initial style load leaves MapLibre in a broken state - Add 403/Forbidden to error detection patterns for basemap failures - Scope fallback to pre-style-load errors only (post-load tile errors don't warrant destroying a working map) |
||
|
|
02f3fe77a9 |
feat: Arabic font support and HLS live streaming UI (#1020)
* feat: enhance support for HLS streams and update font styles * chore: add .vercelignore to exclude large local build artifacts from Vercel deploys * chore: include node types in tsconfig to fix server type errors on Vercel build * fix(middleware): guard optional variant OG lookup to satisfy strict TS * fix: desktop build and live channels handle null safety - scripts/build-sidecar-sebuf.mjs: Skip building removed [domain]/v1/[rpc].ts (removed in #785) - src/live-channels-window.ts: Add optional chaining for handle property to prevent null errors - src-tauri/Cargo.lock: Bump version to 2.5.24 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix: address review issues on PR #1020 - Remove AGENTS.md (project guidelines belong to repo owner) - Restore tracking script in index.html (accidentally removed) - Revert tsconfig.json "node" types (leaks Node globals to frontend) - Add protocol validation to isHlsUrl() (security: block non-http URIs) - Revert Cargo.lock version bump (release management concern) * fix: address P2/P3 review findings - Preserve hlsUrl for HLS-only channels in refreshChannelInfo (was incorrectly clearing the stream URL on every refresh cycle) - Replace deprecated .substr() with .substring() - Extract duplicated HLS display name logic into getChannelDisplayName() --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Elie Habib <elie.habib@gmail.com> |
||
|
|
f83daf5cb3 |
fix(csp): add emrldco inline script hash to index.html CSP (#1014)
PR #1006 moved the emrldco analytics loader from <body> to <head> but didn't add its sha256 hash to the Content-Security-Policy script-src directive, causing the script to be blocked. |
||
|
|
fdbf208e8e | fix: move emrldco script from <body> to <head> (#1006) | ||
|
|
e771c3c6e0 |
feat: add emrldco analytics script with lazy loading (#1000)
Loads the script on window.load event so it never blocks initial render. CSP updated in both index.html and tauri.conf.json. |
||
|
|
30e6022959 | feat(mobile): full-viewport map with geolocation centering (#942) | ||
|
|
0a4a470114 |
fix(csp,runtime): add missing script hash and finance variant to remote hosts (#798)
- Add sha256-PnEBZii+iFaNE2EyXaJhRq34g6bdjRJxpLfJALdXYt8= to CSP script-src to resolve console violation on current builds - Add finance to DEFAULT_REMOTE_HOSTS so desktop Tauri builds resolve the correct API base URL without relying on the full fallback |
||
|
|
83cc35f1d6 |
fix(api): remove [domain] catch-all that intercepted all RPC routes (#753 regression) (#785)
Vercel routes dynamic [domain] segments BEFORE specific directory names, so api/[domain]/v1/[rpc].ts was catching every request meant for api/intelligence/v1/[rpc].ts, api/economic/v1/[rpc].ts, etc. and returning 404. Delete the catch-all so requests reach the real handlers. Also: add missing CSP script hash, add list-iran-events cache tier, and update route-cache-tier test to read from server/gateway.ts. |
||
|
|
31793ede03 |
harden: replace CSP unsafe-inline with script hashes and add trust signals (#781)
Remove 'unsafe-inline' from script-src in both index.html and vercel.json, replacing it with SHA-256 hashes of the two static inline theme-detection scripts. Add security.txt, sitemap.xml, and Sitemap directive in robots.txt to improve scanner reputation. Fix stale variant-metadata test that was reading vite.config.ts instead of the extracted variant-meta.ts module. |
||
|
|
c956b73470 |
feat: consolidate 4 Vercel deployments into 1 via runtime variant detection (#756)
Replace build-time VITE_VARIANT resolution with hostname-based detection for web deployments. A single build now serves all 4 variants (full, tech, finance, happy) via subdomain routing, eliminating 3 redundant Vercel projects and their build minutes. - Extract VARIANT_META into shared src/config/variant-meta.ts - Detect variant from hostname (tech./finance./happy. subdomains) - Preserve VITE_VARIANT env var for desktop builds and localhost dev - Add social bot OG responses in middleware for variant subdomains - Swap favicons and meta tags at runtime per resolved variant - Restrict localStorage variant reads to localhost/Tauri only |
||
|
|
f4a3ccb28c |
feat(country-brief): maximize mode, shareable URLs, expanded sections & i18n (#743)
* country deep dive * feat(country-brief): add maximize mode, shareable URLs, and expanded sections - Add maximize button that expands side panel to fullscreen overlay (960px centered) - URL state: ?country=IR&expanded=1 deep-links to maximized view - Expanded sections: full brief with citations, market volume bars, per-asset infrastructure lists, nearby ports, and 15 news items (vs 5 in side panel) - Click-outside-to-minimize, Escape key handling (maximize→minimize→close) - XSS-safe brief formatting: escapeHtml first, then controlled transforms - Focus trap filters hidden .cdp-expanded-only elements via offsetParent check - Extract getCountryCentroid to country-geometry.ts, remove duplicate helpers - Unify panel interface via CountryBriefPanel, remove instanceof checks - Remove classic CountryBriefPage toggle (always use deep-dive panel) - Migrate hardcoded strings to i18n (signal chips, component bars, labels) - Add 8 unit tests for expanded param URL round-trip --------- Co-authored-by: Elie Habib <elie.habib@gmail.com> |
||
|
|
aa94b0fd5e |
fix(csp): allow localhost in media-src for proxied HLS & remove CNN HLS (#711)
CSP media-src only allowed https: — blocked <video> from loading HLS streams through the sidecar proxy at http://127.0.0.1:PORT. Direct HLS channels (Sky, DW, Fox) use https:// CDN URLs and worked; proxied channels (CNBC, CNN) were silently blocked, falling back to YouTube. Also remove CNN from PROXIED_HLS_MAP — the upstream stream is wrong. |
||
|
|
36e36d8b57 |
Cost/traffic hardening, runtime fallback controls, and PostHog removal (#638)
- Remove PostHog analytics runtime and configuration - Add API rate limiting (api/_rate-limit.js) - Harden traffic controls across edge functions - Add runtime fallback controls and data-loader improvements - Add military base data scripts (fetch-mirta-bases, fetch-osm-bases) - Gitignore large raw data files - Settings playground prototypes |
||
|
|
84e39ba4b1 |
fix(desktop): enable click-to-play YouTube embeds + CISA feed fixes (#476)
* fix(tech): use rss() for CISA feed, drop build from pre-push hook
- CISA Advisories used dead rss.worldmonitor.app domain (404), switch to rss() helper
- Remove Vite build from pre-push hook (tsc already catches errors)
* fix(desktop): enable click-to-play for YouTube embeds in WKWebView
WKWebView blocks programmatic autoplay in cross-origin iframes regardless
of allow attributes, Permissions-Policy, mute-first retries, or secure
context. Documented all 10 approaches tested in docs/internal/.
Changes:
- Switch sidecar embed origin from 127.0.0.1 to localhost (secure context)
- Add MutationObserver + retry chain as best-effort autoplay attempts
- Use postMessage('*') to fix tauri://localhost cross-origin messaging
- Make sidecar play overlay non-interactive (pointer-events:none)
- Fix .webcam-iframe pointer-events:none blocking clicks in grid view
- Add expand button to grid cells for switching to single view on desktop
- Add http://localhost:* to CSP frame-src in index.html and tauri.conf.json
|
||
|
|
3bd501c4ce |
fix+feat: RSS feed repairs, HLS native playback, summarization cache fix, embed improvements (#452)
* fix(feeds): replace 25 dead/stale RSS URLs and add feed validation script - Replace 16 dead feeds (404/403/timeout) with working alternatives (Google News proxies or corrected direct RSS endpoints) - Replace 6 empty feeds with correct RSS paths (VnExpress, Tuoi Tre, Live Science, Greater Good, News24, ScienceDaily) - Replace 3 stale feeds (CNN World, TVN24, Layoffs.fyi) with active sources - Remove Disrupt Africa (inactive since Jan 2024) - Add scripts/validate-rss-feeds.mjs to check all 420 feeds - Add test:feeds npm script * feat(live-news): use stable CDN HLS feeds for desktop native playback Direct HLS feeds bypass YouTube's expiring tokenized URLs and iframe cookie issues on WKWebView. 10 channels (Sky, DW, France24, Euronews, Al Arabiya, Al Jazeera, CBS News, TRT World, Sky News Arabia, Al Hadath) now play via native <video> on desktop with automatic YouTube fallback when CDN feeds are down (5-min cooldown). Also: - Fix euronews handle typo (@euabortnews → @euronews) - Fix TRT World handle (@taborrtworld → @TRTWorld) - Add fallbackVideoId to CBS News, Sky News Arabia, TRT World - Extract hlsManifestUrl from YouTube API for non-mapped channels - Add sidecar /api/youtube-embed endpoint (auth-exempt for iframes) - Switch webcam/embed iframes from cloud to local sidecar origin - CSP: allow frame-src http://127.0.0.1:* for sidecar embeds - Remove legacy WEBKIT_FORCE_SANDBOX env var (deprecated in WebKitGTK) - Add 37 tests covering HLS map integrity, decision tree ordering, cooldown logic, race safety, service layer, sidecar endpoint, and CSP * fix(summarization): pass panelId as geoContext to prevent Redis cache key collision When breaking news appeared across multiple panels (World, US, Europe, Middle East), all panels generated identical cache keys because geoContext was always undefined. The first panel's summary was served to all others. * fix(desktop): sidecar embed autoplay, webcam fullscreen, optional channel fallbacks - Sidecar YouTube embed: use mute param (not hardcoded), add play overlay for WKWebView autoplay fallback, add postMessage bridge for play/pause/ mute/unmute commands matching the cloud embed handler - Webcam iframes: only set allowFullscreen on web to prevent grid-breaking fullscreen on desktop click - Optional channels: add fallbackVideoId + useFallbackOnly for livenow-fox, abc-news, nbc-news, wion so they play instead of showing "not currently live" - Tests: 9 new assertions covering mute param, postMessage bridge, play overlay, yt-ready message, and optional channel fallback coverage (46 total) |
||
|
|
3983278f53 |
feat: dynamic sidecar port with EADDRINUSE fallback + let scoping bug (#375)
* feat: dynamic sidecar port with EADDRINUSE fallback Rust probes port 46123 via TcpListener::bind; if busy, binds port 0 for an OS-assigned ephemeral port. The actual port is stored in LocalApiState, passed to sidecar via LOCAL_API_PORT env, and exposed to frontend via get_local_api_port IPC command. Frontend resolves the port lazily on first API call (with retry-on-failure semantics) and caches it. All hardcoded 46123 references replaced with dynamic getApiBaseUrl()/getLocalApiPort() accessors. CSP connect-src broadened to http://127.0.0.1:* (frame-src unchanged). * fix: scope CSP to desktop builds and eliminate port TOCTOU race P1: Remove http://127.0.0.1:* from index.html (web build CSP). The wildcard allowed web app JS to probe arbitrary localhost services. Vite's htmlVariantPlugin now injects localhost CSP only when VITE_DESKTOP_RUNTIME=1 (desktop builds). P2: Replace Rust probe_available_port() (bind→release→spawn race) with a confirmed port handshake. Sidecar now handles EADDRINUSE fallback internally and writes the actual bound port to a file. Rust polls the port file (up to 5s) to store only the confirmed port. * fix: isSafeUrl ReferenceError — addresses scoped inside try block `let addresses = []` was declared inside the outer `try` block but referenced after the `catch` on line 200. `let` is block-scoped so every request through isSafeUrl crashed with: ReferenceError: addresses is not defined Move the declaration before the `try` so it's in scope for the return. |
||
|
|
b1d835b69f |
feat: HappyMonitor — positive news dashboard (happy.worldmonitor.app) (#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 33b8a8accc2d48acd45f3dcea97a083b8bcebbf0. * Revert "feat: expand map labels progressively as user zooms in" This reverts commit 285f91fe471925ca445243ae5d8ac37723f2eda7. * 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> |
||
|
|
d24d61f333 |
Fix Linux rendering issues and improve monospace font fallbacks (#243)
* fix: resolve AppImage blank white screen and font crash on Linux (#238) Disable WebKitGTK DMA-BUF renderer by default on Linux to prevent blank white screens caused by GPU buffer allocation failures (common with NVIDIA drivers and immutable distros like Bazzite). Add Linux-native monospace font fallbacks (DejaVu Sans Mono, Liberation Mono) to all font stacks so WebKitGTK font resolution doesn't hit out-of-bounds vector access when macOS-only fonts (SF Mono, Monaco) are unavailable. https://claude.ai/code/session_01TF2NPgSSjgenmLT2XuR5b9 * fix: consolidate monospace font stacks into --font-mono variable - Define --font-mono in :root (main.css) and .settings-shell (settings-window.css) - Align font stack: SF Mono, Monaco, Cascadia Code, Fira Code, DejaVu Sans Mono, Liberation Mono - Replace 3 hardcoded JetBrains Mono stacks with var(--font-mono) - Replace 4 hardcoded settings-window stacks with var(--font-mono) - Fix pre-existing bug: var(--font-mono) used in 4 places but never defined - Match index.html skeleton font stack to --font-mono --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
e1b9e9e8a9 | fix(csp): allow PostHog scripts from us-assets.i.posthog.com | ||
|
|
2473c4bfd4 |
feat: pre-render critical CSS skeleton in index.html for instant perceived load
Inline a minimal HTML skeleton (header bar, map placeholder, panels grid) with critical CSS directly in index.html so the page shows a structured layout immediately instead of a blank screen while JavaScript boots. - Dark theme skeleton with hardcoded colors matching CSS variables - Light theme support via [data-theme="light"] selectors - Shimmer animation on placeholder lines for visual feedback - 6 skeleton panels in a responsive grid matching the real layout - Map section with radial gradient matching the map background - Skeleton is automatically replaced when App.renderLayout() sets innerHTML - Marked aria-hidden="true" for screen reader accessibility Expected gain: perceived load time drops to <0.5s. https://claude.ai/code/session_01Fxk8GMRn2cEUq3ThC2a8e5 |
||
|
|
116fc80fc7 |
Merge remote-tracking branch 'origin/main' into feature/finance-variant
# Conflicts: # index.html # src/components/DeckGLMap.ts |
||
|
|
ed9cc922e2 | Fix finance variant runtime resiliency and API pressure | ||
|
|
db5eff4300 |
feat(04-01): add no-transition class lifecycle for FOUC prevention
- index.html and settings.html FOUC scripts add no-transition class to <html> - src/main.ts removes no-transition after first paint via requestAnimationFrame - src/settings-main.ts removes no-transition after first paint via requestAnimationFrame - Prevents transition sweep from dark-to-light on page load while enabling smooth theme toggles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
14f7d856cd |
feat(02-01): add FOUC prevention scripts and wire entry points
- Add inline FOUC prevention script to index.html and settings.html <head> - Update CSP script-src to allow 'unsafe-inline' for FOUC prevention - Import and call applyStoredTheme() in src/main.ts before App init - Import and call applyStoredTheme() in src/settings-main.ts before loadDesktopSecrets Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
a9224254a5 |
fix: security hardening — CORS, auth bypass, origin validation & bump v2.2.7
- Tighten CORS regex to block worldmonitorEVIL.vercel.app spoofing - Move sidecar /api/local-env-update behind token auth + add key allowlist - Add postMessage origin/source validation in LiveNewsPanel - Replace postMessage wildcard '*' targetOrigin with specific origin - Add isDisallowedOrigin() check to 25 API endpoints missing it - Migrate gdelt-geo & EIA from custom CORS to shared _cors.js - Add CORS to firms-fires, stock-index, youtube/live endpoints - Tighten youtube/embed.js ALLOWED_ORIGINS regex - Remove 'unsafe-inline' from CSP script-src - Add iframe sandbox attribute to YouTube embed - Validate meta-tags URL query params with regex allowlist |
||
|
|
4a7f9bfdf4 |
Allow Cloudflare Insights script in CSP
Add https://static.cloudflareinsights.com to script-src directive to prevent CSP blocking of Cloudflare Web Analytics beacon. |
||
|
|
2c2a6dfbc3 |
Fix YouTube CSP, add devtools menu, improve desktop channel switching
- Add worldmonitor.app to frame-src CSP in index.html (was only in tauri.conf.json, causing iframe block) - Add devtools feature and Help > Toggle Developer Tools menu item - Try native YouTube JS API first, fall back to cloud bridge on Error 153 - Add pause-then-play workaround for WKWebView channel switching |
||
|
|
c353cf2070 |
Reduce egress costs, add PWA support, fix Polymarket and Railway relay
Egress optimization: - Add s-maxage + stale-while-revalidate to all API endpoints for Vercel CDN caching - Add vercel.json with immutable caching for hashed assets - Add gzip compression to sidecar responses >1KB - Add gzip to Railway RSS responses (4 paths previously uncompressed) - Increase polling intervals: markets/crypto 60s→120s, ETF/macro/stablecoins 60s→180s - Remove hardcoded Railway URL from theater-posture.js (now env-var only) PWA / Service Worker: - Add vite-plugin-pwa with autoUpdate strategy - Cache map tiles (CacheFirst), fonts (StaleWhileRevalidate), static assets - NetworkOnly for all /api/* routes (real-time data must be fresh) - Manual SW registration (web only, skip Tauri) - Add offline fallback page - Replace manual manifest with plugin-generated manifest Polymarket fix: - Route dev proxy through production Vercel (bypasses JA3 blocking) - Add 4th fallback tier: production URL as absolute fallback Desktop/Sidecar: - Dual-backend cache (_upstash-cache.js): Redis cloud + in-memory+file desktop - Settings window OK/Cancel redesign - Runtime config and secret injection improvements |
||
|
|
ad4e52caee | Fix Tauri desktop runtime reliability and settings UX | ||
|
|
eb0f396d16 | Add Tauri v2 desktop scaffold and runtime bridge | ||
|
|
8f218428f1 |
Update branding: World Monitor v2 with AI focus
- README: Title to "World Monitor v2", AI-powered description - index.html: Title "Global Situation with AI Insights" - All meta tags updated (og, twitter, JSON-LD) - Added AI keywords and features - Updated site.webmanifest with AI branding |
||
|
|
5b18693704 |
Improve SEO with comprehensive meta tags and JSON-LD schema
- Shorten description to 153 chars (was 203) - Add canonical URL - Add search discovery metas (subject, classification, coverage) - Expand keywords with all tracked features - Add og:image dimensions and locale - Fix Twitter tags (name vs property) and add creator/site - Add JSON-LD WebApplication structured data with featureList - Reference new og-image.png (1200x630) for social sharing |
||
|
|
7f0899a9b4 |
Add meta tags and Open Graph for SEO and social sharing
- Primary meta tags: title, description, keywords, author, theme-color - Open Graph tags for Facebook/LinkedIn sharing - Twitter Card tags for Twitter sharing - Uses worldmonitor-icon-1024.png as og:image 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
9d60abec49 |
Add favicon and icon references
- Add favicon links to index.html - Move favico assets to public/ for Vite static serving - Add root favicon.ico for default /favicon.ico requests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
27892b306c |
Initial commit: World Monitor dashboard
Features: - Real-time geopolitical monitoring dashboard - Interactive D3.js world map with hotspots, conflicts, bases - 16 news/data panels: World, Middle East, Tech, AI/ML, Finance, etc. - Market data via Yahoo Finance (with rate limiting) - Crypto prices via CoinGecko - Prediction markets via Polymarket - Earthquake data via USGS - RSS feeds from 50+ sources including: - News: BBC, NPR, Guardian, Reuters, Al Jazeera - AI: OpenAI, Anthropic, Google AI, DeepMind - Government: White House, State Dept, Fed, SEC, Treasury - Intel: Defense One, Bellingcat, CISA, Krebs - Think Tanks: Brookings, CFR, CSIS - Custom monitors with keyword alerts - Draggable panel layout with persistence - Time range filtering for events - Dark theme optimized for monitoring |