mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
24f5312191e3f6cf048768fc3c7db47c70a54eb2
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
24f5312191 |
Phase 1 PR3: RegionalIntelligenceBoard panel UI (#2963)
* feat(intelligence): RegionalIntelligenceBoard panel UI (Phase 1 PR3)
Phase 1 PR3 of the Regional Intelligence Model. New premium panel that
renders a canonical RegionalSnapshot with 6 structured blocks plus the
LLM narrative sections from Phase 1 PR2.
## What landed
### New panel: RegionalIntelligenceBoard
src/components/RegionalIntelligenceBoard.ts (Panel wrapper)
src/components/regional-intelligence-board-utils.ts (pure HTML builders)
Region dropdown (7 non-global regions) → on change calls
IntelligenceServiceClient.getRegionalSnapshot → renders buildBoardHtml().
Layout (top to bottom):
- Narrative sections (situation, balance_assessment, outlook 24h/7d/30d)
— each section hidden when its text is empty, evidence IDs shown as pills
- Regime block — current label, previous label, transition driver
- Balance Vector — 4 pressure axes + 3 buffer axes with horizontal bars,
plus a centered net_balance bar
- Actors — top 5 by leverage_score with role, domains, and colored delta
- Scenarios — 3 horizon columns (24h/7d/30d) × 4 lanes (base/escalation/
containment/fragmentation), sorted by probability within each horizon
- Transmission Paths — top 5 by confidence with mechanism, corridor,
severity, and latency
- Watchlist — active triggers + narrative watch_items
- Meta footer — generated timestamp, confidence, scoring/geo versions,
narrative provider/model
### Pure builders split for test isolation
All HTML builders live in regional-intelligence-board-utils.ts. The Panel
class in RegionalIntelligenceBoard.ts is a thin wrapper that calls them
and inserts the result via setContent. This split matches the existing
resilience-widget-utils pattern and lets node:test runners import the
builders directly without pulling in Vite-only services like
@/services/i18n (which fails with `import.meta.glob is not a function`).
### PRO gating + registration
- src/config/panels.ts: added 'regional-intelligence' to FULL_PANELS with
premium: 'locked', enabled: false, plus isPanelEntitled API-key list
- src/app/panel-layout.ts: async dynamic import mounts the panel after
the DeductionPanel block, reusing the same async-mount + gating pattern
- src/config/commands.ts: CMD+K entry with 🌍 icon and keywords
- tests/panel-config-guardrails.test.mjs: regional-intelligence added to
the allowedContexts allowlist for the ungated direct-assignment check
(the panel is intentionally premium-gated via WEB_PREMIUM_PANELS and
async-mounted, matching DeductionPanel)
## Tests — 38 new unit tests
tests/regional-intelligence-board.test.mts exercises the pure builders:
- BOARD_REGIONS (2): 7 non-global regions, correct IDs
- buildRegimeBlock (4): current label rendered, "Was:" shown on change,
hidden when unchanged, unknown fallback
- buildBalanceBlock (4): all 7 axes rendered, net_balance shown,
unavailable fallback, value clamping for >1 inputs
- buildActorsBlock (4): top-5 cap, sort by leverage_score, delta colors,
empty state
- buildScenariosBlock (4): horizon order (24h/7d/30d), percentage
rendering, in-horizon probability sort, empty state
- buildTransmissionBlock (4): content rendering, confidence sort,
top-5 cap, empty state
- buildWatchlistBlock (5): triggers + watch items, triggers-only,
watch-items-only, empty-text filtering, all-empty state
- buildNarrativeHtml (5): all sections, empty-section hiding, all-empty
returns '', undefined returns '', evidence ID pills
- buildMetaFooter (3): content, "no narrative" when provider empty,
missing-meta returns ''
- buildBoardHtml (3): all 6 block titles + footer, HTML escaping,
mostly-empty snapshot renders without throwing
## Verification
- npm run test:data: 4390/4390 pass
- npm run typecheck: clean
- npm run typecheck:api: clean
- biome lint on touched files: clean (pre-existing panel-layout.ts
complexity warning unchanged)
## Dependency on PR2
This PR renders whatever narrative the snapshot carries. Phase 1 PR2
(#2960) populates the narrative; without PR2 merged the narrative
section just stays hidden (empty sections are filtered out in
buildNarrativeHtml). The UI ships safely in either order.
* fix(intelligence): request-sequence cancellation in RegionalIntelligenceBoard (PR #2963 review)
P2 review finding on PR #2963. loadCurrent() used a naive `loading`
boolean that DROPPED any region change while a fetch was in flight, so
a fast dropdown switch would leave the panel rendering the previous
region indefinitely until the user changed it a third time.
Fix: replaced the boolean with a monotonic `latestSequence` counter.
Each load claims a sequence before awaiting the RPC and only renders
its response when mySequence still matches latestSequence on return.
Earlier in-flight responses are silently discarded. Latest selection
always wins.
## Pure arbitrator helper
Added isLatestSequence(mySequence, latestSequence) to
regional-intelligence-board-utils.ts. The helper is trivially pure,
but exporting it makes the arbitration semantics testable without
instantiating the Panel class (which can't be imported by node:test
due to import.meta.glob in @/services/i18n — see the
feedback_panel_utils_split_for_node_test memory).
## Tests — 7 new regression tests
isLatestSequence (3):
- matching sequences return true
- newer sequences return false
- defensive: mine > latest also returns false
loadCurrent race simulation (4): each test mimics the real
loadCurrent() sequence-claim-and-arbitrate flow with controllable
deferred promises so the resolution order can be exercised directly:
- earlier load resolves AFTER the later one → discarded
- earlier load resolves BEFORE the later one → still discarded
- three rapid switches, scrambled resolution order → only last renders
- single load (no race) still renders normally
## Verification
- npx tsx --test tests/regional-intelligence-board.test.mts: 45/45 pass
- npm run test:data: 4393/4393 pass
- npm run typecheck: clean
- biome lint on touched files: clean
|
||
|
|
c07bd5939c |
fix(panels): deduplicate AI Regulation Dashboard panel name (#2796)
* fix(panels): rename duplicate "AI Regulation Dashboard" panel
The `regulation` panel key (finance variant RSS feed) and `ai-regulation`
(custom RegulationPanel dashboard) both resolved to "AI Regulation
Dashboard" in the panel picker, causing a duplicate entry.
Renamed `regulation` to "Financial Regulation" since it serves financial
regulation news feeds, distinct from the AI regulation dashboard.
* fix(panels): split finance regulation into own key to fix duplicate name
The `regulation` panel key was shared between the AI regulation
dashboard (RegulationPanel, which reads t('panels.regulation')) and
the finance variant's financial regulation RSS feeds. Both resolved
to "AI Regulation Dashboard" in the picker.
Created `fin-regulation` as a distinct key for the finance RSS panel:
- panels.ts: renamed panel config entries in tech + finance variants
- feeds.ts: renamed feed key from `regulation` to `fin-regulation`
- panel-layout.ts: added createNewsPanel('fin-regulation') call
- commands.ts: updated CMD+K entry
- en.json: restored panels.regulation for AI dashboard, added
panels.fin-regulation for financial regulation
The `panels.regulation` locale key now exclusively serves
RegulationPanel's title across all locales.
* fix(panels): add migration, remove from tech variant, fix locale key
- Add ['regulation', 'fin-regulation'] to panel key rename migration
table so existing users' saved settings are preserved on upgrade
- Remove fin-regulation from TECH_PANELS (finance-only panel, tech
variant uses policy for AI regulation feeds)
- Fix locale key: panels.finRegulation (camelCase) not panels.fin-
regulation, matching the dashed-to-camel lookup in
getLocalizedPanelName()
* fix(panels): bump migration key, remove explicit createNewsPanel
P1: Bumped migration key to v2.6.8 so the regulation->fin-regulation
rename runs for users who already have the v2.6 flag set.
P2+P3: Removed explicit createNewsPanel('fin-regulation') call. The
generic FEEDS loop auto-creates it in finance builds (where the feed
key exists in FINANCE_FEEDS). This prevents an empty panel in non-
finance variants and avoids the t('panels.fin-regulation') lookup
mismatch (the settings UI uses camelCased getLocalizedPanelName
which correctly resolves panels.finRegulation).
* fix(panels): variant-aware migration + all 21 locale translations
P1: Migration is now variant-aware. In finance, regulation is renamed
to fin-regulation (preserving settings). In all other variants, the
stale regulation key is pruned (it was dead config with no feeds).
P2: Added finRegulation locale key to all 21 locale files so non-
English users see the translated "Financial Regulation" label instead
of the English fallback.
* fix(panels): migrate saved order keys + prune stale keys in settings window
Medium: The rename migration now also rewrites regulation->fin-regulation
in saved panel order, bottom-set, and bottom storage keys so finance
users preserve their custom panel positioning after upgrade.
Low: The standalone settings window now prunes unknown panel keys from
loaded storage before rendering, preventing stale regulation entries
from appearing as ghost toggles alongside fin-regulation.
|
||
|
|
63858a8711 |
feat(intelligence): expose DeductionPanel on web as PRO feature (#2385)
* feat(intelligence): expose DeductionPanel on web as PRO feature Previously desktop-only (isDesktopApp guard). Now available on web with premium: 'locked' gating — free users see lock + PRO badge, PRO users get full access. 4-layer checklist applied: - Layer 1: added 'deduction' to ALL_PANELS with premium: 'locked' - Layer 2: added 'deduction' to apiKeyPanels in isPanelEntitled - Layer 3: added /api/intelligence/v1/deduct-situation to PREMIUM_RPC_PATHS - Layer 4: removed isDesktopApp guard (SITE_VARIANT === 'full' only) - Bonus: removed now-redundant deduction exclusion from dev panel warning * docs: add analytical frameworks review findings plan * docs: fix markdownlint blank line before list in plan * fix(panel-layout): replay settings for async deduction panel The web-only DeductionPanel mounts after the initial startup pass, so saved disabled state was never replayed on reload. Reapply panel settings after the async mount and add a guardrail test to keep the lazy panel path aligned with the rest of the layout. * fix(intelligence): add deduction to WEB_PREMIUM_PANELS, replay updatePanelGating after async mount, fix stale test comment --------- Co-authored-by: lspassos1 <lspassos@icloud.com> |
||
|
|
77aee7225c |
fix(panels): skip LiveNewsPanel on variants with no channels (#1987)
* fix(panels): skip LiveNewsPanel instantiation when variant has no channels The unified panel registry (PR #1911) registers all panels across all variants. On the happy variant, DEFAULT_LIVE_CHANNELS is empty, so LiveNewsPanel would crash at this.channels[0]! in the constructor. Guard the instantiation with getDefaultLiveChannels().length > 0 so the panel is never created for variants that have no channels to show, rather than special-casing inside the component. * fix(panels): prevent LiveNewsPanel crash when variant has no default channels The previous guard skipped instantiation entirely on happy variant, which broke the settings toggle (applyPanelSettings only calls toggle() on existing instances, never creates new ones) and blocked users who had already persisted custom channels in STORAGE_KEYS.liveChannels. Correct fix: always instantiate, but add a final fallback to FULL_LIVE_CHANNELS in both the constructor and refreshChannelsFromStorage so this.channels[0]! is never undefined regardless of variant defaults. * fix(panels): guard LiveNewsPanel instantiation on channels availability Replace the FULL_LIVE_CHANNELS fallback (which would repopulate the panel with wrong channels on happy variant and break intentionally empty channel sets) with a precise instantiation guard: skip creation only when both getDefaultLiveChannels() and loadChannelsFromStorage() are empty. This means: - Happy + no saved channels: panel not instantiated, no crash, no wrong content - Happy + user-saved channels: panel created, works correctly - Any variant where user cleared all channels: existing behavior preserved (no FULL_LIVE_CHANNELS override) Adds 6 static regression tests that pin: - DEFAULT_LIVE_CHANNELS is [] for happy variant - Constructor has no FULL_LIVE_CHANNELS fallback - refreshChannelsFromStorage has no FULL_LIVE_CHANNELS fallback - panel-layout.ts guard checks both defaults and saved channels * fix(panels): add mid-session lazy instantiation path for LiveNewsPanel The startup guard correctly skips LiveNewsPanel on happy variant when no channels exist, but createPanels() only runs once. If the user adds channels later via the standalone manager (?live-channels=1) or Unified Settings, ctx.panels['live-news'] stayed undefined and applyPanelSettings could only toggle existing instances, leaving live-news permanently unavailable without a full reload. Fix: - Add mountLiveNewsIfReady() to PanelLayout: instantiates, makeDraggable, and mounts the panel into the grid when called after channels appear - Add mountLiveNewsIfReady optional callback to EventHandlerCallbacks - Wire it in App.ts via this.panelLayout.mountLiveNewsIfReady() - Call it from the liveChannels storage event handler when the panel does not yet exist (the existing refreshChannelsFromStorage path is taken when it does) Tests: 3 new regression tests pin the lazy instantiation wiring; panel-config-guardrails allowlist updated for the new assignment site. |
||
|
|
4353c20637 | feat(widgets): AI widget builder with live WorldMonitor data (#1732) | ||
|
|
bf7b03ab8f |
refactor: guard panel creation by variant config (#1221)
* fix: three panel issues — Tech Readiness toggle, Crypto top 10, FIRMS key check 1. #1132 — Add tech-readiness to FULL_PANELS so it appears in the Settings toggle list for Full/Geopolitical variant users. 2. #979 — Expand crypto panel from 4 coins to top 10 by market cap (BTC, ETH, USDT, BNB, SOL, XRP, USDC, ADA, DOGE, TRX) across client config, server metadata, CoinPaprika fallback map, and seed script. 3. #997 — Check isFeatureAvailable('nasaFirms') before loading FIRMS data. When the API key is missing, show a clear "not configured" message instead of the generic "No fire data available". Closes #1132, closes #979, closes #997 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace stablecoins with AVAX/LINK, remove duplicate key, revert FIRMS change - Replace USDT/USDC (stablecoins pegged ~$1) with AVAX and LINK - Remove duplicate 'usd-coin' key in COINPAPRIKA_ID_MAP - Add CoinPaprika fallback IDs for avalanche-2 and chainlink - Revert FIRMS API key gating (handled differently now) - Add sync comments across the 3 crypto config locations * fix: update AIS relay + seed CoinPaprika fallback for all 10 coins The AIS relay (primary seeder) still had the old 4-coin list. The seed script's CoinPaprika fallback map was also missing the new coins. Both now have all 10 entries. * refactor: DRY crypto config into shared/crypto.json Single source of truth for crypto IDs, metadata, and CoinPaprika fallback mappings. All 4 consumers now import from shared/crypto.json: - src/config/markets.ts (client) - server/worldmonitor/market/v1/_shared.ts (server) - scripts/seed-crypto-quotes.mjs (seed script) - scripts/ais-relay.cjs (primary relay seeder) Adding a new coin now requires editing only shared/crypto.json. * chore: fix pre-existing markdown lint errors in README.md Add blank lines between headings and lists per MD022/MD032 rules. * fix: correct CoinPaprika XRP mapping and add crypto config test - Fix xrp-ripple → xrp-xrp (current CoinPaprika id) - Add tests/crypto-config.test.mjs: validates every coin has meta, coinpaprika mapping, unique symbols, no stablecoins, and valid id format — bad fallback ids now fail fast * test: validate CoinPaprika ids against live API The regex-only check wouldn't have caught the xrp-ripple typo. New test fetches /v1/coins from CoinPaprika and asserts every configured id exists. Gracefully skips if API is unreachable. * fix(test): handle network failures in CoinPaprika API validation Wrap fetch in try-catch so DNS failures, timeouts, and rate limits skip gracefully instead of failing the test suite. * refactor: guard panel creation by variant config Only create panels listed in the active variant's DEFAULT_PANELS. Previously, ~30 panels were created unconditionally for ALL variants, wasting DOM nodes and memory (e.g., tech variant got 15+ geopolitical panels it never uses). Changes: - Add shouldCreatePanel(), createNewsPanel(), createPanel() helpers that gate creation on DEFAULT_PANELS membership - Add shouldCreatePanel guard inside lazyPanel() for lazy-loaded panels - Replace 27 repetitive 4-line NewsPanel blocks with one-liner calls - Replace variant-specific SITE_VARIANT blocks with per-panel guards - Add null guards to all hard panel dereferences in data-loader.ts and event-handlers.ts (markets, heatmap, commodities, crypto, polymarket, monitors) - Add commodity variant to trade-policy/supply-chain data loading - Remove climate/satellite-fires from COMMODITY_PANELS (no data loader) - Guard giving data fetch with DEFAULT_PANELS check - Add FEEDS loop guard to skip panels not in config - Add DEV-mode assertion warning about unconfigured panels - Add panel-config-guardrails.test.mjs (static analysis test) * fix: tech-readiness refresh and settings visibility for full variant - Add full variant to tech-readiness data loading condition - Add tech-readiness to full variant's dataTracking category map --------- Co-authored-by: Nicolas Gomes Ferreira Dos Santos <ndossantos@ucsd.edu> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |