* fix(widgets): fix CSP violations in pro widget iframe by using sandbox page
srcdoc iframes inherit the parent page's Content-Security-Policy response
headers. The parent's hash-based script-src blocks inline scripts and
cdn.jsdelivr.net (Chart.js), making pro widgets silently broken.
Fix: replace srcdoc with a dedicated /wm-widget-sandbox.html page that
has its own permissive CSP via vercel.json route headers. Widget HTML is
passed via postMessage after the sandbox page loads.
- Add public/wm-widget-sandbox.html: minimal relay page that receives
HTML via postMessage and renders it with document.open/write/close.
Validates message origin against known worldmonitor.app domains.
- vercel.json: add CSP override route for sandbox page (unsafe-inline +
cdn.jsdelivr.net), exclude from SPA rewrite and no-cache rules.
- widget-sanitizer.ts: switch wrapProWidgetHtml to src + data-wm-id,
store widget bodies in module-level Map, auto-mount via MutationObserver.
Fix race condition (always use load event, not readyState check).
Delete store entries after mount to prevent memory leak.
- tests: update 4 tests to reflect new postMessage architecture.
* test(deploy): update deploy-config test for wm-widget-sandbox.html exclusion
* fix(auth): consolidate premium access checks into hasPremiumAccess()
Single source of truth for all premium access paths: desktop API key,
wm-pro-key / wm-widget-key tester keys, and Clerk Pro role.
- Export hasPremiumAccess() from panel-gating.ts (covers all 3 paths)
- Remove duplicate local hasPremiumAccess() from data-loader.ts
- Add market-implications to WEB_PREMIUM_PANELS so it gets auth gating
- Fix lazy panel race: call updatePanelGating() inside lazyPanel() so
daily-market-brief and market-implications aren't stuck loading
- Remove stale getSecretState / isProUser imports from panel-layout.ts
- Update static-analysis tests to assert hasPremiumAccess instead of isProUser
* fix(auth): pass getAuthState() to initial applyProBlockGating call
Prevents a flash of incorrect state for Clerk Pro users on first paint
before the auth subscription fires.
Add isProUser() helper that returns true if either key is present,
replacing scattered isWidgetFeatureEnabled/isProWidgetEnabled checks
across panel-layout. Desktop _lockPanels now also respects web pro keys.
* feat(widgets): add Exa web search + fix widget API endpoints
- Replace Tavily with Exa as primary stock-news search provider
(Exa → Brave → SerpAPI → Google News RSS cascade)
- Add search_web tool to widget agent so AI can fetch live data
about any topic beyond the pre-defined RPC catalog
- Exa primary (type:auto + content snippets), Brave fallback
- Fix all widget tool endpoints: /rpc/... paths were hitting
Vercel catch-all and returning SPA HTML instead of JSON data
- Fix wm-widget-shell min-height causing fixed-size border that
clipped AI widget content
- Add HTML response guard in tool handler
- Update env key: TAVILY_API_KEYS → EXA_API_KEYS throughout
* fix(stock-news): use type 'neural' for Exa search (type 'news' is invalid)
* feat(widgets): add PRO interactive widgets via iframe srcdoc
Introduces a PRO tier for AI-generated widgets that supports full JS
execution (Chart.js, sortable tables, animated counters) via sandboxed
iframes — no Docker, no build step required.
Key design decisions:
- Server returns <body> + inline <script> only; client builds the full
<!DOCTYPE html> skeleton with CSP guaranteed as the first <head> child
so the AI can never inject or bypass the security policy
- sandbox="allow-scripts" only — no allow-same-origin, no allow-forms
- PRO HTML stored in separate wm-pro-html-{id} localStorage key to
isolate 80KB quota pressure from the main widget metadata array
- Raw localStorage.setItem() for PRO writes with HTML-first write order
and metadata rollback on failure (bypasses saveToStorage which swallows
QuotaExceededError)
- Separate PRO_WIDGET_KEY env var + x-pro-key header gate on Railway
- Separate rate limit bucket (20/hr PRO vs 10/hr basic)
- Claude Sonnet 4.6 (8192 tokens, 10 turns, 120s) for PRO vs Haiku for
basic; health endpoint exposes proKeyConfigured for modal preflight
* feat(pro): gate finance panels and widget buttons behind wm-pro-key
The PRO localStorage key now unlocks the three previously desktop-only
finance panels (stock-analysis, stock-backtest, daily-market-brief) on
the web variant, giving PRO users access without needing WORLDMONITOR_API_KEY.
Button visibility is now cleanly separated by key:
- wm-widget-key only → basic "Create with AI" button
- wm-pro-key only → PRO "Create Interactive" button only
- both keys → both buttons
- no key → neither button
Widget boot loader also accepts either key so PRO-only users see their
saved interactive widgets on page load.
* fix(widgets): inject Chart.js CDN into PRO iframe shell so new Chart() is defined