mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
a969a9e3a38a7c581ee05573a2d49cdc58399e92
30 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a969a9e3a3 |
feat(auth): integrate clerk.dev (#1812)
* feat(auth): integrate better-auth with @better-auth/infra dash plugin Wire up better-auth server config with the dash() plugin from @better-auth/infra, and the matching sentinelClient() on the client side. Adds BETTER_AUTH_API_KEY to .env.example. * feat(auth): swap @better-auth/infra for @convex-dev/better-auth [10-01 task 1] Install @convex-dev/better-auth@0.11.2, remove @better-auth/infra, delete old server/auth.ts skeleton, rewrite auth-client.ts to use crossDomainClient + convexClient plugins. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(auth): create Convex auth component files [10-01 task 2] Add convex.config.ts (register betterAuth component), auth.config.ts (JWT/JWKS provider), auth.ts (better-auth server with Convex adapter, crossDomain + convex plugins), http.ts (mount auth routes with CORS). Uses better-auth/minimal for lighter bundle. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(auth): add admin, organization, and dash plugins [10-01] Re-install @better-auth/infra for dash() plugin to enable dash.better-auth.com admin dashboard. Add admin() and organization() plugins from better-auth/plugins for user and org management. Update both server (convex/auth.ts) and client (auth-client.ts). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): drop @better-auth/infra (Node.js deps incompatible with Convex V8) Keep admin() and organization() from better-auth/plugins (V8-safe). @better-auth/infra's dash() transitively imports SAML/SSO with node:crypto, fs, zlib — can't run in Convex's serverless runtime. Dashboard features available via admin plugin endpoints instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(11-01): create auth-state.ts with OTT handler and session subscription - Add initAuthState() for OAuth one-time token verification on page load - Add subscribeAuthState() reactive wrapper around useSession nanostore atom - Add getAuthState() synchronous snapshot getter - Export AuthUser and AuthSession types for UI consumption Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(11-01): add Google OAuth provider and wire initAuthState into App.ts - Add socialProviders.google with GOOGLE_CLIENT_ID/SECRET to convex/auth.ts - Add all variant subdomains to trustedOrigins for cross-subdomain CORS - Call initAuthState() in App.init() before panelLayout.init() - Add authModal field to AppContext interface (prepares for Plan 02) - Add authModal: null to App constructor state initialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(11-02): create AuthModal with Sign In/Sign Up tabs and Google OAuth - Sign In tab: email/password form calling authClient.signIn.email() - Sign Up tab: name/email/password form calling authClient.signUp.email() - Google OAuth button calling authClient.signIn.social({ provider: 'google', callbackURL: '/' }) - Auto-close on successful auth via subscribeAuthState() subscription - Escape key, overlay click, and X button close the modal - Loading states, error display, and client-side validation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(11-02): add AuthHeaderWidget, mount in header, add auth CSS - AuthHeaderWidget: reactive header widget showing Sign In button (anonymous) or avatar + dropdown (authenticated) - User dropdown: name, email, Free tier badge, Sign Out button calling authClient.signOut() - setupAuthWidget() in EventHandlerManager creates modal + widget, mounts at authWidgetMount span - authWidgetMount added to panel-layout.ts header-right, positioned before download wrapper - setupAuthWidget() called from App.ts after setupUnifiedSettings() - Full auth CSS: modal styles, tabs, forms, Google button, header widget, avatar, dropdown Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(11-02): add localhost:3000 to trustedOrigins for local dev CORS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): remove admin/organization plugins that break Convex adapter validator The admin() plugin adds banned/role fields to user creation data, but the @convex-dev/better-auth adapter validator doesn't include them. These plugins are Phase 12 work — will re-add with additionalFields config when needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(12-01): add Resend email transport, verification + reset callbacks, role field - Install resend SDK for transactional email - Add emailVerification with sendOnSignUp:true and fire-and-forget Resend callbacks - Add sendResetPassword callback with 1-hour token expiry - Add user.additionalFields.role (free/pro, input:false, defaultValue:free) - Create userRoles fallback table in schema with by_userId index - Create getUserRole query and setUserRole mutation in convex/userRoles.ts - Lazy-init Resend client to avoid Convex module analysis error Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(12-01): enhance auth-state with emailVerified and role fields - Add emailVerified (boolean) and role ('free' | 'pro') to AuthUser interface - Fetch role from Convex userRoles table via HTTP query after session hydration - Cache role per userId to avoid redundant fetches - Re-notify subscribers asynchronously when role is fetched for a new user - Map emailVerified from core better-auth user field (default false) - Derive Convex cloud URL from VITE_CONVEX_SITE_URL env var Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(12-01): add Convex generated files from deployment - Track convex/_generated/ files produced by npx convex dev --once Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(12-03): create panel-gating service with auth-aware showGatedCta - Add PanelGateReason enum (NONE/ANONYMOUS/UNVERIFIED/FREE_TIER) - Add getPanelGateReason() computing gating from AuthSession + premium flag - Add Panel.showGatedCta() rendering auth-aware CTA overlays - Add Panel.unlockPanel() to reverse locked state - Extract lockSvg to module-level const shared by showLocked/showGatedCta - Add i18n keys: signInToUnlock, signIn, verifyEmailToUnlock, resendVerification, upgradeDesc, upgradeToPro Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(12-02): add forgot password flow, password reset form, and token detection - Widen authModal interface in app-context.ts to support reset-password mode and setResetToken - AuthModal refactored with 4 views: signin, signup, forgot-password, reset-password - Forgot password view sends reset email via authClient.requestPasswordReset - Reset password form validates matching passwords and calls authClient.resetPassword - auth-state.ts detects ?token= param from email links, stores as pendingResetToken - App.ts routes pending reset token to auth modal after UI initialization - CSS for forgot-link, back-link, and success message elements Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(12-02): add email verification banner to AuthHeaderWidget and tier badge - Show non-blocking verification banner below header for unverified users - Banner has "Resend" button calling authClient.sendVerificationEmail - Banner is dismissible (stored in sessionStorage, reappears next session) - Tier badge dynamically shows Free/Pro based on user.role - Pro badge has gradient styling distinct from Free badge - Dropdown shows unverified status indicator with yellow dot - Banner uses fixed positioning, does not push content down - CSS for banner, pro badge, and verification status indicators Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(12-03): wire reactive auth-based gating into panel-layout - Add WEB_PREMIUM_PANELS Set (stock-analysis, stock-backtest, daily-market-brief) - Subscribe to auth state changes in PanelLayoutManager.init() - Add updatePanelGating() iterating panels with getPanelGateReason() - Add getGateAction() returning CTA callbacks per gate reason - Remove inline showLocked() calls for web premium panels - Preserve desktop _lockPanels for forecast, oref-sirens, telegram-intel - Clean up auth subscription in destroy() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(13-01): create auth-token utility and inject Bearer header in web fetch redirect - Add src/services/auth-token.ts with getSessionBearerToken() that reads session token from localStorage - Add WEB_PREMIUM_API_PATHS Set for the 4 premium market API paths - Inject Authorization: Bearer header in installWebApiRedirect() for premium paths when session exists - Desktop installRuntimeFetchPatch() left unchanged (API key only) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(13-01): create server-side session validation module - Add server/auth-session.ts with validateBearerToken() for Vercel edge gateway - Validates tokens via Convex /api/auth/get-session with Better-Auth-Cookie header - Falls back to userRoles:getUserRole Convex query for role resolution - In-memory cache with 60s TTL and 100-entry cap - Network errors not cached to allow retry on next request Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(13-02): add bearer token fallback auth for premium API endpoints - Dynamic import of auth-session.ts when premium endpoint + API key fails - Valid pro session tokens fall through to route handler - Non-pro authenticated users get 403 'Pro subscription required' - Invalid/expired tokens get 401 'Invalid or expired session' - Non-premium endpoints and static API key flow unchanged Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): sign-in button invisible in dark theme — white on white --accent is #fff in dark theme, so background: var(--accent) + color: #fff was invisible. Changed to transparent background with var(--text) color. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): add premium panel keys to full and finance variant configs stock-analysis, stock-backtest, and daily-market-brief were defined in the shared panels.ts but missing from variant DEFAULT_PANELS, causing shouldCreatePanel() to return false and panel gating CTAs to never render. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(auth): add Playwright smoke tests for auth UI (phases 12-13) 6 tests covering: Sign In button visibility, auth modal opening, modal views (Sign In/Sign Up/Forgot Password), premium panel gating for anonymous users, and auth token absence when logged out. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): remove role additionalField that breaks Convex component validator The betterAuth Convex component has a strict input validator for the user model that doesn't include custom fields. The role additionalField caused ArgumentValidationError on sign-up. Roles are already stored in the separate userRoles table — no data loss. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): use Authorization Bearer header for Convex session validation Better-Auth-Cookie header returned null — the crossDomain plugin's get-session endpoint expects Authorization: Bearer format instead. Confirmed via curl against live Convex deployment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): use verified worldmonitor.app domain for auth emails Was using noreply@resend.dev (testing domain) which can't send to external recipients. Switched to noreply@worldmonitor.app matching existing waitlist/contact emails. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): await Resend email sends — Convex kills dangling promises void (fire-and-forget) causes Convex to terminate the fetch before Resend receives it. Await ensures emails actually get sent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: update Convex generated auth files after config changes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): guard against undefined VITE_CONVEX_SITE_URL in auth-state The Convex cloud URL derivation crashed the entire app when VITE_CONVEX_SITE_URL wasn't set in the build environment (Vercel preview). Now gracefully defaults to empty string and skips role fetching when the URL is unavailable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(auth): add dash + organization plugins, remove Google OAuth, fix dark mode button - Add @better-auth/infra dash plugin for hosted admin dashboard - Add organization plugin for org management in dashboard - Add dash.better-auth.com to trustedOrigins - Remove Google OAuth (socialProviders, button, divider, CSS) - Fix auth submit button invisible in dark mode (var(--accent) → #3b82f6) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): replace dash plugin with admin — @better-auth/infra incompatible with Convex V8 @better-auth/infra imports SSO/SAML libraries requiring Node.js built-ins (crypto, fs, stream) which Convex's V8 runtime doesn't support. Replaced with admin plugin from better-auth/plugins which provides user management endpoints (set-role, list-users, ban, etc.) natively. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: remove stale Convex generated files after plugin update Convex dev regenerated _generated/ — the per-module JS files (auth.js, http.js, schema.js, etc.) are no longer emitted. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(auth): remove organization plugin — will add in subsequent PR Organization support (team accounts, invitations, member management) is not wired into any frontend flow yet. Removing to keep the auth PR focused on email/password + admin endpoints. Will add back when building the org/team feature. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add authentication & panel gating guide Documents the auth stack, panel gating configuration, server-side session enforcement, environment variables, and user roles. Includes step-by-step guide for adding new premium panels. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(test): stub panel-gating in RuntimeConfigPanel test harness Panel.ts now imports @/services/panel-gating, which wasn't stubbed — causing the real runtime.ts (with window.location) to be bundled, breaking Node.js tests with "ReferenceError: location is not defined". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): allow Vercel preview origins in Convex trustedOrigins * fix(auth): broaden Convex trustedOrigins to cover *.worldmonitor.app previews * fix(auth): use hostonly wildcard pattern for *.worldmonitor.app in trustedOrigins * fix(auth): add Convex site origins to trustedOrigins * fix(ci): add convex/ to vercel-ignore watched paths * fix(auth): remove admin() plugin — adds banned/role fields rejected by Convex validator * fix(auth): remove admin() plugin — injects banned/role fields rejected by Convex betterAuth validator * feat(auth): replace email/password with email OTP passwordless flow - Replace emailAndPassword + emailVerification with emailOTP plugin - Rewrite AuthModal: email entry -> OTP code verification (no passwords) - Remove admin() plugin (caused Convex schema validation errors) - Remove email verification banner and UNVERIFIED gate reason (OTP inherently verifies email) - Remove password reset flow (forgot/reset password views, token handling) - Clean up unused CSS (tabs, verification banner, success messages) - Update docs to reflect new passwordless auth stack Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(quick-2): harden Convex userRoles and add role cache TTL - P0: Convert setUserRole from mutation to internalMutation (not callable from client) - P2: Add 5-minute TTL to role cache in auth-state.ts - P2: Add localStorage shape warning on auth-token.ts - P3: Document getUserRole public query trade-off - P3: Fix misleading cache comment in auth-session.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(quick-2): auth widget teardown, E2E test rewrite, gateway comment - P2: Store authHeaderWidget on AppContext, destroy in EventHandlerManager.destroy() - P2: Also destroy authModal in destroy() to prevent leaked subscriptions - P1: Rewrite E2E tests for 2-view OTP modal (email input + submit button) - P1: Remove stale "Sign Up" and "Forgot Password" test assertions - P2: Replace flaky waitForTimeout(5000) with Playwright auto-retry assertion - P3: Add clarifying comment on premium bearer-token fallback in gateway Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(header): restructure header/footer, add profile editing, pro-gate playback/export - Remove version, @eliehabib, GitHub link, and download button from header - Move version + @eliehabib credit to footer brand line; download link to footer nav - Move auth widget (profile avatar) to far right of header (after settings gear) - Add default generic SVG avatar for users with no image and no name - Add profile editing in auth dropdown: display name + avatar URL with Save/Cancel - Add Settings shortcut in auth dropdown (opens UnifiedSettings) - Gate Historical Playback and Export controls behind pro role (hidden for free users) - Reactive pro-gate: subscribes to auth state changes, stores unsub in proGateUnsubscribers[] - Clean up proGateUnsubscribers on EventHandlerManager.destroy() to prevent leaks - Fix: render Settings button unconditionally (hidden via style), stable DOM structure - Fix: typed updateUser call with runtime existence check instead of (any) cast - Make initFooterDownload() private to match class conventions * feat(analytics): add Umami auth integration and event tracking - Wire analytics.ts facade to Umami (port from main #1914): search, country, map layers, panels, LLM, theme, language, variant switch, webcam, download, findings, deeplinks - Add Window.umami shim to vite-env.d.ts - Add initAuthAnalytics() that subscribes to auth state and calls identifyUser(id, role) / clearIdentity() on sign-in/sign-out - Add trackSignIn, trackSignUp, trackSignOut, trackGateHit exports - Call initAuthAnalytics() from App.ts after initAuthState() - Track sign-in/sign-up (via isNewUser flag) in AuthModal OTP verify - Track sign-out in AuthHeaderWidget before authClient.signOut() - Track gate-hit for export, playback (event-handlers) and pro-banner * feat(auth): professional avatar widget with colored initials and clean profile edit - Replace white-circle avatar with deterministic colored initials (Gmail/Linear style) - Avatar color derived from email hash across 8-color palette - Dropdown redesigned: row layout with large avatar + name/email/tier info - Profile edit form: name-only (removed avatar URL field) - Remove Settings button from dropdown (gear icon in header is sufficient) - Discord community widget: single CTA link, no redundant text label - Add all missing CSS for dropdown interior, profile edit form, menu items * fix(auth): lock down billing tier visibility and fix TOCTOU race P1: getUserRole converted to internalQuery — billing tier no longer accessible via any public Convex client API. Exposed only through the new authenticated /api/user-role HTTP action which validates the session Bearer token before returning the role. P1: subscribeAuthState generation counter + AbortController prevents rapid sign-in/sign-out from delivering stale role for wrong user. P2: typed RawSessionUser/RawSessionValue interfaces replace any casts at the better-auth nanostore boundary. fetchUserRole drops userId param — server derives identity from Bearer token only. P2: isNewUser heuristic removed from OTP verify — better-auth emailOTP has no reliable isNewUser signal. All verifications tracked as trackSignIn. OTP resend gets 30s client-side cooldown. P2: auth-token.ts version pin comment added (better-auth@1.5.5 + @convex-dev/better-auth@0.11.2). Gateway inner PREMIUM_RPC_PATHS comment clarified to explain why it is not redundant. Adds tests/auth-session.test.mts: 11 tests covering role fallback endpoint selection, fail-closed behavior, and CORS origin matching. * feat(quick-4): replace better-auth with Clerk JS -- packages, Convex config, browser auth layer - Remove better-auth, @convex-dev/better-auth, @better-auth/infra, resend from dependencies - Add @clerk/clerk-js and jose to dependencies - Rewrite convex/auth.config.ts for Clerk issuer domain - Simplify convex/convex.config.ts (remove betterAuth component) - Delete convex/auth.ts, convex/http.ts, convex/userRoles.ts - Remove userRoles table from convex/schema.ts - Create src/services/clerk.ts with Clerk JS init, sign-in, sign-out, token, user metadata, UserButton - Rewrite src/services/auth-state.ts backed by Clerk (same AuthUser/AuthSession interface) - Delete src/services/auth-client.ts (better-auth client) - Delete src/services/auth-token.ts (localStorage token scraping) - Update .env.example with Clerk env vars, remove BETTER_AUTH_API_KEY Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(quick-4): UI components, runtime fetch, server-side JWT, CSP, and tests - Delete AuthModal.ts, create AuthLauncher.ts (thin Clerk.openSignIn wrapper) - Rewrite AuthHeaderWidget.ts to use Clerk UserButton + openSignIn - Update event-handlers.ts to use AuthLauncher instead of AuthModal - Rewrite runtime.ts enrichInitForPremium to use async getClerkToken() - Rewrite server/auth-session.ts for jose-based JWT verification with cached JWKS - Update vercel.json CSP: add *.clerk.accounts.dev to script-src and frame-src - Add Clerk CSP tests to deploy-config.test.mjs - Rewrite e2e/auth-ui.spec.ts for Clerk UI - Rewrite auth-session.test.mts for jose-based validation - Use dynamic import for @clerk/clerk-js to avoid Node.js test breakage Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): allow Clerk Pro users to load premium data on web The data-loader gated premium panel loading (stock-analysis, stock-backtest, daily-market-brief) on WORLDMONITOR_API_KEY only, which is desktop-only. Web users with Clerk Pro auth were seeing unlocked panels stuck on "Loading..." because the requests were never made. Added hasPremiumAccess() helper that checks for EITHER desktop API key OR Clerk Pro role, matching the migration plan Phase 7 requirements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): address PR #1812 review — all 4 merge blockers + 3 gaps Blockers: 1. Remove stale Convex artifacts (http.js, userRoles.js, betterAuth component) from convex/_generated/api.d.ts 2. isProUser() now checks getAuthState().user?.role === 'pro' alongside legacy localStorage keys 3. Finance premium refresh scheduling now fires for Clerk Pro web users (not just API key holders) 4. JWT verification now validates audience: 'convex' to reject tokens scoped to other Clerk templates Gaps: 5. auth-session tests: 10 new cases (valid pro/free, expired, wrong key/audience/issuer, missing sub/plan, JWKS reuse) using self-signed keys + local JWKS server 6. premium-stock-gateway tests: 4 new bearer token cases (pro→200, free→403, invalid→401, public unaffected) 7. docs/authentication.mdx rewritten for Clerk (removed all better-auth references, updated stack/files/env vars/roles sections) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address P1 reactive Pro UI + P2 daily-market-brief + P3 stale env vars P1 — In-session Pro UI changes no longer require a full reload: - setupExportPanel: removed early isProUser() return, always creates and relies on reactive subscribeAuthState show/hide - setupPlaybackControl: same pattern — always creates, reactive gate - Custom widget panels: always loaded regardless of Pro status - Pro add-panel and MCP add-panel blocks: always rendered, shown/hidden reactively via subscribeAuthState callback - Flight search wiring: always wired, checks Pro status inside callback so mid-session sign-ins work immediately P2 — daily-market-brief added to hasPremiumAccess() block in loadAllData() so Clerk Pro web users get initial data load (was only primed in primeVisiblePanelData, missing from the general reload path) P3 — Removed stale CONVEX_SITE_URL and VITE_CONVEX_SITE_URL from docs/authentication.mdx env vars table (neither is referenced in codebase) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add isProUser import, populate PREMIUM_RPC_PATHS, and fix bearer token auth flow - Added missing isProUser import in App.ts (fixes typecheck) - Populated PREMIUM_RPC_PATHS with stock analysis endpoints - Restructured gateway auth: trusted browser origins bypass API key for premium endpoints (client-side isProUser gate), while bearer token validation runs as a separate step for premium paths when present Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(gateway): require credentials for premium paths + defer free-tier enforcement until auth ready P0: Removed trusted-origin bypass for premium endpoints — Origin header is spoofable and cannot be a security boundary. Premium paths now always require either an API key or valid bearer token. P1: Deferred panel/source free-tier enforcement until auth state resolves. Previously ran in the constructor before initAuthState(), causing Clerk Pro users to have their panels/sources trimmed on every startup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(auth): apply WorldMonitor design system to Clerk modal Theme-aware appearance config passed to clerk.load(), openSignIn(), and mountUserButton(). Dark mode: dark bg (#111), green primary (#44ff88), monospace font. Light mode: white bg, green-600 primary (#16a34a). Reads document.documentElement.dataset.theme at call time so theme switches are respected. * fix(auth): gate Clerk init and auth widget behind BETA_MODE Clerk auth initialization and the Sign In header widget are now only activated when localStorage `worldmonitor-beta-mode` is set to "true", allowing silent deployment for internal testing before public rollout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(auth): gate Clerk init and auth widget behind isProUser() Clerk auth initialization and the Sign In header widget are now only activated when the user has wm-widget-key or wm-pro-key in localStorage (i.e. isProUser() returns true), allowing silent deployment for internal testing before public rollout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(data-loader): replace stale isProUser() with hasPremiumAccess() loadMarketImplications() still referenced the removed isProUser import, causing a TS2304 build error. Align with the rest of data-loader.ts which uses hasPremiumAccess() (checks both API key and Clerk auth). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(auth): address PR #1812 review — P1 security fixes + P2 improvements P1 fixes: - Add algorithms: ['RS256'] allowlist to jwtVerify (prevents alg:none bypass) - Reset loadPromise on Clerk init failure (allows retry instead of permanent breakage) P2 fixes: - Extract PREMIUM_RPC_PATHS to shared module (eliminates server/client divergence risk) - Add fail-fast guard in convex/auth.config.ts for missing CLERK_JWT_ISSUER_DOMAIN - Add 50s token cache with in-flight dedup to getClerkToken() (prevents concurrent races) - Sync Clerk CSP entries to index.html and tauri.conf.json (previously only in vercel.json) - Type clerkInstance as Clerk instead of any Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(auth): clear cached token on signOut() Prevents stale token from being returned during the ≤50s cache window after a user signs out. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Sebastien Melki <sebastien@anghami.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Sebastien Melki <sebastienmelki@gmail.com> |
||
|
|
a1c3c1d684 |
feat(panels): AI Market Implications — LLM trade signals from live world state (#2146) (#2165)
* fix(intelligence): use camelCase field names for ListMarketImplicationsResponse
* fix(bootstrap): register marketImplications in cache-keys.ts and add hydration consumer
* chore: stage all market-implications feature files for proto freshness check
* feat(market-implications): add LLM routing env vars for market implications stage
* fix(market-implications): move types to services layer to fix boundary violation
* fix: add list-market-implications gateway tier entry
* fix(market-implications): add health.js entries + i18n tooltip key
- api/health.js: add marketImplications to BOOTSTRAP_KEYS
('intelligence:market-implications:v1') and SEED_META
(seed-meta:intelligence:market-implications, maxStaleMin=150 = 2x
the 75min TTL, matching gold standard)
- en.json: add components.marketImplications.infoTooltip which was
referenced in MarketImplicationsPanel but missing from locales
* fix(market-implications): wire CMD+K entry and panels.marketImplications i18n key
- commands.ts: add panel:market-implications command with trade/signal
keywords so the panel appears in CMD+K search
- en.json: add panels.marketImplications used by UnifiedSettings panel
toggle display and SearchModal label resolution
|
||
|
|
5e8a106999 |
feat(forecast): extract critical news signals (#2064)
* feat(forecast): extract critical news signals * fix(forecast): harden critical signal extraction * feat(forecast): add structured urgent signal extraction * docs(env): document critical forecast llm overrides |
||
|
|
429b1d99dd |
Revert "feat: seed orchestrator with auto-seeding, persistence, and managemen…" (#2060)
This reverts commit
|
||
|
|
bb79386d24 |
feat: seed orchestrator with auto-seeding, persistence, and management CLI (#1940)
* feat(docker): enable Redis RDB persistence, add seed-orchestrator to supervisord, copy scripts into image
* feat(orchestrator): add prefixed logger utility
* feat(orchestrator): add seed-meta read/write helpers
* feat(orchestrator): add child process runner with timeout support
* feat(orchestrator): add central seed catalog with 42 seeders
* feat(orchestrator): implement seed orchestrator with tiered scheduling
Adds scripts/seed-orchestrator.mjs with:
- classifySeeders: classify seeders into active/skipped by env vars
- buildStartupSummary: human-readable startup report
- Tiered cold start (hot/warm/cold/frozen with per-tier concurrency)
- Freshness check via seed-meta keys before running stale seeders
- Steady-state scheduling with setTimeout-based recurring timers
- Overlap protection, retry-after-60s, consecutive failure demotion
- Global concurrency cap of 5 with queue-based overflow
- Graceful shutdown on SIGTERM/SIGINT (15s drain timeout)
- Meta writing for null-metaKey seeders to seed-meta:orchestrator:{name}
* fix(seeds): use local API for warm-ping seeders in Docker mode
* fix(orchestrator): allow ACLED email+password as alternative to access token
* feat(wmsm): add seed manager CLI scaffold with help, catalog, and checks
* feat(wmsm): implement status command with freshness display
* feat(wmsm): implement schedule command with next-run estimates
* feat(wmsm): implement refresh command with single and --all modes
* feat(wmsm): implement flush and logs commands
* fix(wmsm): auto-detect docker vs podman runtime
* feat(orchestrator): extract pure scheduling functions and add test harness
* feat(orchestrator): add SEED_TURBO=real|dry mode with compressed intervals
* feat(orchestrator): add SEED_TURBO env passthrough and fix retry log message
|
||
|
|
f4183f99c7 |
feat: self-hosted Docker stack (#1521)
* feat: self-hosted Docker stack with nginx, Redis REST proxy, and seeders
Multi-stage Docker build: esbuild TS handler compilation, vite frontend
build, nginx + Node.js API under supervisord. Upstash-compatible Redis
REST proxy with command allowlist for security. AIS relay WebSocket
sidecar. Seeder wrapper script with auto-sourced env vars from
docker-compose.override.yml. Self-hosting guide with architecture
diagram, API key setup, and troubleshooting.
Security: Redis proxy command allowlist (blocks FLUSHALL/CONFIG/EVAL),
nginx security headers (X-Content-Type-Options, X-Frame-Options,
Referrer-Policy), non-root container user.
* feat(docker): add Docker secrets support for API keys
Entrypoint reads /run/secrets/* files and exports as env vars at
startup. Secrets take priority over environment block values and
stay out of docker inspect / process metadata.
Both methods (env vars and secrets) work simultaneously.
* fix(docker): point supervisord at templated nginx config
The entrypoint runs envsubst on nginx.conf.template and writes
the result to /tmp/nginx.conf (with LOCAL_API_PORT substituted
and listening on port 8080 for non-root). But supervisord was
still launching nginx with /etc/nginx/nginx.conf — the default
Alpine config that listens on port 80, which fails with
"Permission denied" under the non-root appuser.
* fix(docker): remove KEYS from Redis allowlist, fix nginx header inheritance, add LLM vars to seeders
- Remove KEYS from redis-rest-proxy allowlist (O(N) blocking, Redis DoS risk)
- Move security headers into each nginx location block to prevent add_header
inheritance suppression
- Add LLM_API_URL / LLM_API_KEY / LLM_MODEL to run-seeders.sh grep filter
so LLM API keys set in docker-compose.override.yml are forwarded to seed scripts
* fix(docker): add path-based POST to Redis proxy, expand allowlist, add missing seeder secrets
- Add POST /{command}/{args...} handler to redis-rest-proxy so Upstash-style
path POSTs work (setCachedJson uses POST /set/<key>/<value>/EX/<ttl>)
- Expand allowlist: HLEN, LTRIM (seed-military-bases, seed-forecasts),
ZREVRANGE (premium-stock-store), ZRANDMEMBER (seed-military-bases)
- Add ACLED_EMAIL, ACLED_PASSWORD, OPENROUTER_API_KEY, OLLAMA_API_URL,
OLLAMA_MODEL to run-seeders.sh so override keys reach host-run seeders
---------
Co-authored-by: Elie Habib <elie.habib@gmail.com>
|
||
|
|
1c0e292260 | feat(llm): support forecast model overrides (#1751) | ||
|
|
39931456a1 |
feat(forecast): add structured scenario pipeline and trace export (#1646)
* feat(forecast): add AI Forecasts prediction module (Pro-tier)
MiroFish-inspired prediction engine that generates structured forecasts
across 6 domains (conflict, market, supply chain, political, military,
infrastructure) using existing WorldMonitor data streams.
- Proto definitions for ForecastService with GetForecasts RPC
- Dedicated seed script (seed-forecasts.mjs) with 6 domain detectors,
cross-domain cascade resolver, prediction market calibration, and
trend detection via prior snapshot comparison
- Premium-gated RPC handler (PREMIUM_RPC_PATHS enforcement)
- Lazy-loaded ForecastPanel with domain filters, probability bars,
trend arrows, signal evidence, and cascade links
- Health monitoring integration (seed-meta freshness tracking)
- Refresh scheduler with API key guard
* test(forecast): add 47 unit tests for forecast detectors and utilities
Covers forecastId, normalize, resolveCascades, calibrateWithMarkets,
computeTrends, and smoke tests for all 6 domain detectors. Exports
testable functions from seed script with direct-run guard.
* fix(forecast): domain mismatch 'infra' vs 'infrastructure', add panel category
- Seed script used 'infra' but ForecastPanel filtered on 'infrastructure',
causing Infra tab to show zero results
- Added 'forecast' to intelligence category in PANEL_CATEGORY_MAP
* fix(forecast): move CSS to one-time injection, improve type safety
- P2: Move style block from setContent to one-time document.head injection
to prevent CSS accumulation on repeated renders
- P3: Replace +toFixed(3) with Math.round for readability in seed script
- P3: Use Forecast type instead of any[] in RPC handler filter
* fix(forecast): handle sebuf proto data shapes from Redis
Detectors now normalize CII scores from server-side proto format
(combinedScore, TREND_DIRECTION_RISING, region) to uniform shape.
Outage severity handles proto enum format (SEVERITY_LEVEL_HIGH).
Added confidence floor of 0.3 for single-source predictions.
Verified against live Redis: 2 predictions generated (Iran infra
shutdown, IL political instability).
* feat(forecast): unlock AI Forecasts on web, lock desktop only (trial)
- Remove forecast RPC from PREMIUM_RPC_PATHS (web access is free)
- Panel locked on desktop only (same as oref-sirens/telegram-intel)
- Remove API key guards from data-loader and refresh scheduler
- Web users get full access during trial period
* chore: regenerate proto types with make generate
Re-ran make generate after rebasing on main. Plugin v0.7.0 dropped
@ts-nocheck from output, added it back to all 50 generated files.
Fixed 4 type errors from proto codegen changes:
- MarketSource enum -> string union type
- TemporalAnomalyProto -> TemporalAnomaly rename
- webcam lastUpdated number -> string
* chore: add proto freshness check to pre-push hook
Runs make generate before push and compares checksums of generated files.
If proto types are stale, blocks push with instructions to regenerate.
Skips gracefully if buf CLI is not installed.
* fix(forecast): use chokepoints v4 key, include ciiContribution in unrest
- P1: Switch chokepoints input from stale v2 to active v4 Redis key,
matching bootstrap.js and cache-keys.ts
- P2: Add ciiContribution to unrest component fallback chain in
normalizeCiiEntry so political detector reads the correct sebuf field
* feat(forecast): Phase 2 LLM scenario enrichment + confidence model
MiroFish-inspired enhancements:
- LLM scenario narratives via Groq/OpenRouter (narrative-only, no numeric
adjustment). Evidence-grounded prompts with mandatory signal citation
and few-shot examples from MiroFish's SECTION_SYSTEM_PROMPT_TEMPLATE.
- Top-4 predictions batched into single LLM call for cost efficiency.
- News context from newsInsights attached to all predictions for LLM
prompt grounding (NOT in signals, cannot affect confidence).
- Deterministic confidence model: source diversity via SIGNAL_TO_SOURCE
mapping (deduplicates cii+cii_delta, theater+indicators) + calibration
agreement from prediction market drift. Floor 0.2, ceiling 1.0.
- Output validation: rejects scenarios without signal references.
- Truncated JSON repair for small model output.
- Structured JSON logging for LLM calls.
- Redis cache for LLM scenarios (1h TTL).
- 23 new tests (70 total), all passing.
- Live-tested: OpenRouter gemini-2.5-flash produces evidence-grounded
scenario narratives from real WorldMonitor data.
* feat(forecast): Phase 3 multi-perspective scenarios, projections, data-driven cascades
MiroFish-inspired enhancements:
- Multi-perspective LLM analysis: top-2 predictions get strategic,
regional, and contrarian viewpoints via combined LLM call
- Probability projections: domain-specific decay curves (h24/d7/d30)
anchored to timeHorizon so probability equals projections[timeHorizon]
- Data-driven cascade rules: moved from hardcoded array to JSON config
(scripts/data/cascade-rules.json) with schema validation, named
predicate evaluators, unknown key rejection, and fallback to defaults
- 4 new cascade paths: infrastructure->supply_chain, infrastructure->market
(both requiresSeverity:total), conflict->political, political->market
- Proto: added Perspectives and Projections messages to Forecast
- ForecastPanel: renders projections row and conditional perspectives toggle
- 89 tests (19 new), all passing
- Live-tested: OpenRouter produces perspectives from real data
* feat(forecast): Phase 4 data utilization + entity graph
Fixes data gaps that prevented 4 of 6 detectors from firing:
- Input normalizers: chokepoint v4 shape + GPS hexes-to-zones mapping
- Chokepoint warm-ping (production-only, requires WM_API_BASE_URL)
- Lowered CII conflict threshold from 70 to 60, gated on level=high|critical
4 new standalone detectors:
- UCDP conflict zones (10+ events per country)
- Cyber threat concentration (5+ threats per country)
- GPS jamming in maritime shipping zones (5 regions)
- Prediction markets as signals (60-90% probability markets)
Entity-relationship graph (file-based, 38 nodes):
- Countries, theaters, commodities, chokepoints, alliances
- Alias table resolves both ISO codes and display names
- Graph cascade discovery links predictions across entities
Result: 51 predictions (up from 1-2), spanning conflict, infrastructure,
and supply chain domains. 112 tests, all passing.
* fix(forecast): redis cache format, signal source mapping, type safety
Fresh-eyes audit fixes:
- BUG: redisSet used wrong Upstash API format (POST body with {value,ex}
instead of command array ['SET',key,value,'EX',ttl]). LLM cache writes
were silently failing, causing fresh LLM calls every run.
- BUG: prediction_market signal type missing from SIGNAL_TO_SOURCE,
inflating confidence for market-derived predictions.
- CLEANUP: Remove unnecessary (f as any) casts in ForecastPanel since
generated Forecast type already has projections/perspectives fields.
- CLEANUP: Bump health maxStaleMin from 60 to 90 to avoid false STALE
alerts when LLM calls add latency to seed runs.
* feat(forecast): headline-entity matching with news corroboration signals
Uses entity graph aliases to match headlines to predictions by
country/theater (excludes commodity/infrastructure nodes to prevent
false positives). Predictions with matching headlines get a
news_corroboration signal visible in the panel.
Also fixes buildUserPrompt to merge unique headlines from ALL
predictions in the LLM batch (was only reading preds[0].newsContext).
Live-tested: 13 of 51 predictions now have corroborating headlines
(Iran, Israel, Syria, Ukraine, etc). 116 tests, all passing.
* feat(forecast): add country-codes.json for headline-entity matching
56 countries with ISO codes, full names, and scoring keywords (extracted
from src/config/countries.ts + UCDP-relevant additions). Used by
attachNewsContext for richer headline matching via getSearchTermsForRegion
which combines country-codes + entity graph + keyword aliases.
14/57 predictions now have news corroboration (limited by headline
coverage, not matching quality: only 8 headlines currently available).
* feat(forecast): read 300 headlines from news digest instead of 8
Read news:digest:v1:full:en (300 headlines across 16 categories) instead
of just news:insights:v1 topStories (8 headlines). Fallback to topStories
if digest is unavailable.
Result: news corroboration jumped from 25% to 64% (38/59 predictions).
* fix(forecast): handle parenthetical country names in headline matching
Strip suffixes like '(Zaire)', '(Burma)', '(Soviet Union)' from UCDP
region names before matching against country-codes.json. Also use
includes() for reverse name lookup to catch partial matches.
Corroboration: 64% -> 69% (41/59). Remaining 18 unmatched are countries
with no current English-language news coverage.
* fix(forecast): cache validated LLM output, add digest test, log cache errors
Fresh-eyes audit fixes:
- Combined LLM cache now stores only validated items (was caching raw
unvalidated output, serving potentially invalid scenarios on cache hit)
- redisSet logs warnings on failure (was silently swallowing all errors)
- Added digest-based test for attachNewsContext (primary path was untested)
- Fixed test arity: attachNewsContext(preds, news, digest) with 3 params
* fix(forecast): remove dead confidenceFromSources, reduce warm-ping timeout
- P2: Remove confidenceFromSources (dead code, computeConfidence overwrites
all initial confidence values). Inline the formula in original detectors.
- P3: Reduce warm-ping timeout from 30s to 15s (non-critical step)
- P3: Add trial status comment on forecast panel config
* fix(forecast): resolve ISO codes to country names, fix market detector, safe pre-push
P1 fixes from code review:
- CII ISO codes (IL, IR) now resolved to full country names (Israel, Iran)
via country-codes.json. Prevents substring false positives (IL matching
Chile) in event correlation. Uses word-boundary regex for matching.
- Market detector CII-to-theater mapping now uses entity graph traversal
instead of broken theater-name substring matching. Iran correctly maps
to Middle East theater via graph links.
- Pre-push hook no longer runs destructive git checkout on proto freshness
failure. Reports mismatch and exits without modifying worktree.
* feat(forecast): add structured scenario pipeline and trace export
* fix(forecast): hydrate bootstrap and trim generated drift
* fix(forecast): keep required supply-chain contract updates
* fix(ci): add forecasts to cache-keys registry and regenerate proto
Add forecasts entry to BOOTSTRAP_CACHE_KEYS and BOOTSTRAP_TIERS in
cache-keys.ts to match api/bootstrap.js. Regenerate SupplyChain proto
to fix duplicate TransitDayCount and add riskSummary/riskReportAction.
|
||
|
|
0383253a59 |
feat(supply-chain): chokepoint transit intelligence with 3 data sources (#1560)
* feat(supply-chain): replace S&P Global with 3 free maritime data sources Replace expensive S&P Global Maritime API with IMF PortWatch (vessel transit counts), CorridorRisk (risk intelligence), and AISStream chokepoint crossing counter. All external API calls run on Railway relay, Vercel reads Redis only. - Add 4 new chokepoints (10 total): Cape of Good Hope, Gibraltar, Bosphorus, Dardanelles - Add TransitSummary proto (field 14) with today counts, WoW%, 180d history, risk context - Add D3 multi-line chart (tanker vs cargo) with expandable chokepoint cards - Add crossing detection with enter+dwell+exit semantics, 30min cooldown, 5min min dwell - Add PortWatch seed loop (6h), CorridorRisk seed loop (1h), transit seed loop (10min) - Add canonical chokepoint ID map for cross-source name resolution - 177 tests passing across 6 test files * fix(supply-chain): address P2 review findings - Discard partial PortWatch pagination results on mid-page failure (prevents truncated history with wrong WoW numbers cached for 6h) - Rename "Transit today" to "24h" label (rolling 24h window, not calendar day) - Fix chart label from "30d" to "180d" (matches actual PortWatch query range) - Add 30s initial seed for chokepoint transits on relay cold start (prevents 10min gap of zero transit data) * feat(supply-chain): swap D3 chart for TradingView lightweight-charts Replace hand-rolled D3 SVG transit chart with lightweight-charts v5 canvas rendering for Bloomberg-quality time-series visualization. - Add TransitChart helper class with mount/destroy lifecycle, theme listener, and autoSize support - Use MutationObserver (not rAF) to mount chart after setContent debounce - Clean up chart on tab switch, collapse, and re-render (no orphaned canvases) - Respond to theme-changed events via chart.applyOptions() - D3 stays for other 5 components (ProgressCharts, RenewableEnergy, etc.) * feat(supply-chain): add geo coords and trade routes for 4 new chokepoints Cherry-pick from PR #1511: Cape of Good Hope, Gibraltar, Bosphorus, and Dardanelles map-layer coordinates and trade route definitions. * fix(supply-chain): health.js v2->v4 key + double cache TTLs for missed seeds - health.js chokepoints key was still v2, now v4 (matches handler + bootstrap) - PortWatch TTL: 21600s (6h) -> 43200s (12h), seed interval stays 6h - CorridorRisk TTL: 3600s (1h) -> 7200s (2h), seed interval stays 1h - Ensures one missed seed run doesn't expire the key and cause empty data |
||
|
|
0420a54866 |
fix(acled): add OAuth token manager with automatic refresh (#1437)
* fix(acled): add OAuth token manager with automatic refresh ACLED access tokens expire every 24 hours, but WorldMonitor stores a static ACLED_ACCESS_TOKEN with no refresh logic — causing all ACLED API calls to fail after the first day. This commit adds `acled-auth.ts`, an OAuth token manager that: - Exchanges ACLED_EMAIL + ACLED_PASSWORD for an access token (24h) and refresh token (14d) via the official ACLED OAuth endpoint - Caches tokens in memory and auto-refreshes before expiry - Falls back to static ACLED_ACCESS_TOKEN for backward compatibility - Deduplicates concurrent refresh attempts - Degrades gracefully when no credentials are configured The only change to the existing `acled.ts` is replacing the synchronous `process.env.ACLED_ACCESS_TOKEN` read with an async call to the new `getAcledAccessToken()` helper. Fixes #1283 Relates to #290 * fix: address review feedback on ACLED OAuth PR - Use Redis (Upstash) as L2 token cache to survive Vercel Edge cold starts (in-memory cache retained as fast-path L1) - Add CHROME_UA User-Agent header on OAuth token exchange and refresh - Update seed script to use OAuth flow via getAcledToken() helper instead of raw process.env.ACLED_ACCESS_TOKEN - Add security comment to .env.example about plaintext password trade-offs - Sidecar ACLED_ACCESS_TOKEN case is a validation probe (tests user-provided value, not process.env) — data fetching delegates to handler modules * feat(sidecar): add ACLED_EMAIL/ACLED_PASSWORD to env allowlist and validation - Add ACLED_EMAIL and ACLED_PASSWORD to ALLOWED_ENV_KEYS set - Add ACLED_EMAIL validation case (store-only, verified with password) - Add ACLED_PASSWORD validation case with OAuth token exchange via acleddata.com/api/acled/user/login - On successful login, store obtained OAuth token in ACLED_ACCESS_TOKEN - Follows existing validation patterns (Cloudflare challenge handling, auth failure detection, User-Agent header) * fix: address remaining review feedback (duplicate OAuth, em dashes, emoji) - Extract shared ACLED OAuth helper into scripts/shared/acled-oauth.mjs - Remove ~55 lines of duplicate OAuth logic from seed-unrest-events.mjs, now imports getAcledToken from the shared helper - Replace em dashes with ASCII dashes in acled-auth.ts section comments - Replace em dash with parentheses in sidecar validation message - Remove emoji from .env.example security note Addresses koala73's second review: MEDIUM (duplicate OAuth), LOW (em dashes), LOW (emoji). * fix: align sidecar OAuth endpoint, fix L1/L2 cache, cleanup artifacts - Sidecar: switch from /api/acled/user/login (JSON) to /oauth/token (URL-encoded) to match server/_shared/acled-auth.ts exactly - acled-auth.ts: check L2 Redis when L1 is expired, not only when L1 is null (fixes stale L1 skipping fresher L2 from another isolate) - acled-oauth.mjs: remove stray backslash on line 9 - seed-unrest-events.mjs: remove extra blank line at line 13 --------- Co-authored-by: Elie Habib <elie.habib@gmail.com> Co-authored-by: RepairYourTech <30200484+RepairYourTech@users.noreply.github.com> |
||
|
|
9a6ae69124 |
fix(map): use CORS-enabled R2 URL for PMTiles in Tauri desktop (#1119)
The CF proxy custom domain (maps.worldmonitor.app) doesn't forward R2 CORS headers, so PMTiles fails silently in Tauri where the origin is https://tauri.localhost. Web works because it's same-origin via CF proxy. Detect Tauri via __TAURI__ and swap to VITE_PMTILES_URL_PUBLIC (direct R2 public URL with CORS). Web continues using the CF proxy URL for edge caching benefits. Requires adding https://tauri.localhost to R2 CORS allowed origins. |
||
|
|
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 |
||
|
|
f771114522 |
feat: aviation monitoring layer with flight tracking, airline intel panel, and news feeds (#907)
* feat: Implement comprehensive aviation monitoring service with flight search, status, news, and tracking. * feat: Introduce Airline Intelligence Panel with aviation data tabs, map components, and localization. * feat: Implement DeckGL-based map for advanced visualization, D3/SVG fallback, i18n support, and aircraft tracking. * Update server/worldmonitor/aviation/v1/get-carrier-ops.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update server/worldmonitor/aviation/v1/search-flight-prices.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update server/worldmonitor/aviation/v1/track-aircraft.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update server/worldmonitor/aviation/v1/get-airport-ops-summary.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update proto/worldmonitor/aviation/v1/position_sample.proto Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update server/worldmonitor/aviation/v1/list-airport-flights.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update proto/worldmonitor/aviation/v1/price_quote.proto Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat: Add server-side endpoints for aviation news and aircraft tracking, and introduce a new DeckGLMap component for map visualization. * Update server/worldmonitor/aviation/v1/list-airport-flights.ts The cache key for listAirportFlights excludes limit, but the upstream fetch/simulated generator uses limit to determine how many flights to return. If the first request within TTL uses a small limit, larger subsequent requests will be incorrectly capped until cache expiry. Include limit (or a normalized bucket/max) in cacheKey, or always fetch/cache a fixed max then slice per request. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update server/worldmonitor/aviation/v1/get-flight-status.ts getFlightStatus accepts origin, but cacheKey does not include it. This can serve cached results from an origin-less query to an origin-filtered query (or vice versa). Add origin (normalized) to the cache key or apply filtering after fetch to ensure cache correctness. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat: Implement DeckGL map for advanced visualization and new aviation data services. * fix(aviation): prevent cache poisoning and keyboard shortcut in inputs - get-carrier-ops: move minFlights filter post-cache to avoid cache fragmentation (different callers sharing cached full result) - AviationCommandBar: guard Ctrl+J shortcut so it does not fire when focus is inside an INPUT or TEXTAREA element Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: introduce AviationCommandBar component for parsing user commands, fetching aviation data, and displaying results. * feat: Implement aircraft tracking service with OpenSky and simulated data sources. * feat: introduce DeckGLMap component for WebGL-accelerated map visualizations using deck.gl and maplibre-gl. * fix(aviation): address code review findings for PR #907 Proto: add missing (sebuf.http.query) annotations on all GET request fields across 6 proto files; add currency/market fields to SearchFlightPricesRequest. Server: add parseStringArray to aviation _shared.ts and apply to get-airport-ops-summary, get-carrier-ops, list-aviation-news handlers to prevent crash on comma-separated query params; remove leaked API token from URL params in travelpayouts_data; fix identical simulated flight statuses in list-airport-flights; remove unused endDate var; normalize cache key entity casing in list-aviation-news. Client: refactor AirlineIntelPanel to extend Panel base class and register in DEFAULT_PANELS for full/tech/finance variants; fix AviationCommandBar reference leak with proper destroy() cleanup in panel-layout; rename priceUsd→priceAmount in display type and all usages; change auto-refresh to call refresh() instead of loadOps(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: introduce aviation command bar component with aircraft tracking and flight information services. * feat: Add `AirlineIntelPanel` component for displaying airline operations, flights, carriers, tracking, news, and prices in a tabbed interface. * feat: Add endpoints for listing airport flights and fetching aviation news. * Update proto/worldmonitor/aviation/v1/search_flight_prices.proto Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat: Add server endpoint for listing airport flights and client-side MapPopup types and utilities. * feat: Introduce MapPopup component with support for various data types and responsive positioning for map features. * feat: Add initial English localization file (en.json). * fix(aviation): address PR review findings across aviation stack - Add User-Agent header to Travelpayouts provider (server convention) - Use URLSearchParams for API keys instead of raw URL interpolation - Add input length validation on flightNumber (max 10 chars) - Replace regex XML parsing with fast-xml-parser in aviation news - Fix (f as any)._airport type escape with typed Map<FI, string> - Extract DEFAULT_WATCHED_AIRPORTS constant from hardcoded arrays - Use event delegation for AirlineIntelPanel price search listener - Add bootstrap hydration key for flight delays - Bump OpenSky cache TTL to 120s (anonymous tier rate limit) - Match DeckGLMap aircraft poll interval to server cache (120s) - Fix GeoJSON polygon winding order (shoelace check + auto-reversal) * docs: add aviation env vars to .env.example AVIATIONSTACK_API, ICAO_API_KEY, TRAVELPAYOUTS_API_TOKEN * feat: Add aviation news listing API and introduce shared RSS allowed domains. * fix: add trailing newline to rss-allowed-domains.json, remove unused ringIsClockwise --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Elie Habib <elie.habib@gmail.com> |
||
|
|
b423995363 |
feat(conflict): wire UCDP (#760)
* feat(conflict): wire UCDP API access token across full stack UCDP API now requires an `x-ucdp-access-token` header. Renames the stub `UC_DP_KEY` to `UCDP_ACCESS_TOKEN` (matching ACLED convention) and wires it through Rust keychain, sidecar allowlist + verification, handler fetch headers, feature toggles, and desktop settings UI. - Rename UC_DP_KEY → UCDP_ACCESS_TOKEN in type system and labels - Add ucdpConflicts feature toggle with required secret - Add UCDP_ACCESS_TOKEN to Rust SUPPORTED_SECRET_KEYS (24→25) - Add sidecar ALLOWED_ENV_KEYS entry + validation with dynamic GED version probing - Handler sends x-ucdp-access-token header when token is present - UC_DP_KEY fallback in handler for one-release migration window - Update .env.example, desktop-readiness, and docs * feat(conflict): pre-fetch UCDP events via Railway cron + Redis cache Replace the 228-line edge handler that fetched UCDP GED API on every request with a thin Redis reader. The heavy fetch logic (version discovery, paginated backward fetch, 1-year trailing window filter) now runs as a setInterval loop in the Railway relay (ais-relay.cjs) every 6 hours, writing to Redis key conflict:ucdp-events:v1. Changes: - Add UCDP seed loop to ais-relay.cjs (6h interval, 6 pages, 2K cap) - Rewrite list-ucdp-events.ts as thin Redis reader (35 lines) - Add conflict:ucdp-events:v1 to bootstrap batch keys - Protect key from cache-purge via durable data prefix - Add manual-only seed-ucdp-events workflow + standalone script - Rename panel "UCDP Events" → "Armed Conflict Events" in locale - Add 24h TTL + 25h staleness check as safety nets |
||
|
|
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 |
||
|
|
2399b539b8 |
feat: configurable VITE_WS_API_URL + harden POST→GET shim (#480)
* fix(gateway): harden POST→GET shim with scalar guard and size limit - Only convert string/number/boolean values to query params (skip objects, nested arrays, __proto__ etc.) to prevent prototype pollution vectors - Skip body parsing for Content-Length > 1MB to avoid memory pressure * feat: make API base URL configurable via VITE_WS_API_URL Replace hardcoded api.worldmonitor.app with VITE_WS_API_URL env var. When empty, installWebApiRedirect() is skipped entirely — relative /api/* calls stay on the same domain (local installs). When set, browser fetch is redirected to that URL. Also adds VITE_WS_API_URL and VITE_WS_RELAY_URL hostnames to APP_HOSTS allowlist dynamically. |
||
|
|
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> |
||
|
|
59646df7d8 | Harden Railway relay auth, caching, and proxy routing (#320) | ||
|
|
9b810e1836 | chore: remove unused WORLDPOP_API_KEY from .env.example (#318) | ||
|
|
fcd590a9e6 | Update .env.example (#228) | ||
|
|
1922a781cd | Add PostHog analytics with privacy-first design (#216) | ||
|
|
a388afe400 |
feat: API key gating for desktop cloud fallback + registration (#215)
* feat: API key gating for desktop cloud fallback + registration system Gate desktop cloud fallback behind WORLDMONITOR_API_KEY — desktop users need a valid key for cloud access, otherwise operate local-only (sidecar). Add email registration system via Convex DB for future key distribution. Client-side: installRuntimeFetchPatch() checks key presence before allowing cloud fallback, with secretsReady promise + 2s timeout. Server-side: origin-aware validation in sebuf gateway — desktop origins require key, web origins pass through. - Add WORLDMONITOR_API_KEY to 3-place secret system (Rust, TS, sidecar) - New "World Monitor" settings tab with key input + registration form - New api/_api-key.js server-side validation (origin-aware) - New api/register-interest.js edge function with rate limiting - Convex DB schema + mutation for email registration storage - CORS headers updated for X-WorldMonitor-Key + Authorization - E2E tests for key gate (blocked without key, allowed with key) - Deployment docs (API_KEY_DEPLOYMENT.md) + updated desktop config docs * fix: harden worldmonitor key + registration input handling * fix: show invalid WorldMonitor API key status * fix: simplify key validation, trim registration checks, add env example vars - Inline getValidKeys() in _api-key.js - Remove redundant type checks in register-interest.js - Simplify WorldMonitorTab status to present/missing - Add WORLDMONITOR_VALID_KEYS and CONVEX_URL to .env.example * feat(sidecar): integrate proto gateway bundle into desktop build The sidecar's buildRouteTable() only discovers .js files, so the proto gateway at api/[domain]/v1/[rpc].ts was invisible — all 45 sebuf RPCs returned 404 in the desktop app. Wire the existing build script into Tauri's build commands and add esbuild as an explicit devDependency. |
||
|
|
be485ad1c2 |
chore: switch license to AGPL-3.0, externalize Sentry DSN
- Replace MIT with AGPL-3.0-only to enforce attribution on derivatives - Move hardcoded Sentry DSN to VITE_SENTRY_DSN env var - Add null-coalesce guards for i18n legend keys and SVG viewBox - Extend Sentry ignoreErrors with 7 additional noise patterns |
||
|
|
a31f81a0fe |
fix: filter trending noise, fix sidecar auth & restore tech panels — v2.2.6
- Expand SUPPRESSED_TRENDING_TERMS from 13 to ~170 entries to filter common English words (department, state, news, etc.) from intelligence findings - Move sidecar admin endpoints (debug-toggle, traffic-log, env-update, local-status) before LOCAL_API_TOKEN auth gate — settings window sends bare fetch without token, causing silent 401 failures - Restore Market Radar and Economic Indicators panels to tech variant - Remove stale Documentation section from README - Clean up .env.example cyber threat keys (handled internally) - Bump v2.2.6 |
||
|
|
d93eeaf551 | docs: add cyber threat API keys to .env.example | ||
|
|
e1925e735c |
Consolidate variant naming and fix PWA tile caching
- Rename variant default from 'world' to 'full' across config files - Replace all startups.worldmonitor.app references with tech.worldmonitor.app - Add CARTO basemap tile caching to Workbox runtime config (basemaps.cartocdn.com) |
||
|
|
19754716c6 | feat: add intelligence layers and harden data ingestion | ||
|
|
08f03f8ed3 | Fix tooltip coverage, pulse scheduling, and map interaction defaults | ||
|
|
c876158abe | Fix DeckGL map regressions and preserve perf optimizations | ||
|
|
3985def3c7 |
Add .env.example with all environment variables (#39)
* Add .env.example with all environment variables documented * Fix .env.example: add missing vars, fix wrong names, organize by deployment - Remove ghost var VITE_OPENSKY_RELAY_URL (not used in codebase) - Add missing AISSTREAM_API_KEY (Railway relay) - Add missing WS_RELAY_URL (Vercel server-side relay connection) - Add missing VITE_VARIANT (site variant selector) - Move OPENSKY_CLIENT_ID/SECRET to Railway section (used by relay, not Vercel) - Label sections with deployment target (Vercel vs Railway) - Remove stale docs/DOCUMENTATION.md reference from header --------- Co-authored-by: Elie Habib <elie.habib@gmail.com> |