Commit Graph

20 Commits

Author SHA1 Message Date
Elie Habib
99b536bfb4 docs(energy): /corrections revision-log page (Week 4 launch requirement) (#3323)
* docs(energy): /corrections revision-log page (Week 4 launch requirement)

All five methodology pages reference /corrections (the auto-revision-log
URL promised in the Global Energy Flow parity plan §20) but the page
didn't exist — clicks 404'd. This lands the page.

Content:
- Explains the revision-log shape: `{date, assetOrEventId, fieldChanged,
  previousValue, newValue, trigger, sourcesUsed, classifierVersion}`.
- Defines the trigger vocabulary (classifier / source / decay / override)
  so readers know what kind of change they're seeing.
- States the v1-launch truth honestly: the log is empty at launch and
  fills as the post-launch classifier pass (in proactive-intelligence.mjs)
  runs on its normal schedule. No fake entries, no placeholder rows.
- Documents the correction-submission path (operators / regulators /
  researchers with public source URLs) and the contract that
  corrections write `override`-trigger entries citing the submitted
  source — not anonymous overrides.
- Cross-links all five methodology pages.
- Explains WHY we publish this: evidence-first classification only
  works if the audit trail is public; otherwise "the classifier said
  so" has no more authority than any other opaque pipeline.

Also fixes a navigation gap: docs/docs.json was missing both
methodology/disruptions (landed in PR #3294 but never registered in
nav) and the new corrections page. Both now appear in the "Intelligence
& Analysis" group alongside the other methodology pages.

No code changes. MDX lint + docs.json JSON validation pass.

* docs(energy): reframe /corrections as planned-surface spec (P1 review fix)

Greptile P1: the prior /corrections page made live-product claims
("writes an append-only entry here", "expect the first entries within
days", "email corrections@worldmonitor.app") that the code doesn't
back. The revision-log writer ships with the post-launch classifier;
the correction-intake pipeline does not yet exist; and the related
detail handlers still return empty `revisions` arrays with code
comments explicitly marking the surface as future work.

Fix: rewrite the page as a planned-surface specification with a
prominent Status callout. Changes:

- Page title: "Revision Log" → "Revision Log (Planned)"
- Prominent <Note> callout at the top states v1 launch truth: log is
  not yet live, RPC `revisions` arrays are empty by design,
  corrections are handled manually today.
- "Current state (v1 launch)" section removed; replaced with two
  explicit sections: "What IS live today" (evidence bundles,
  methodology, versioned classifier output) and "What is NOT live
  today" (log entries, automated correction intake, override-writer).
- "Within days" timeline language removed — no false operational SLA.
- Email submission path removed (no automated intake exists). Points
  readers to GitHub issues for manual review today.
- Preserves the planned data shape, trigger vocabulary, policy
  commitment, and "why we publish this" framing — those are spec, not
  claims.

Also softens /corrections references in the four methodology pages
(pipelines, storage, shortages, disruptions) so none of them claim
the revision log is live. Each now says "planned revision-log shape
and submission policy" and points manual corrections at GitHub issues.

MDX lint 122/122 clean. docs.json JSON validation clean. No code
changes; pure reframing to match reality.

* docs(shortages): fix P1 overclaim + wrong RPC name (round-2 review)

Two findings on the same file:

P1 — `energy_asset_overrides` table documented as existing. It doesn't.
The PR's corrections.mdx explicitly lists the override-writer as NOT
live in v1; this section contradicted that. Rewrote as "Break-glass
overrides (planned)" with a clear Status callout matching the pattern
established in docs/corrections.mdx and the other methodology pages.
Points readers at GitHub issues for manual corrections today.

P2 — Wrong RPC name: `listActiveFuelShortages` doesn't exist. The
shipped RPC (in proto/worldmonitor/supply_chain/v1/
list_fuel_shortages.proto + server/worldmonitor/supply-chain/v1/
list-fuel-shortages.ts) is `ListFuelShortages`. Replaced the name +
reframed the sentence to describe what the actual RPC already exposes
(every FuelShortageEntry includes evidence.evidenceSources[]) rather
than projecting a future surface.

Also swept the other methodology pages for the same class of bug:
- grep for _overrides: only the one line above
- grep for listActive/ getActive RPC names: none found
- verified all RPC mentions in docs/methodology + docs/corrections.mdx
  match names actually in proto (ListPipelines, ListStorageFacilities,
  ListFuelShortages, ListEnergyDisruptions, GetPipelineDetail,
  GetStorageFacilityDetail, GetFuelShortageDetail)

MDX lint clean. No code changes.

* docs(methodology): round-3 sibling sweep for revision-log overclaims

Reviewer (Greptile) caught a third round of the same overclaim pattern
I've been trying to stamp out: docs/methodology/shortages.mdx line 46
said "Stale shortages never persist silently. Every demotion writes to
the public revision log." — contradicting the same PR's /corrections
page which explicitly frames the revision log as not-yet-live. Fixed
that one AND did the mechanical sibling sweep the review pattern
clearly called for.

Changes:

- `docs/methodology/shortages.mdx:46` — rewrote the auto-decay footer
  to future tense: "When the post-launch classifier ships, stale
  shortages will never persist silently — every demotion will write
  an entry to the planned public revision log." Points readers at
  /corrections for the designed shape. Notes that today the demotion
  thresholds ARE the contract; the structured audit trail is what
  lands with the classifier.

- `docs/methodology/chokepoints.mdx:64` — sibling sweep caught the
  same bug class ("Every badge transition writes to the public
  revision log"). Reworded to future tense and pointed manual
  corrections at GitHub issues, matching the pattern already applied
  to pipelines / storage / shortages in prior commits on this PR.

Final audit of remaining revision-log mentions across all 5
methodology pages + corrections.mdx — every one uses hedged tense now
("planned", "will", "when live", "designed", "not yet live", "once
the classifier ships"). The one remaining present-tense "emit" in
shortages.mdx:77 is inside the "(planned)" break-glass section with
its own Status callout, so it's correctly scoped.

Following the plan-doc-as-docs-source-overclaim skill's step-4
(sibling sweep) explicitly this time — which also retroactively
validates the skill extraction: three review rounds was the cost of
not running the sweep on round 1.

MDX lint clean. No code changes.

* docs(corrections): drop hardcoded launch date (Greptile P2)

Greptile inline P2 at docs/corrections.mdx:60: the phrase
"v1 launch (2026-04-23)" pins a specific calendar date that will read
inaccurately to visitors months later once entries start appearing.

Dropped the parenthetical date. "Status — v1 launch:" keeps the
scoping clear without tying it to a specific day. When live entries
start appearing on this page (or when the page is rewritten to show
real rows), a "last updated" marker will replace the status callout
entirely — no migration churn needed.

MDX lint 122/122 clean.
2026-04-23 09:22:48 +04:00
Elie Habib
a17a3383d9 feat(variant): Energy Atlas — Release 1 Day 1 (variant scaffolding) (#3291)
* feat(variant): add energy variant scaffolding for energy.worldmonitor.app

Release 1 Day 1 of the Energy Atlas plan — introduces src/config/variants/energy.ts
modeled on the commodity variant. No new panels or RPCs yet; the variant reuses
existing energy-related panels (energy-complex, oil-inventories, hormuz,
energy-crisis, fuel-prices, renewable-energy) + supply-chain/sanctions context.

Map layers enable pipelines, waterways, AIS, commodityPorts, minerals, climate,
outages, natural, weather. All geopolitical/military/tech/finance/happy variant
layers explicitly disabled per variant isolation conventions.

Next PRs on feat/energy-atlas-release-1 add:
- Pipeline & storage registries (curated critical assets, ~75 gas / ~75 oil / ~125 storage)
- Global fuel-shortage registry with automated evidence-threshold promotion
- Pipeline/storage disruption event log
- Country drill-down Energy section
- Atlas landing composition at variant root

* feat(variant): wire energy variant into runtime + atlas landing composition

Day 2 of the Energy Atlas Release 1 plan. The Day-1 commit added a canonical
variants/energy.ts but discovery during Day 2 showed the app's runtime variant
resolution lives in src/config/panels.ts (ENERGY_PANELS/ENERGY_MAP_LAYERS/etc.),
not in variants/*.ts (which are orphans). This commit does the real wiring.

Changes:
- src/config/panels.ts — ENERGY_PANELS, ENERGY_MAP_LAYERS, ENERGY_MOBILE_MAP_LAYERS;
  registered in ALL_PANELS, VARIANT_DEFAULTS, VARIANT_PANEL_OVERRIDES; wired into
  DEFAULT_MAP_LAYERS + MOBILE_DEFAULT_MAP_LAYERS ternaries. Panels at launch:
  map, live-news, insights, energy-complex, oil-inventories, hormuz, energy-crisis,
  fuel-prices, renewable-energy, commodities, energy (news), macro-signals,
  supply-chain, sanctions-pressure, gulf-economies, gcc-investments, climate,
  monitors, world-clock, latest-brief.
- src/config/variant.ts — recognize 'energy' as allowed SITE_VARIANT; resolve
  energy.worldmonitor.app subdomain to 'energy'; honor localStorage override.
- src/config/variant-meta.ts — SEO entry for energy.worldmonitor.app (title,
  description, keywords targeting 'oil pipeline tracker', 'gas storage map',
  'fuel shortage tracker', 'chokepoint monitor', etc.).
- src/app/panel-layout.ts — desktop variant switcher + mobile menu both list
  energy with  icon and t('header.energy') label.
- src/App.ts + src/app/data-loader.ts — energy variant enables trade-policy and
  supply-chain data loads (chokepoint exposure is a core Atlas surface).
- src/app/data-loader.ts — daily-brief newsCategories override for energy variant
  (energy, energy-markets, oil-gas-news, pipeline-news, lng-news).
- src/locales/en.json — 'header.energy' translation key.
- src/config/variants/energy.ts — add clarifying comment that real wiring lives
  in panels.ts (same orphan pattern as commodity.ts/finance.ts/etc.).

Atlas landing composition: the variant now renders its energy panel set with
energy-specific names (Energy Atlas Map, Energy Headlines, AI Energy Insights)
when SITE_VARIANT === 'energy'. Pipeline and commodity-port map layers enabled
so Week 2's pipeline registry + storage-facility registry drop in with layers
already toggled on.

Typecheck clean; 175 pre-push tests expected to remain green.

Subsequent PRs on feat/energy-atlas-release-1:
- Week 2: pipeline registry + storage facility registry (evidence-based)
- Week 3: fuel-shortage classifier + disruption log + country drill-down
- Week 4: automated revision log, SEO polish, launch

* feat(energy): chokepoint strip at top of atlas (7 chokepoints)

Day 3 of the Energy Atlas Release 1 plan. Adds ChokepointStripPanel — a
compact horizontal strip of chip-style cards, one per chokepoint, showing
name + status color + flow-as-%-of-baseline + active-warnings badge.
Ordered by volume: Hormuz, Malacca, Suez, Bab el-Mandeb, Turkish Straits,
Danish Straits, Panama.

GEF covers 5 chokepoints (Hormuz, Malacca, Suez, Bab el-Mandeb, Panama).
We cover 7 — adds Turkish Straits + Danish Straits. One of the surpass
vectors enumerated in §5.7 of the plan doc.

Data: reuses the existing fetchChokepointStatus() RPC backed by
supply_chain:chokepoints:v4 (Portwatch DWT + AIS calibration). No new
backend work; this is pure UI composition.

Changes:
- src/components/ChokepointStripPanel.ts — new Panel subclass with
  in-line CSS for the chip strip; falls back gracefully when a chokepoint
  is missing from the response or FlowEstimate is absent.
- src/components/index.ts — barrel export.
- src/app/panel-layout.ts — import + createPanel registration near
  existing energy panels.
- src/config/panels.ts — ENERGY_PANELS adds 'chokepoint-strip' at priority 1
  (renders near top of atlas). Also fixes two panel-ID mismatches caught
  while wiring: 'hormuz' → 'hormuz-tracker' and 'renewable-energy' →
  'renewable' (matches HormuzPanel.ts and RenewableEnergyPanel registration).

Typecheck clean. No new tests required — panel renders real data.

* feat(energy): attribution footer utility + methodology page stubs

Days 4 & 5 of the Energy Atlas Release 1 plan.

## Day 4 — Attribution footer (src/utils/attribution-footer.ts)

A reusable string-builder that stamps every energy-atlas number with its
provenance. Design intent per plan §5.6 (quantitative rigour moat):
"every flow number carries {value, baseline, n_vessels, methodology,
confidence}".

Input schema:
- sourceType: operator | regulator | ais | satellite | press | classifier | derived
- method: short free-text ("AIS-DWT calibrated", "GIE AGSI+ daily")
- sampleSize + sampleLabel: observation count and unit
- updatedAt: ISO8601 / Date / number — rendered as "Xm/h/d ago"
- confidence: 0..1 — bucketed to high/medium/low
- classifierVersion: surfaced when evidence-derived badges ship in Week 2+
- creditName / creditUrl: CC-BY / dataset credit (OWID, GEM pattern)

Every field also writes data-attributes (data-attr-source, data-attr-n,
data-attr-confidence, data-attr-classifier) so MCP / scraper / analyst
agents can extract the same provenance the reader sees. Agent-native by
default.

Applied to ChokepointStripPanel — the panel now shows its evidence
footer ("AIS calibration · Portwatch DWT + AIS · N AIS disruption
signals · updated Xh ago · EIA World Oil Transit Chokepoints").
Future pipeline / storage / shortage panels drop the same helper in
and hit the same rigour bar automatically.

7 unit tests (tests/attribution-footer.test.mts, node:test via tsx):
minimal footer, method + sample size + credit, "X ago" formatting,
confidence band mapping, full data-attribute emission, credit omission,
HTML escaping.

## Day 5 — Public methodology page stubs (docs/methodology/)

Four new MDX pages surfaced in docs/docs.json navigation under
"Intelligence & Analysis":

- chokepoints.mdx — 7 chokepoints, Portwatch+AIS calibration method,
  status badge derivation, known limits, revision-log link.
- pipelines.mdx — curated critical-asset scope, GEM CC-BY attribution,
  evidence-schema (NOT conclusion labels), freshness SLA, corrections.
- storage.mdx — curated ~125 facilities scope, "published not
  synthesized" fill % policy, country-aggregate fallback, attribution.
- shortages.mdx — automated tiered evidence threshold, LLM second-pass
  gating, auto-decay cadence, evidence-source transparency, break-glass
  override policy (admin-only, off critical path).

All four explicitly document WorldMonitor's automated-data-quality
posture: no human review queues, quality via classifier rigour +
evidence transparency + auto-decay + public revision log.

Typecheck clean. attribution-footer.test.mts passes all 7 tests.

* fix(variant): close three energy-variant isolation leaks from review

Addresses three High findings from PR review:

1. Map-layer isolation (src/config/map-layer-definitions.ts)
   - Add 'energy' to the MapVariant type union.
   - Add energy entry to VARIANT_LAYER_ORDER with the curated energy
     subset (pipelines, waterways, commodityPorts, commodityHubs, ais,
     tradeRoutes, minerals, sanctions, fires, climate, weather, outages,
     natural, resilienceScore, dayNight).
   Without this, getLayersForVariant() and sanitizeLayersForVariant()
   (called from DeckGLMap and App.ts) fell back to VARIANT_LAYER_ORDER.full,
   letting the full geopolitical palette (military, nuclear, iranAttacks,
   conflicts, hotspots, bases, protests, flights, ucdpEvents,
   displacement, gpsJamming, satellites, ciiChoropleth, cables,
   datacenters, economic, cyberThreats, spaceports, irradiators,
   radiationWatch) into the desktop map tray and saved/URL layer
   sanitization — breaking the PR's stated no-geopolitical-bleed goal
   and violating multi-variant-site-data-isolation.

2. News feeds (src/config/feeds.ts + src/app/data-loader.ts)
   - Add ENERGY_FEEDS with three keys matching ENERGY_PANELS: live-news
     (broad energy headlines from OilPrice, Rigzone, Reuters/Bloomberg/FT
     energy), energy (OPEC + crude + NatGas/LNG + pipelines/chokepoints
     + crisis/shortages + refineries), supply-chain (tanker/shipping,
     chokepoints, energy sanctions, ports/terminals).
   - Add SITE_VARIANT === 'energy' branch to the FEEDS variant selector.
   - Correct newsCategories override in data-loader.ts — my earlier
     speculative values ['energy','energy-markets','oil-gas-news',
     'pipeline-news','lng-news'] included keys that did not exist in
     any feed map. Replaced with real ENERGY_FEEDS keys ['live-news',
     'energy', 'supply-chain'].
   Without this, FEEDS resolved to FULL_FEEDS for the energy variant —
   live-news + daily-brief both ingested the world/geopolitical feed set.

3. Insights / AI brief framing (src/components/InsightsPanel.ts)
   - Add SITE_VARIANT === 'energy' branch to geoContext: dedicated energy
     prompt focused on physical supply (pipelines, chokepoints, storage,
     days-of-cover, refineries, LNG, sanctions, shortages) with
     evidence-grounded attribution, no bare conclusions.
   - Add ' ENERGY BRIEF' heading branch in renderWorldBrief().
   Without this, the renamed 'AI Energy Insights' panel fell through
   to the empty default prompt and rendered 'WORLD BRIEF'.

Typecheck clean. attribution-footer tests still pass (no coupling changed).

* fix(variant): close energy-variant leak in SVG/mobile fallback map

Fifth High finding from PR review: src/components/Map.ts createLayerToggles()
(line 381-409) has no 'energy' branch in its variant ternary, so energy-variant
users whose MapContainer routes to the SVG/mobile fallback (no WebGL, mobile
with deviceMemory < 3, or DeckGL init throws) see the full geopolitical toggle
set — iranAttacks, conflicts, hotspots, bases, nuclear, irradiators, military,
protests, flights, gpsJamming, ciiChoropleth, cables, datacenters.

Clicking any toggle flips the layer via toggleLayer() which is variant-blind
(Map.ts:3383) — so users could enable military / nuclear layers on the energy
variant despite the rest of the isolation work in panels.ts,
map-layer-definitions.ts, feeds.ts, and InsightsPanel.ts.

Fix: add energyLayers array with the SVG-capable subset of ENERGY_MAP_LAYERS —
pipelines, waterways, ais, commodityHubs, minerals, sanctions, outages, natural,
weather, fires, economic. Intentionally omitted: commodityPorts, climate,
tradeRoutes, resilienceScore, dayNight — none of these have render handlers in
Map.ts's SVG path, so including them would create toggles that do nothing.
Extended the ternary with 'energy' → energyLayers between 'happy' and the
'full' fallback.

Note (preexisting, NOT fixed here): the same ternary has no 'commodity' branch
either, so commodity.worldmonitor.app also gets the full geopolitical toggle
set on the SVG fallback. Out of scope for this PR; flagged for a separate fix.

Defence-in-depth: sanitizeLayersForVariant() (now fixed in
map-layer-definitions.ts) strips saved-URL layers to the energy subset before
the SVG map sees them, so even if a user arrives with ?layers=military in the
URL, it's gone by the time initialState reaches MapComponent. The toggle-list
fix closes the UI-path leak; the sanitize fix closes the URL-path leak.

Typecheck clean.
2026-04-22 21:37:25 +04:00
Elie Habib
4c9888ac79 docs(mintlify): panel reference pages (PR 2) (#3213)
* docs(mintlify): add user-facing panel reference pages (PR 2)

Six new end-user pages under docs/panels/ for the shipped panels that
had no user-facing documentation in the published docs, per the plan
docs/plans/2026-04-19-001-feat-docs-user-facing-ia-refresh-plan.md.
All claims are grounded in the live component source + SEED_META +
handler dirs — no invented fields, counts, or refresh windows.

- panels/latest-brief.mdx — daily AI brief panel (ready/composing/
  locked states). Hard-gated PRO (`premium: 'locked'`).
- panels/forecast.mdx — AI Forecasts panel (internal id `forecast`,
  label "AI Forecasts"). Domain + macro-region filter pills; 10%
  probability floor. Free on web, locked on desktop.
- panels/consumer-prices.mdx — 5-tab retail-price surface (Overview
  / Categories / Movers / Spread / Health) with market, basket, and
  7/30/90-day controls. Free.
- panels/disease-outbreaks.mdx — WHO / ProMED / national health
  ministries outbreak alerts with alert/warning/watch pills. Free.
- panels/radiation-watch.mdx — EPA RadNet + Safecast observations
  with anomaly scoring and source-confidence synthesis. Free.
- panels/thermal-escalation.mdx — FIRMS/VIIRS thermal clusters with
  persistence and conflict-adjacency flags. Free.

Also:
- docs/docs.json — new Panels nav group (Latest Brief, AI Forecasts,
  Consumer Prices, Disease Outbreaks, Radiation Watch, Thermal
  Escalation).
- docs/features.mdx — cross-link every panel name in the Cmd+K
  inventory to its new page (and link Country Instability + Country
  Resilience from the same list).
- docs/methodology/country-resilience-index.mdx — short "In the
  dashboard" bridge section naming the three CRI surfaces
  (Resilience widget, Country Deep-Dive, map choropleth) so the
  methodology page doubles as the user-facing panel reference for
  CRI. No separate docs/panels/country-resilience.mdx — keeps the
  methodology page as the single source of truth.

* docs(panels): fix Latest Brief polling description

Reviewer catch: the panel does schedule a 60-second re-poll while
in the composing state. `COMPOSING_POLL_MS = 60_000` at
src/components/LatestBriefPanel.ts:78, and `scheduleComposingPoll()`
is called from `renderComposing()` at :366. The poll auto-promotes
the panel to ready without a manual refresh and is cleared when
the panel leaves composing. My earlier 'no polling timer' line was
right for the ready state but wrong as a blanket claim.

* docs(panels): fix variant-availability claims across all 6 panel pages

Reviewer catch on consumer-prices surfaced the same class of error
on 4 other panel pages: I described variant availability with loose
phrasing ('most variants', 'where X context is relevant', 'tech/
finance/happy opt-in') that didn't match the actual per-variant
panel registries in src/config/panels.ts.

Verified matrix against each *_PANELS block directly:

  Panel              | FULL | TECH | FINANCE | HAPPY | COMMODITY
  consumer-prices    | opt  |  -   |  def    |   -   |  def
  latest-brief       | def  | def  |  def    |   -   |  def   (all PRO-locked)
  disease-outbreaks  | def  |  -   |   -     |   -   |   -
  radiation-watch    | def  |  -   |   -     |   -   |   -
  thermal-escalation | def  |  -   |   -     |   -   |   -
  forecast           | def  |  -   |   -     |   -   |   -   (PRO-locked on desktop)

All 6 pages now name the exact variant blocks in src/config/panels.ts
that register them, so the claim is re-verifiable by grep rather than
drifting with future panel-registry changes.

* docs(panels): fix 5 reviewer findings — no invented controls/sources/keys

All fixes cross-checked against source.

- consumer-prices: no basket selector UI exists. The panel has a
  market bar, a range bar, and tab/category affordances; basket is
  derived from market selection (essentials-<code>, or DEFAULT_BASKET
  for the 'all' aggregate view). Per
  src/components/ConsumerPricesPanel.ts:120-123 and :216-229.
- disease-outbreaks: 'Row click opens advisory' was wrong. The only
  interactive elements in-row are the source-name <a> link
  (sanitised URL, target=_blank); clicking the row itself is a no-op
  (the only content-level listener is for [data-filter] pills and
  the search input). Per DiseaseOutbreaksPanel.ts:35-49,115-117.
- disease-outbreaks: upstream list was wrong. Actual seeder uses
  WHO DON (JSON API), CDC HAN (RSS), Outbreak News Today
  (aggregator), and ThinkGlobalHealth disease tracker
  (ProMED-sourced, 90d lookback). Noted the in-panel tooltip's
  shorter 'WHO, ProMED, health ministries' summary and gave the full
  upstream list with the 72h Redis TTL. Per seed-disease-outbreaks
  .mjs:31-38.
- radiation-watch: summary bar renders 6 cards, not 7 — Anomalies,
  Elevated, Confirmed, Low Confidence, Conflicts, Spikes. The
  CPM-derived indicator is a per-row badge (radiation-flag-converted
  at :67), not a summary card. Moved the CPM reference to the
  per-row badges list. Per RadiationWatchPanel.ts:85-112.
- latest-brief: Redis key shape corrected. The composer writes the
  envelope to brief:{userId}:{issueSlot} (where issueSlot comes from
  issueSlotInTz, not a plain date) and atomically writes a latest
  pointer at brief:latest:{userId} → {issueSlot}. Readers resolve
  via the pointer. 7-day TTL on both. Per
  seed-digest-notifications.mjs:1103-1115 and
  api/latest-brief.ts:80-89.

* docs(panels): Tier 1 — PRO/LLM panel reference pages (9)

Adds user-facing panel pages for the 9 PRO/LLM-backed surfaces flagged
in the extended audit. All claims grounded in component source +
src/config/panels.ts entries (with line cites).

- panels/chat-analyst.mdx — WM Analyst (conversational AI, 5 quick
  actions, 4 domain scopes, POSTs /api/chat-analyst via premiumFetch).
- panels/market-implications.mdx — AI Market Implications trade signals
  (LONG/SHORT/HEDGE × HIGH/MEDIUM/LOW, transmission paths, 120min
  maxStaleMin, degrade-to-warn). Carries the repo's disclaimer verbatim.
- panels/deduction.mdx — Deduct Situation (opt-in PRO; 5s cooldown;
  composes buildNewsContext + active framework).
- panels/daily-market-brief.mdx — Daily Market Brief (stanced items,
  framework selector, live vs cached source badge).
- panels/regional-intelligence.mdx — Regional Intelligence Board
  (7 BOARD_REGIONS, 6 structured blocks + narrative sections,
  request-sequence arbitrator, opt-in PRO).
- panels/strategic-posture.mdx — AI Strategic Posture (cached posture
  + live military vessels → recalcPostureWithVessels; free on web,
  enhanced on desktop).
- panels/stock-analysis.mdx — Premium Stock Analysis (per-ticker
  deep dive: signal, targets, consensus, upgrades, insiders, sparkline).
- panels/stock-backtest.mdx — Premium Backtesting (longitudinal view;
  live vs cached data badge).
- panels/wsb-ticker-scanner.mdx — WSB Ticker Scanner (retail sentiment
  + velocity score with 4-tier color bucketing).

All 9 are PRO (8 via apiKeyPanels allowlist at src/config/panels.ts:973,
strategic-posture is free-on-web/enhanced-on-desktop). Variant matrices
name the exact *_PANELS block registering each panel.

* docs(panels): Tier 2 — flagship free data panels (7)

Adds reference pages for 7 flagship free panels. Every claim grounded
in the panel component + src/config/panels.ts per-variant registration.

- panels/airline-intel.mdx — 6-tab aviation surface (ops/flights/
  airlines/tracking/news/prices), 8 aviation RPCs, user watchlist.
- panels/tech-readiness.mdx — ranked country tech-readiness index with
  6-hour in-panel refresh interval.
- panels/trade-policy.mdx — 6-tab trade-policy surface (restrictions/
  tariffs/flows/barriers/revenue/comtrade).
- panels/supply-chain.mdx — composite stress + carriers + minerals +
  Scenario Engine trigger surface (free panel, PRO scenario activation).
- panels/sanctions-pressure.mdx — OFAC SDN + Consolidated list
  pressure rollup with new/vessels/aircraft summary cards and top-8
  country rows.
- panels/hormuz-tracker.mdx — Hormuz chokepoint drill-down; status
  indicator + per-series bar charts; references Scenario Engine's
  hormuz-tanker-blockade template.
- panels/energy-crisis.mdx — IEA 2026 Energy Crisis Policy Response
  Tracker; category/sector/status filters.

All 7 are free. Variant matrices name exact *_PANELS blocks
registering each panel.

* docs(panels): Tier 3 — compact panels (5)

Adds reference pages for 5 compact user-facing panels.

- panels/world-clock.mdx — 22 global market-centre clocks with
  exchange labels + open/closed indicators (client-side only).
- panels/monitors.mdx — personal keyword alerts, localStorage-persisted;
  links to Features → Custom Monitors for longer explanation.
- panels/oref-sirens.mdx — OREF civil-defence siren feed; active +
  24h wave history; free on web, PRO-locked on desktop (_desktop &&
  premium: 'locked' pattern).
- panels/telegram-intel.mdx — topic-tabbed Telegram channel mirror
  via relay; free on web, PRO-locked on desktop.
- panels/fsi.mdx — US KCFSI + EU FSI stress composites with
  four-level colour buckets (Low/Moderate/Elevated/High).

All 5 grounded in component source + variant registrations.
oref-sirens and telegram-intel correctly describe the _desktop &&
locking pattern rather than the misleading 'PRO' shorthand used
earlier for other desktop-locked panels.

* docs(panels): Tier 4 + 5 catalogue pages, nav re-grouping, features cross-links

Closes out the comprehensive panel-reference expansion. Two catalogue
pages cover the remaining ~60 panels collectively so they're all
searchable and findable without dedicated pages per feed/tile.

- panels/news-feeds.mdx — catalogue covering all content-stream panels:
  regional news (africa/asia/europe/latam/us/middleeast/politics),
  topical news (climate/crypto/economic/markets/mining/commodity/
  commodities), tech/startup streams (startups/unicorns/accelerators/
  fintech/ipo/layoffs/producthunt/regionalStartups/thinktanks/vcblogs/
  defense-patents/ai-regulation/tech-hubs/ai/cloud/hardware/dev/
  security/github), finance streams (bonds/centralbanks/derivatives/
  forex/institutional/policy/fin-regulation/commodity-regulation/
  analysis), happy variant streams (species/breakthroughs/progress/
  spotlight/giving/digest/events/funding/counters/gov/renewable).
- panels/indicators-and-signals.mdx — catalogue covering compact
  market-indicator tiles, correlation panels, and misc signal surfaces.
  Grouped by function: sentiment, macro, calendars, market-structure,
  commodity, crypto, regional economy, correlation panels, misc signals.

docs/docs.json — split the Panels group into three for navigability:
  - Panels — AI & PRO (11 pages)
  - Panels — Data & Tracking (16 pages)
  - Panels — Catalogues (2 pages)

docs/features.mdx — Cmd+K inventory rewritten as per-family sub-lists
  with links to every panel page (or catalogue page for the ones
  that live in a catalogue). Replaces the prior run-on paragraph.

Every catalogue panel is also registered in at least one *_PANELS
block in src/config/panels.ts — the catalogue pages note this and
point readers to the config file for variant-availability details.

* docs(panels): fix airline-intel + world-clock source-of-truth errors

- airline-intel: refresh behavior section was wrong on two counts.
  (1) The panel DOES have a polling timer: a 5-minute setInterval
  in the constructor calling refresh() (which reloads ops + active
  tab). (2) The 'prices' tab does NOT re-fetch on tab switch —
  it's explicitly excluded from both tab-switch and auto-refresh
  paths, loading only on explicit search-button click. Three
  distinct refresh paths now documented with source line hints.
  Per src/components/AirlineIntelPanel.ts ~:173 (setInterval),
  :287 (prices tab-switch guard), :291 (refresh() prices skip).
- world-clock: the WORLD_CITIES list has 30 entries, not '~22'.
  Replaced the approximate count with the exact number and a
  :14-43 line-range cite so it's re-verifiable.
2026-04-20 00:53:17 +04:00
Elie Habib
d1a4cf7780 docs(mintlify): add Route Explorer + Scenario Engine workflow pages (#3211)
* docs(mintlify): add Route Explorer + Scenario Engine workflow pages

Checkpoint for review on the IA refresh (per plan
docs/plans/2026-04-19-001-feat-docs-user-facing-ia-refresh-plan.md).

- docs/docs.json: link Country Resilience Index methodology under
  Intelligence & Analysis so the flagship 222-country feature is
  reachable from the main nav (previously orphaned). Add a new
  Workflows group containing route-explorer and scenario-engine.
- docs/route-explorer.mdx: standalone workflow page. Who it is for,
  Cmd+K entry, four tabs (Current / Alternatives / Land / Impact),
  inputs, keyboard bindings, map-state integration, PRO gating
  with free-tier blur + public-route highlight, data sources.
- docs/scenario-engine.mdx: standalone workflow page. Template
  categories (conflict / weather / sanctions / tariff_shock /
  infrastructure / pandemic), how a scenario activates on the map,
  PRO gating, pointers to the async job API.

Deferred to follow-up commits in the same PR:
  - documentation.mdx landing rewrite
  - features.mdx refresh
  - maritime-intelligence.mdx link-out to Route Explorer
  - Panels nav group (waits for PR 2 content)

All content grounded in live source files cited inline.

* docs(mintlify): fix Route Explorer + Scenario Engine review findings

Reviewer caught 4 cases where I described behavior I hadn't read
carefully. All fixes cross-checked against source.

- route-explorer (free-tier): the workflow does NOT blur a numeric
  payload behind a public demo route. On free tier, fetchLane()
  short-circuits to renderFreeGate() which blurs the left rail,
  replaces the tab area with an Upgrade-to-PRO card, and applies a
  generic public-route highlight on the map. No lane data is
  rendered in any tab. See src/components/RouteExplorer/
  RouteExplorer.ts:212 + :342.
- route-explorer (keyboard): Tab / Shift+Tab moves focus between the
  panel and the map. Direct field jumps are F (From), T (To), P
  (Product/HS2), not Tab-cycling. Also added the full KeyboardHelp
  binding list (S swap, ↑/↓ list nav, Enter commit, Cmd+, copy URL,
  Esc close, ? help, 1-4 tabs). See src/components/RouteExplorer/
  KeyboardHelp.ts:9 and RouteExplorer.ts:623.
- scenario-engine: the SCENARIO_TEMPLATES array only ships templates
  of 4 types today (conflict, weather, sanctions, tariff_shock).
  The ScenarioType union includes infrastructure and pandemic but
  no templates of those types ship. Dropped them from the shipped
  table and noted the type union leaves room for future additions.
- scenario-engine + api-scenarios: the worker writes
  status: 'done' (not 'completed') on success, 'failed' on error;
  pending is synthesised by the status endpoint when no worker
  record exists. Fixed both the new workflow page and the merged
  api-scenarios.mdx completed-response example + polling language.
  See scripts/scenario-worker.mjs:421 and
  src/components/SupplyChainPanel.ts:870.

* docs(mintlify): fix third-round review findings (real IDs + 4-state lifecycle)

- api-scenarios (template example): replaced invented
  hormuz-closure-30d / ["hormuz"] with the actually-shipped
  hormuz-tanker-blockade / ["hormuz_strait"] from scenario-
  templates.ts:80. Listed the other 5 shipped template IDs so
  scripted users aren't dependent on a single example.
- api-scenarios (status lifecycle): worker writes FOUR states,
  not three. Added the intermediate "processing" state with
  startedAt, written by the worker at job pickup (scenario-
  worker.mjs:411). Lifecycle now: pending → processing →
  done|failed. Both pending and processing are non-terminal.
- scenario-engine (scripted use blurb): mirror the 4-state
  language and link into the lifecycle table.
- scenario-engine (UI dismiss): replaced "Click Deactivate"
  with the actual × dismiss control on the scenario banner
  (aria-label: "Dismiss scenario") per
  src/components/SupplyChainPanel.ts:790. Also described the
  banner contents (name, chokepoints, countries, tagline).
- api-shipping-v2: while fixing chokepoint IDs, also corrected
  "hormuz" → "hormuz_strait" and "bab-el-mandeb" → "bab_el_mandeb"
  across all four occurrences in the shipping v2 page (from
  PR #3209). Real IDs come from server/_shared/chokepoint-
  registry.ts (snake_case, not kebab-case, not bare "hormuz").

* docs(mintlify): fix fourth-round findings (banner DOM, webhook TTL refresh)

- scenario-engine: accurate description of the rendered scenario
  banner. Always-present elements are the ⚠ icon, scenario name,
  top-5 impacted countries with impact %, and dismiss ×. Params
  chip (e.g. '14d · +110% cost') and 'Simulating …' tagline are
  conditional on the worker result carrying template parameters
  (durationDays, disruptionPct, costShockMultiplier). The banner
  never lists affected chokepoints by name — the map and the
  chokepoint cards surface those. Per renderScenarioBanner at
  src/components/SupplyChainPanel.ts:750.
- api-shipping-v2 (webhook TTL): register extends both the record
  and the owner-index set's 30-day TTL via atomic pipeline
  (SET + SADD + EXPIRE). rotate-secret and reactivate only
  extend the record's TTL — neither touches the owner-index set,
  so the owner index can expire independently if a caller only
  rotates/reactivates within a 30-day window. Re-register to keep
  both alive. Per api/v2/shipping/webhooks.ts:230 (register
  pipeline) and :325 (rotate setCachedJson on record only).

* docs(mintlify): fix PRO auth contract (trusted origin ≠ PRO)

- api-scenarios: 'X-WorldMonitor-Key (or trusted browser origin)
  + PRO' was wrong — isCallerPremium() explicitly skips
  trusted-origin short-circuits (keyCheck.required === false) and
  only counts (a) an env-valid or user-owned wm_-prefixed API key
  with apiAccess entitlement, or (b) a Clerk bearer with role=pro
  or Dodo tier ≥ 1. Browser calls work because premiumFetch()
  injects one of those credentials per request, not because Origin
  alone authenticates. Per server/_shared/premium-check.ts:34 and
  src/services/premium-fetch.ts:66.
- usage-auth: strengthened the 'Entitlement / tier gating' section
  to state outright that authentication and PRO entitlement are
  orthogonal, and that trusted Origin is NOT accepted as PRO even
  though it is accepted for public endpoints. Listed the two real
  credential forms that pass the gate.

* docs(mintlify): fix stale line cite (MapContainer.activateScenario at :1010)

Greptile review P2: prose cited MapContainer.ts:1004 but activateScenario
is declared at :1010. Line 1004 landed inside the JSDoc block.

* docs(mintlify): finish PR 1 — landing rewrite, features refresh, maritime link-out

Completes the PR 1 items from docs/plans/2026-04-19-001-feat-docs-user-
facing-ia-refresh-plan.md that were deferred after the checkpoint on
Route Explorer + Scenario Engine + CRI nav. No new pages — only edits
to existing pages to point at and cohere with the new workflow pages.

- documentation.mdx: landing rewrite. Dropped brittle counts (344
  news sources, 49 layers, 24 CII countries, 31+ sources, 24 typed
  services) in favor of durable product framing. Surfaced the
  shipped differentiators that were invisible on the landing
  previously: Country Resilience Index (222 countries, linked to
  its methodology page), AI daily brief, Route Explorer,
  Scenario Engine, MCP server. Kept CII and CRI as two distinct
  country-risk surfaces — do not conflate.
- features.mdx: replaced the 'all 55 panels' Cmd+K claim and the
  stale inventory list with family-grouped descriptions that
  include the panels this audit surfaced as missing (disease-
  outbreaks, radiation-watch, thermal-escalation, consumer-prices,
  latest-brief, forecast, country-resilience). Added a Workflows
  section linking to Route Explorer and Scenario Engine, and a
  Country-level risk section linking CII + CRI. Untouched
  sections (map, marker clustering, data layers, export, monitors,
  activity tracking) left as-is.
- maritime-intelligence.mdx: collapsed the embedded Route Explorer
  subsection to a one-paragraph pointer at /route-explorer so the
  standalone page is the canonical home.

Panels nav group remains intentionally unadded; it waits on PR 2
content to avoid rendering an empty group in Mintlify.
2026-04-19 18:39:36 +04:00
Elie Habib
e4c95ad9be docs(mintlify): cover MCP, OAuth, non-RPC endpoints, and usage (#3209)
* docs(mintlify): cover MCP, OAuth, non-RPC endpoints, and usage

Audit against api/ + proto/ revealed 9 OpenAPI specs missing from nav,
the scenario/v1 service undocumented, and MCP (32 tools + OAuth 2.1 flow)
with no user-facing docs. The stale Docs_To_Review/API_REFERENCE.md still
pointed at pre-migration endpoints that no longer exist.

- Wire 9 orphaned specs into docs.json: ConsumerPrices, Forecast, Health,
  Imagery, Radiation, Resilience, Sanctions, Thermal, Webcam
- Hand-write ScenarioService.openapi.yaml (3 RPCs) until it's proto-backed
  (tracked in issue #3207)
- New MCP page with tool catalog + client setup (Claude Desktop/web, Cursor)
- New MDX for OAuth, Platform, Brief, Commerce, Notifications, Shipping v2,
  Proxies
- New Usage group: quickstart, auth matrix, rate limits, errors
- Remove docs/Docs_To_Review/API_REFERENCE.md and EXTERNAL_APIS.md
  (referenced dead endpoints); add README flagging dir as archival

* docs(mintlify): move scenario docs out of generated docs/api/ tree

The pre-push hook enforces that docs/api/ is proto-generated only.
Replace the hand-written ScenarioService.openapi.yaml with a plain
MDX page (docs/api-scenarios.mdx) until the proto migration lands
(tracked in issue #3207).

* docs(mintlify): fix factual errors flagged in PR review

Reviewer caught 5 endpoints where I speculated on shape/method/limits
instead of reading the code. All fixes cross-checked against the
source:

- api-shipping-v2: route-intelligence is GET with query params
  (fromIso2, toIso2, cargoType, hs2), not POST with a JSON body.
  Response shape is {primaryRouteId, chokepointExposures[],
  bypassOptions[], warRiskTier, disruptionScore, ...}.
- api-commerce: /api/product-catalog returns {tiers, fetchedAt,
  cachedUntil, priceSource} with tier groups free|pro|api_starter|
  enterprise, not the invented {currency, plans}. Document the
  DELETE purge path too.
- api-notifications: Slack/Discord /oauth/start are POST + Clerk
  JWT + PRO (returning {oauthUrl}), not GET redirects. Callbacks
  remain GET.
- api-platform: /api/version returns the latest GitHub Release
  ({version, tag, url, prerelease}), not deployed commit/build
  metadata.
- api-oauth + mcp: /api/oauth/register limit is 5/60s/IP (match
  code), not 10/hour.

Also caught while double-checking: /api/register-interest and
/api/contact are 5/60min and 3/60min respectively (1-hour window,
not 1-minute). Both require Turnstile. Removed the fabricated
limits for share-url, notification-channels, create-checkout
(they fall back to the default per-IP limit).

* docs(mintlify): second-round fixes — verify every claim against source

Reviewer caught 7 more cases where I described API behavior I hadn't
read. Each fix below cross-checked against the handler.

- api-commerce (product-catalog): tiers are flat objects with
  monthlyPrice/annualPrice/monthlyProductId/annualProductId on paid
  tiers, price+period for free, price:null for enterprise. There is
  no nested plans[] array.
- api-commerce (referral/me): returns {code, shareUrl}, not counts.
  Code is a deterministic 8-char HMAC of the Clerk userId; binding
  into Convex is fire-and-forget via ctx.waitUntil.
- api-notifications (notification-channels): actual action set is
  create-pairing-token, set-channel, set-web-push, delete-channel,
  set-alert-rules, set-quiet-hours, set-digest-settings. Replaced
  the made-up list.
- api-shipping-v2 (webhooks): alertThreshold is numeric 0-100
  (default 50), not a severity string. Subscriber IDs are wh_+24hex;
  secret is raw 64-char hex (no whsec_ prefix). POST registration
  returns 201. Added the management routes: GET /{id},
  POST /{id}/rotate-secret, POST /{id}/reactivate.
- api-platform (cache-purge): auth is Authorization: Bearer
  RELAY_SHARED_SECRET, not an admin-key header. Body takes keys[]
  and/or patterns[] (not {key} or {tag}), with explicit per-request
  caps and prefix-blocklist behavior.
- api-platform (download): platform+variant query params, not
  file=<id>. Response is a 302 to a GitHub release asset; documented
  the full platform/variant tables.
- mcp: server also accepts direct X-WorldMonitor-Key in addition to
  OAuth bearer. Fixed the curl example which was incorrectly sending
  a wm_live_ API key as a bearer token.
- api-notifications (youtube/live): handler reads channel or videoId,
  not channelId.
- usage-auth: corrected the auth-matrix row for /api/mcp to reflect
  that OAuth is one of two accepted modes.

* docs(mintlify): fix Greptile review findings

- mcp.mdx: 'Five' slow tools → 'Six' (list contains 6 tools)
- api-scenarios.mdx: replace invalid JSON numeric separator
  (8_400_000_000) with plain integer (8400000000)

Greptile's third finding — /api/oauth/register rate-limit contradiction
across api-oauth.mdx / mcp.mdx / usage-rate-limits.mdx — was already
resolved in commit 4f2600b2a (reviewed commit was eb5654647).
2026-04-19 15:03:16 +04:00
Elie Habib
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>
2026-03-26 13:47:22 +02:00
Elie Habib
880c8dfdfd feat(community): add Discord link to dashboard (#1884)
* feat(community): add Discord link to dashboard footer and community widget

- CommunityWidget: URL → discord.gg/re63kWKxaz
- preferences-content: discussion link → Discord
- panel-layout footer: add Discord link
- en.json: "Join the Discussion" / "Open Discussion" → "Join Discord"

* refactor(community): replace Discussions with Discord in all footers

* fix(community): rotate dismissed key + update all locales for Discord rollout

- Bump DISMISSED_KEY to wm-community-dismissed-v2 so users who dismissed
  the old GitHub Discussions promo see the new Discord invite
- Update joinDiscussion/openDiscussion in all 20 non-English locales to
  "Join Discord" (proper noun, same in all languages)
2026-03-19 19:25:17 +04:00
Elie Habib
ba2117353e feat(community): add Discord link to footer (#1882)
* feat(community): add Discord link to footer across pro, blog, and docs

Adds https://discord.gg/re63kWKxaz to:
- pro-test/src/App.tsx — main Footer component and enterprise page footer
- blog-site/src/layouts/Base.astro — site-wide blog footer
- docs/docs.json — Mintlify socials icon + Community footer links section

* docs(readme): add Discord badge to header badges
2026-03-19 18:24:27 +04:00
Jon Torrez
987ed03f5d feat(webcams): add webcam map layer with Windy API integration (#1540) (#1540)
- Webcam markers on flat, globe, and DeckGL maps with category-based icons
- Server-side spatial queries via Redis GEOSEARCH with quantized bbox caching
- Pinned webcams panel with localStorage persistence
- Seed script for Windy API with regional bounding boxes and adaptive splitting
- Input validation (webcamId regex + encodeURIComponent) and NaN projection guards
- Bandwidth optimizations: zoom threshold, bbox overlap check, 1s cooldown
- Client-side image cache with 200-entry FIFO eviction
- Globe altitude-based viewport estimation for webcam loading
- CSP updates for webcam iframe sources
- Seed-meta key for health.js freshness tracking
2026-03-14 09:34:54 +04:00
Elie Habib
d36263d996 style: add Status page link to all footers (#1498) 2026-03-12 17:33:31 +04:00
Elie Habib
8174be951d feat(docs): Mintlify SEO metatags and changelog integration (#1495)
* style(docs): add OG image and SEO metatags for Mintlify

Sharing docs links showed generic meta from the main site.
Add seo.metatags to docs.json with OG image, site name,
and Twitter card configuration.

* feat(docs): add Mintlify changelog with Update components and RSS

Convert CHANGELOG.md into Mintlify-native changelog page using
<Update> components with tag filtering and RSS feed support.
All 27 versions converted with categorized tags for filtering.
2026-03-12 16:42:49 +04:00
Elie Habib
e23f1cf85b docs: add dedicated license page with anti-rebranding and enforcement sections (#1490)
Extract license content from contributing.mdx into its own first-class
docs/license.mdx page. Add prominent warnings about rebranding/renaming
being prohibited without a commercial license, an enforcement section,
and expanded commercial use restrictions.

Update README.md license section to reflect the dual-license model
(AGPL-3.0 for non-commercial, commercial license required for business
use). Previously it incorrectly stated commercial use was allowed under
AGPL alone.

Update cross-references in documentation.mdx and getting-started.mdx to
point to the new /license page.
2026-03-12 14:43:28 +04:00
Elie Habib
1f38dea225 docs: restructure documentation into focused, navigable pages (#1465)
* docs: restructure documentation into focused, navigable pages (#docs-reorg)

Break the 4096-line documentation.mdx monolith into 13 focused pages
organized by feature area. Reorganize Mintlify navigation from 5 generic
groups into 7 feature-based groups. Move Orbital Surveillance from
Infrastructure to Map Layers where it belongs.

- Extract: signal-intelligence, features, overview, hotspots, CII,
  geographic-convergence, strategic-risk, infrastructure-cascade,
  military-tracking, maritime-intelligence, natural-disasters,
  contributing, getting-started
- Append to: architecture.mdx (9 sections), ai-intelligence.mdx (3 sections)
- Fix legacy .md links in map-engine.mdx, maps-and-geocoding.mdx
- Slim documentation.mdx to an 80-line index/hub page
- Eliminate duplicate content that caused repeated headings

* fix(docs): remove duplicate H1 headings from all Mintlify pages

Mintlify auto-renders the frontmatter `title` as an H1, so having
`# Title` in the body creates a doubled heading on every page.
Remove the redundant H1 (and repeated description lines) from all
31 .mdx files.
2026-03-12 06:44:35 +04:00
Elie Habib
dce6b77b27 fix(docs): remove invalid colors.anchors from Mintlify config (#1464)
Mintlify v2 schema rejects colors.anchors as unrecognized, blocking
deployment validation. Added in #1463 but not a valid schema key.
2026-03-12 01:33:45 +04:00
Elie Habib
2a7d7fc3fe fix: standardize brand name to "World Monitor" with space (#1463)
Replace "WorldMonitor" with "World Monitor" in all user-facing display
text across blog posts, docs, layouts, structured data, footer, offline
page, and X-Title headers. Technical identifiers (User-Agent strings,
X-WorldMonitor-Key headers, @WorldMonitorApp handle, function names)
are preserved unchanged. Also adds anchors color to Mintlify docs config
to fix blue link color in dark mode.
2026-03-12 01:28:16 +04:00
Elie Habib
878e30c371 style(docs): align Mintlify header/footer with site design (#1455)
* style(docs): align Mintlify header/footer with site design system

- Navbar: Blog, Dashboard, Pro, GitHub (matching site header)
- Primary CTA: "Get Early Access" green button linking to /pro#waitlist
- Colors: switch from blue (#3b82f6) to green (#4ade80) accent
- Footer: Dashboard, Pro, Blog + Community (GitHub, Discussions, X)
- Add X/Twitter social link
- Normalize all URLs to www.worldmonitor.app
- Name simplified to "World Monitor" (matching site branding)

* feat: add Docs link to dashboard, blog, and pro navigation

Add link to /docs across all site surfaces:
- Blog header nav and footer
- Dashboard footer nav
- Pro page footer (main + enterprise)
2026-03-12 01:02:52 +04:00
Elie Habib
caa3e9f82f fix(docs): rename doc files to lowercase for Mintlify (#1451)
* fix(docs): rename doc files to lowercase kebab-case for Mintlify

Mintlify serves pages at lowercase URLs. Uppercase filenames caused
404s on the Documentation tab. Renames all 18 doc files, updates
docs.json references, and fixes internal cross-links.

* fix(docs): rename .md to .mdx for Mintlify compatibility

Mintlify expects .mdx files. Plain .md files were not being found,
causing 404s on all documentation pages.

* feat(docs): add navbar links and footer with site variants

- Navbar: Live App, Tech, Finance, Blog links + GitHub CTA
- Footer: World Monitor variants + Resources columns
- Logo links back to worldmonitor.app
2026-03-12 00:30:15 +04:00
Elie Habib
f292282ff7 fix(docs): add required theme field to Mintlify docs.json (#1450)
* fix(docs): add required theme field to Mintlify docs.json

Mintlify v2 requires a theme discriminator. Without it, deployment
fails with "Invalid discriminator value" error.

* fix(docs): migrate docs.json to Mintlify v2 schema

- navigation: object with tabs/groups instead of array
- Remove colors.background (unrecognized in v2)
- topbarLinks/topbarCtaButton → navbar
- footerSocials → footer.socials
- openapi: single string per group instead of array
- Split docs into Documentation + API Reference tabs
2026-03-11 23:58:11 +04:00
Elie Habib
3d4c2fbdf4 chore(docs): trigger Mintlify deployment (#1449)
Trivial name change to force Mintlify GitHub App to detect docs changes
after monorepo setting was updated in the dashboard.
2026-03-11 23:42:09 +04:00
Elie Habib
33a423b87f fix(docs): rename mint.json to docs.json for Mintlify v2 (#1446)
Mintlify deprecated mint.json in favor of docs.json. The dashboard
expects docs.json to exist in the configured directory.
2026-03-11 23:29:04 +04:00