Skip to content

feat: add embed mode support#241

Closed
gilbrotheraway wants to merge 1138 commits into
koala73:mainfrom
gilbrotheraway:gilbrotheraway-patch-1
Closed

feat: add embed mode support#241
gilbrotheraway wants to merge 1138 commits into
koala73:mainfrom
gilbrotheraway:gilbrotheraway-patch-1

Conversation

@gilbrotheraway

@gilbrotheraway gilbrotheraway commented Feb 23, 2026

Copy link
Copy Markdown

Enhance theme script to support embed mode so people can embed specific features(map, etc)

Summary

This PR introduces a lightweight "embed mode" to allow external dashboards (like Glance) to embed the application via an iframe without the surrounding UI clutter.

Changes included:

  • Updated the inline theme script in index.html to detect an ?embed=true URL parameter before the first paint.
  • Added CSS to index.html that hides the header, news/data panels, resize handles, and skeleton loaders when the embed-mode class is active on the body.
  • Ensures the map stretches to fill the entire viewport (100vh) for a seamless embedded widget experience.

Type of change

  • Bug fix
  • New feature
  • New data source / feed
  • New map layer
  • Refactor / code cleanup
  • Documentation
  • CI / Build / Infrastructure

Affected areas

  • Map / Globe
  • News panels / RSS feeds
  • AI Insights / World Brief
  • Market Radar / Crypto
  • Desktop app (Tauri)
  • API endpoints (/api/*)
  • Config / Settings
  • Other: index.html (Boot script and critical CSS)

Checklist

  • Tested on worldmonitor.app variant
  • Tested on tech.worldmonitor.app variant (if applicable)
  • New RSS feed domains added to api/rss-proxy.js allowlist (if adding feeds)
  • No API keys or secrets committed
  • TypeScript compiles without errors (npm run typecheck)

Screenshots

(N/A - UI only changes visually when the ?embed=true parameter is present)

Example Glance iframe widget configuration:

- type: iframe
  source: "https://worldmonitor.app/?embed=true&view=global&layers=military,flights,ais"
  height: 450 # adjust to fit your dashboard

Note: You can easily grab the exact view and layers URL string by arranging the map how you want it in your main app, clicking your "Copy Link" header button, and appending &embed=true to it.

koala73 and others added 30 commits February 17, 2026 15:55
Polymarket and other proxied requests from finance variant were blocked
by CORS because the Railway relay only allowed worldmonitor.app and
tech.worldmonitor.app origins.
Switch from CARTO dark_all raster tiles (which showed continent names
in local languages like 亚洲, AFRIKA, أفريقيا) to CARTO Dark Matter
vector style with English labels. Set renderWorldCopies: false to
prevent horizontal map duplication. Use interleaved deck.gl overlay
so basemap labels render above data layers.

Closes koala73#81

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add bottom viewport clamp so popups near the map's bottom edge slide
upward to remain fully visible instead of being cut off.
setStyle() replaces all map sources/layers. Country boundaries were
only loaded once (guarded by countryGeoJsonLoaded) so they vanished
after theme toggle with no way to re-add them.

- Reset countryGeoJsonLoaded in switchBasemap so loadCountryBoundaries
  can re-run after the new style loads
- Listen for style.load before re-adding country source/layers
- Guard setupCountryHover with countryHoverSetup flag to prevent
  duplicate mousemove/mouseout listeners on re-load
- Apply theme-correct paint values after layer creation
Store the active highlighted ISO code so it can be re-applied after
setStyle() rebuilds map layers with empty default filters.
WebGL renderer check now also detects Intel GPUs to return macos-x64.
When architecture can't be determined, return unknown so users see
both Apple Silicon and Intel download buttons instead of defaulting
to the wrong binary.
Add 'macos' platform type for Macs where WebGL can't determine
Apple Silicon vs Intel. Shows both Mac download buttons without
the irrelevant Windows option.
## Summary
- Detects user's OS via `navigator.userAgent` and shows only the
relevant download button (Windows, macOS Apple Silicon, or macOS Intel)
- Uses WebGL renderer info to distinguish Apple Silicon from Intel Macs
where possible
- Adds a "Show all platforms" toggle so users can still access other
platform downloads
- Falls back to showing all 3 buttons if OS can't be detected

## Test plan
- [x] Open the web app on a Mac — should see only the macOS (Apple
Silicon) button
- [x] Open on Windows — should see only the Windows button
- [x] Click "Show all platforms" — all 3 buttons should appear
- [x] Click "Show less" — should collapse back to the detected platform
- [x] Spoof an unrecognized user agent — all 3 buttons should show (no
toggle)

Replaces koala73#91
Closes koala73#90

🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Guard against Invalid Date in RSS parser — malformed pubDate strings
  from IAEA/CrisisWatch feeds caused RangeError on toISOString()
- Replace 13 dead/blocked RSS feed URLs (403/404/500) with Google News
  site-scoped fallbacks: Politico, RUSI, Kyiv Independent, War Zone,
  MEI, Wilson Center, GMF, CNAS, Lowy, Arms Control, Bulletin, EU ISS
…backs

Sequoia Blog, EU Startups, Tech in Asia, LAVCA, YC Launches,
Dev Events, and SemiAnalysis were returning 403/404/timeout errors.
…eeds

- Middle East panel: BBC Persian (direct RSS), Iran International and
  Fars News (Google News fallbacks — no public RSS endpoints)
- Asia panel: MIIT and MOFCOM China government feeds via RSSHub
Allow users to hide the Intelligence Findings badge for passive viewing
(e.g. TV displays). The badge can be disabled via right-click context
menu or the PANELS settings modal. Preference persists in localStorage.

Closes koala73#89

Co-Authored-By: Claude Opus 4.6 <[email protected]>
## Summary
- Adds ability to hide the Intelligence Findings badge for passive/TV
viewing — stops polling, sounds, and pulse animations when disabled
- Two ways to toggle: right-click context menu on the badge, or the
PANELS settings modal
- Preference persists in `localStorage` across page reloads

Closes koala73#89

## Test plan
- [ ] Load app — badge shows by default (no regression)
- [ ] Right-click badge → "Hide Intelligence Findings" → badge
disappears, no polling/sounds
- [ ] Open PANELS → "Intelligence Findings" toggle shows as disabled →
click to re-enable → badge reappears
- [ ] Refresh page → preference persists
- [ ] Build succeeds with no type errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)
These CSS custom properties were used in 14 places but never defined,
causing transparent backgrounds on playback panel, toggle button,
header flash animation, select options, and offline retry button.
"here" and other basic English words (pronouns, prepositions, adverbs)
were not in SUPPRESSED_TRENDING_TERMS, causing false keyword spike
findings for common words.
"here" and other basic English words (pronouns, prepositions, adverbs)
were not in SUPPRESSED_TRENDING_TERMS, causing false keyword spike
findings for common words.
koala73 and others added 19 commits February 21, 2026 13:55
…koala73#218)

Restore the WORLDMONITOR_API_KEY check that was removed in e882a00,
which left desktop cloud fallback ungated — causing deterministic 401s
from the edge gateway for keyless desktop installs. Also disable cloud
fallback when the runtime-config module fails to import, since the
cloudFallback() path depends on the same module and would throw.

https://claude.ai/code/session_014yJsGsxD1sWt6B6PvQXiaA

Co-authored-by: Claude <[email protected]>
* feat: add cable health scoring via sebuf InfrastructureService

Port submarine cable health monitoring from PR koala73#134 to the sebuf
architecture. Adds GetCableHealth RPC to InfrastructureService that
analyzes NGA maritime warnings to detect cable faults and repair
activity, computing health scores with time-decay.

- Proto: GetCableHealthRequest/Response, CableHealthRecord, evidence
- Handler: NGA warning fetch, cable matching (name + proximity), signal
  processing, health computation with redis caching
- Client: circuit breaker, proto enum → frontend string adapter, 1-min cache
- Frontend: health-based cable coloring (fault=red, degraded=orange),
  evidence display in cable popup, SVG + DeckGL support

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address PR review feedback for cable health scoring

- Fix geographic proximity: use cosine-latitude correction instead of
  raw Euclidean distance on lat/lon degrees, return distanceKm directly
- Fix signal kind: use 'cable_advisory' (not 'operator_fault') for
  non-fault NGA warnings so advisories don't trigger fault status
- Parallelize loadCableActivity + loadCableHealth with Promise.all
- Remove console.log from client-side cable-health service
- Add in-memory fallback cache on server so transient Redis+NGA
  failures serve stale data instead of empty response

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
PR koala73#220 added popups.cable.health.evidence only to en.json.
Add translated values to all 15 other locales.
…back

Prevent timeout spam on Railway when UCDP API is down:
- Negative cache: 5-min backoff after upstream failure
- Version discovery cache: reuse discovered API version for 1 hour
- Parallel version probing via Promise.allSettled
- Stale-on-error: serve fallback data instead of empty array
Plaintext keys (OLLAMA_API_URL, OLLAMA_MODEL, etc.) render pre-filled
with stored values. captureUnsavedInputs() was capturing these unchanged
values into pendingSecrets, triggering unnecessary verification on save.
Now compares against stored value and skips if unchanged.
- Add /ingest/* rewrites in vercel.json → us.i.posthog.com
- Web uses /ingest proxy, desktop uses direct PostHog host
- Enable capture_pageview so every visitor registers
- Fix proto.price falsy bug: price of 0 was treated as null
- Replace global lastSuccessfulResults with per-symbol-set Map to prevent
  stock data leaking into commodity fallback
- Add yahooGate (600ms) to serialize Yahoo requests and avoid IP rate limits
- Add per-symbol-set cache key in server handler to isolate stock/commodity/sector calls
- Clear UCDP circuit breaker cache on empty responses to prevent 10-min lockout
- Add UCDP retry loop (3 attempts, 15s apart) on cold start
- Delay ETF panel initial fetch by 8s to reduce Yahoo contention on startup
When user has a WorldMonitor API key, enable cloud relay through
worldmonitor.app as fallback for Yahoo 429 rate limits.
## Changelog

### Bug Fixes
- market: Fix price falsy bug (price of 0 treated as null)
- market: Per-symbol-set caching prevents stock/commodity data leakage
- market: Yahoo request gate (600ms) reduces IP-level rate limiting
- market: ETF panel 8s delayed fetch reduces Yahoo contention on startup
- ucdp: Clear circuit breaker cache on empty responses
- ucdp: Retry loop (3 attempts, 15s) for cold start resilience
- ucdp: Negative cache, version cache, stale-on-error fallback
- analytics: Proxy PostHog through own domain to bypass ad blockers
- settings: Skip API key re-verification when no keys changed
- csp: Allow PostHog scripts from us-assets.i.posthog.com
- api: Sanitize og-story level input
- api: Restore API-key gate on config import failure

### Features
- Cable health scoring via sebuf InfrastructureService
- PostHog analytics with privacy-first design

### i18n
- Cable health evidence key added to all locales
Invert the path logic from an allowlist of watched directories to an
exclusion list (*.md, .planning, docs, e2e, scripts, .github). This
brings the command from 318 to 242 characters while keeping the same
build-trigger behavior for all source code changes.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ty) (koala73#226)

* docs: add community guidelines — contributing guide, code of conduct, and security policy

Add three community health files for the open-source project:

- CONTRIBUTING.md: comprehensive guide covering architecture overview (sebuf,
  variants, directory structure), development setup with make commands,
  AI-assisted development policy, sebuf RPC workflow, data source and RSS
  feed contribution guides, coding standards, and PR process
- CODE_OF_CONDUCT.md: Contributor Covenant v2.1 adapted for World Monitor
- SECURITY.md: responsible disclosure policy, security considerations for
  edge functions/sebuf handlers, and contributor best practices

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: add missing blank line before list in CONTRIBUTING.md (MD032)

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* docs: expand AI section with LLM label attribution and rationale

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* docs: remove GitHub link from AI section

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* docs: simplify AI section back to concise version with PR labels

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Enhance theme script to support embed mode so people can embed specific features(map, etc)
@vercel

vercel Bot commented Feb 23, 2026

Copy link
Copy Markdown

@gilbrotheraway is attempting to deploy a commit to the Elie Team on Vercel.

A member of the Team first needs to authorize it.

@gilbrotheraway

Copy link
Copy Markdown
Author

@koala73

@gilbrotheraway gilbrotheraway changed the title Refactor theme script for embed mode support feat: add embed mode support Feb 24, 2026
@koala73 koala73 added the wontfix This will not be worked on label Feb 28, 2026
@koala73

koala73 commented Feb 28, 2026

Copy link
Copy Markdown
Owner

Not interested

@koala73 koala73 closed this Feb 28, 2026
koala73 added a commit that referenced this pull request Apr 21, 2026
Fixes 5 findings from multi-agent review, 2 of them P1:

- #241 P1: `.gitignore !api/internal/**` was too broad — it re-included
  `.env`, `.env.local`, and any future secret file dropped into that
  directory. Narrowed to explicit source extensions (`*.ts`, `*.js`,
  `*.mjs`) so parent `.env` / secrets rules stay in effect inside
  api/internal/.

- #242 P1: `Dockerfile.digest-notifications` did not COPY
  `shared/brief-llm-core.js` + `.d.ts`. Cron would have crashed at
  container start with ERR_MODULE_NOT_FOUND. Added alongside
  brief-envelope + brief-filter COPY lines.

- #243 P2: Cron dropped the endpoint's source/producedBy ground-truth
  signal, violating PR #3247's own round-3 memory
  (feedback_gate_on_ground_truth_not_configured_state.md). Added
  structured log at the call site: `[brief-llm] whyMatters source=<src>
  producedBy=<pb> hash=<h>`. Endpoint response now includes `hash` so
  log + shadow-record pairs can be cross-referenced.

- #244 P2: Defense-in-depth prompt-injection hardening. Story fields
  flowed verbatim into both LLM prompts, bypassing the repo's
  sanitizeForPrompt convention. Added sanitizeStoryFields helper and
  applied in both analyst and gemini paths.

- #245 P2: Removed redundant `validate` option from callLlmReasoning.
  With only openrouter configured in prod, a parse-reject walked the
  provider chain, then fell through to the other path (same provider),
  then the cron's own fallback (same model) — 3x billing on one reject.
  Post-call parseWhyMatters check already handles rejection cleanly.

Deferred to P3 follow-ups (todos 246-248): singleflight, v2 sunset,
misc polish (country-normalize LOC, JSDoc pruning, shadow waitUntil,
auto-sync mirror, context-assembly caching).

Tests: 6022 pass. typecheck + typecheck:api clean.
koala73 added a commit that referenced this pull request Apr 21, 2026
…nt (#3248)

* feat(brief): route whyMatters through internal analyst-context endpoint

The brief's "why this is important" callout currently calls Gemini on
only {headline, source, threatLevel, category, country} with no live
state. The LLM can't know whether a ceasefire is on day 2 or day 50,
that IMF flagged >90% gas dependency in UAE/Qatar/Bahrain, or what
today's forecasts look like. Output is generic prose instead of the
situational analysis WMAnalyst produces when given live context.

This PR adds an internal Vercel edge endpoint that reuses a trimmed
variant of the analyst context (country-brief, risk scores, top-3
forecasts, macro signals, market data — no GDELT, no digest-search)
and ships it through a one-sentence LLM call with the existing
WHY_MATTERS_SYSTEM prompt. The endpoint owns its own Upstash cache
(v3 prefix, 6h TTL), supports a shadow mode that runs both paths in
parallel for offline diffing, and is auth'd via RELAY_SHARED_SECRET.

Three-layer graceful degradation (endpoint → legacy Gemini-direct →
stub) keeps the brief shipping on any failure.

Env knobs:
- BRIEF_WHY_MATTERS_PRIMARY=analyst|gemini (default: analyst; typo → gemini)
- BRIEF_WHY_MATTERS_SHADOW=0|1 (default: 1; only '0' disables)
- BRIEF_WHY_MATTERS_SHADOW_SAMPLE_PCT=0..100 (default: 100)
- BRIEF_WHY_MATTERS_ENDPOINT_URL (Railway, optional override)

Cache keys:
- brief:llm:whymatters:v3:{hash16} — envelope {whyMatters, producedBy,
  at}, 6h TTL. Endpoint-owned.
- brief:llm:whymatters:shadow:v1:{hash16} — {analyst, gemini, chosen,
  at}, 7d TTL. Fire-and-forget.
- brief:llm:whymatters:v2:{hash16} — legacy. Cron's fallback path
  still reads/writes during the rollout window; expires in ≤24h.

Tests: 6022 pass (existing 5915 + 12 core + 36 endpoint + misc).
typecheck + typecheck:api + biome on changed files clean.

Plan (Codex-approved after 4 rounds):
docs/plans/2026-04-21-001-feat-brief-why-matters-analyst-endpoint-plan.md

* fix(brief): address /ce:review round 1 findings on PR #3248

Fixes 5 findings from multi-agent review, 2 of them P1:

- #241 P1: `.gitignore !api/internal/**` was too broad — it re-included
  `.env`, `.env.local`, and any future secret file dropped into that
  directory. Narrowed to explicit source extensions (`*.ts`, `*.js`,
  `*.mjs`) so parent `.env` / secrets rules stay in effect inside
  api/internal/.

- #242 P1: `Dockerfile.digest-notifications` did not COPY
  `shared/brief-llm-core.js` + `.d.ts`. Cron would have crashed at
  container start with ERR_MODULE_NOT_FOUND. Added alongside
  brief-envelope + brief-filter COPY lines.

- #243 P2: Cron dropped the endpoint's source/producedBy ground-truth
  signal, violating PR #3247's own round-3 memory
  (feedback_gate_on_ground_truth_not_configured_state.md). Added
  structured log at the call site: `[brief-llm] whyMatters source=<src>
  producedBy=<pb> hash=<h>`. Endpoint response now includes `hash` so
  log + shadow-record pairs can be cross-referenced.

- #244 P2: Defense-in-depth prompt-injection hardening. Story fields
  flowed verbatim into both LLM prompts, bypassing the repo's
  sanitizeForPrompt convention. Added sanitizeStoryFields helper and
  applied in both analyst and gemini paths.

- #245 P2: Removed redundant `validate` option from callLlmReasoning.
  With only openrouter configured in prod, a parse-reject walked the
  provider chain, then fell through to the other path (same provider),
  then the cron's own fallback (same model) — 3x billing on one reject.
  Post-call parseWhyMatters check already handles rejection cleanly.

Deferred to P3 follow-ups (todos 246-248): singleflight, v2 sunset,
misc polish (country-normalize LOC, JSDoc pruning, shadow waitUntil,
auto-sync mirror, context-assembly caching).

Tests: 6022 pass. typecheck + typecheck:api clean.

* fix(brief-why-matters): ctx.waitUntil for shadow write + sanitize legacy fallback

Two P2 findings on PR #3248:

1. Shadow record was fire-and-forget without ctx.waitUntil on an Edge
   function. Vercel can terminate the isolate after response return,
   so the background redisPipeline write completes unreliably — i.e.
   the rollout-validation signal the shadow keys were supposed to
   provide was flaky in production.

   Fix: accept an optional EdgeContext 2nd arg. Build the shadow
   promise up front (so it starts executing immediately) then register
   it with ctx.waitUntil when present. Falls back to plain unawaited
   execution when ctx is absent (local harness / tests).

2. scripts/lib/brief-llm.mjs legacy fallback path called
   buildWhyMattersPrompt(story) on raw fields with no sanitization.
   The analyst endpoint sanitizes before its own prompt build, but
   the fallback is exactly what runs when the endpoint misses /
   errors — so hostile headlines / sources reached the LLM verbatim
   on that path.

   Fix: local sanitizeStoryForPrompt wrapper imports sanitizeForPrompt
   from server/_shared/llm-sanitize.js (existing pattern — see
   scripts/seed-digest-notifications.mjs:41). Wraps story fields
   before buildWhyMattersPrompt. Cache key unchanged (hash is over raw
   story), so cache parity with the analyst endpoint's v3 entries is
   preserved.

Regression guard: new test asserts the fallback prompt strips
"ignore previous instructions", "### Assistant:" line prefixes, and
`<|im_start|>` tokens when injection-crafted fields arrive.

Typecheck + typecheck:api clean. 6023 / 6023 data tests pass.

* fix(digest-cron): COPY server/_shared/llm-sanitize into digest-notifications image

Reviewer P1 on PR #3248: my previous commit (4eee220) added
`import sanitizeForPrompt from server/_shared/llm-sanitize.js` to
scripts/lib/brief-llm.mjs, but Dockerfile.digest-notifications cherry-
picks server/_shared/* files and doesn't copy llm-sanitize. Import is
top-level/static — the container would crash at module load with
ERR_MODULE_NOT_FOUND the moment seed-digest-notifications.mjs pulls in
scripts/lib/brief-llm.mjs. Not just on fallback — every startup.

Fix: add `COPY server/_shared/llm-sanitize.js server/_shared/llm-sanitize.d.ts`
next to the existing brief-render COPY line. Module is pure string
manipulation with zero transitive imports — nothing else needs to land.

Cites feedback_validation_docker_ship_full_scripts_dir.md in the comment
next to the COPY; the cherry-pick convention keeps biting when new
cross-dir imports land in scripts/lib/ or scripts/shared/.

Can't regression-test at build time from this branch without a
docker-build CI job, but the symptom is deterministic — local runs
remain green (they resolve against the live filesystem); only the
container crashes. Post-merge, Railway redeploy of seed-digest-
notifications should show a clean `Starting Container` log line
instead of the MODULE_NOT_FOUND crash my prior commit would have caused.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

wontfix This will not be worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants