fix(energy-atlas): wire 4 panels into App.ts primeTask (panels stuck on 'Loading…')#3386
Conversation
…ly fetch
Root cause: `App.ts::primeDataForVisiblePanels` is the sole near-viewport
kickoff path for panel `fetchData()` calls. Panel's constructor only
calls `showLoading()`; nothing else in the app triggers fetchData on
attach. Every panel that does real work has a corresponding
`if (shouldPrime('<key>')) primeTask(...)` entry in that table.
All 4 Energy Atlas panels were missing their entries:
- pipeline-status → PipelineStatusPanel
- storage-facility-map → StorageFacilityMapPanel
- fuel-shortages → FuelShortagePanel
- energy-disruptions → EnergyDisruptionsPanel
User-visible symptom: the 4 panels shipped as part of #3366 / #3378 /
the Energy Atlas PR chain rendered their headers + spinner but never
left "Loading…". Verified all four upstream RPCs return data:
- /api/supply-chain/v1/list-pipelines?commodityType= → 100 KB
- /api/supply-chain/v1/list-storage-facilities → 115 KB
- /api/supply-chain/v1/list-fuel-shortages → 21 KB
- /api/supply-chain/v1/list-energy-disruptions → 36 KB
and /api/health reports all 5 backing Redis keys as OK with the
expected record counts (75 gas + 75 oil + 200 storage + 29 shortages
+ 52 disruptions).
Fix: 4 primeTask entries mirroring the pattern already used for
energy-crisis, hormuz-tracker, oil-inventories, etc. Each panel
already implements the full fetch path (bootstrap-cache-first,
RPC fallback, setCached... back-propagation, error states); the
entries just give App.ts a reason to call it.
Placement: immediately after oil-inventories, grouping the energy
surface together.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Greptile SummaryThis PR wires four existing Energy Atlas panels ( Confidence Score: 5/5Safe to merge — purely additive wiring that follows the established pattern with no logic or security concerns. The change adds four well-established primeTask blocks with no novel logic. All prerequisites (bootstrap hydration, panel instantiation in panel-layout.ts, config registration) were already in place. No existing behavior is modified, typecheck passes, and the pattern is identical to dozens of existing entries. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant VP as Viewport Observer
participant App as App.ts
participant PT as primeTask
participant P as EnergyAtlasPanel
participant BS as Bootstrap Cache
participant RPC as Supply-Chain RPC
VP->>App: panel enters near-viewport
App->>App: primeDataForVisiblePanels()
Note over App: shouldPrime('pipeline-status') etc.<br/>(added by this PR)
App->>PT: primeTask('pipeline-status', () => panel.fetchData())
PT->>P: fetchData()
P->>BS: getHydratedData() — bootstrap cache lookup
alt cache hit
BS-->>P: raw registry data
P->>P: render()
else cache miss
P->>RPC: list-pipelines / list-storage-facilities / etc.
RPC-->>P: 200 payload
P->>BS: setCached…() — back-propagate to shared store
P->>P: render()
end
Reviews (1): Last reviewed commit: "fix(energy-atlas): wire 4 panels into Ap..." | Re-trigger Greptile |
… (parity PR 2, plan U5-U6) (#3398) * feat(energy-atlas): EnergyRiskOverviewPanel — executive overview tile (PR 2, plan U5-U6) Lands the consolidated "first fold" surface that peer reference energy-intel sites use as their executive overview. Six tiles in a single panel: 1. Strait of Hormuz status (closed/disrupted/restricted/open) 2. EU gas storage fill % (red <30, amber 30-49, green ≥50) 3. Brent crude price + 1-day change (importer-leaning: up=red, down=green) 4. Active disruption count (filtered to endAt === null) 5. Data freshness ("X min ago" from youngest fetchedAt) 6. Hormuz crisis day counter (default 2026-02-23 start, env-overridable) Per docs/plans/2026-04-25-003-feat-energy-parity-pushup-plan.md PR 2. U5 — Component (src/components/EnergyRiskOverviewPanel.ts): - Composes 5 existing services via Promise.allSettled — never .all. One slow or failing source CANNOT freeze the panel; failed tiles render '—' and carry data-degraded="true" for QA inspection. Single most important behavior — guards against the recurrence of the #3386 panel-stuck bug. - Uses the actual Hormuz status enum 'closed'|'disrupted'|'restricted'|'open' (NOT 'normal'/'reduced'/'critical' — that triplet was a misread in earlier drafts). Test suite explicitly rejects the wrong triplet via the gray sentinel fallback. - Brent color inverted from a default market panel: oil price UP = red (bad for energy importers, the dominant Atlas reader); DOWN = green. - Crisis-day counter sourced from VITE_HORMUZ_CRISIS_START_DATE env (default 2026-02-23). NaN/future-dated values handled with explicit '—' / 'pending' sentinels so the tile never renders 'Day NaN'. - 60s setInterval re-renders the freshness tile only — no new RPCs fire on tick. setInterval cleared in destroy() so panel teardown is clean. - Tests: 24 in tests/energy-risk-overview-panel.test.mts cover Hormuz color enum (including the wrong-triplet rejection), EU gas thresholds, Brent inversion, active-disruption color bands, freshness label formatting, crisis-day counter (today/5-days/NaN/future), and the degraded-mode contract (all-fail still renders 6 tiles with 4 marked data-degraded). U6 — Wiring (5 sites per skill panel-stuck-loading-means-missing-primetask): - src/components/index.ts: barrel export - src/app/panel-layout.ts: import + createPanel('energy-risk-overview', ...) - src/config/panels.ts: priority-1 entry in ENERGY_PANELS (top-of-grid), priority-2 entry in FULL_PANELS (CMD+K-discoverable, default disabled), panelKey added to PANEL_CATEGORY_MAP marketsFinance category - src/App.ts: import type + primeTask kickoff (between energy-disruptions and climate-news in the existing ordering convention) - src/config/commands.ts: panel:energy-risk-overview command with keywords for 'risk overview', 'executive overview', 'hormuz status', 'crisis day' No new RPCs (preserves agent-native parity — every metric the panel shows is already exposed via existing Connect-RPC handlers and bootstrap-cache keys; agents can answer the same questions through the same surface). Tests: typecheck clean; 24 unit tests pass on the panel's pure helpers. Manual visual QA pending PR merge + deploy. Plan section §M effort estimate: ~1.5d. Codex-approved through 8 review rounds against origin/main @ 0500733. * fix(energy-atlas): extract Risk Overview state-builder + real component test (PR2 review) P2 — tests duplicated helper logic instead of testing the real panel (energy-risk-overview-panel.test.mts:10): - The original tests pinned color/threshold helpers but didn't import the panel's actual state-building logic, so the panel could ship with a broken Promise.allSettled wiring while the tests stayed green. Refactor: - Extract the state-building logic into a NEW Vite-free module: src/components/_energy-risk-overview-state.ts. Exports buildOverviewState(hormuz, euGas, brent, disruptions, now) and a countDegradedTiles() helper for tests. - The panel now imports and calls buildOverviewState() directly inside fetchData(); no logic duplication. The Hormuz tile renderer narrows status with an explicit cast at use site. - Why a new module: the panel transitively imports `import.meta.glob` via the i18n service, which doesn't resolve under node:test even with tsx loader. Extracting the testable logic into a Vite-dependency-free module is the cleanest way to exercise the production code from tests, per skill panel-stuck-loading-means- missing-primetask's emphasis on "test the actual production logic, not a copy-paste of it". Tests added (11 real-component cases via the new module): - All four sources fulfilled → 0 degraded. - All four sources rejected → 4 degraded, no throw, no cascade. - Mixed (1 fulfilled, 3 rejected) → only one tile populated. - euGas with `unavailable: true` sentinel → degraded. - euGas with fillPct=0 → degraded (treats as no-data, not "0% red"). - brent empty data array → degraded. - brent first-quote price=null → degraded. - disruptions upstreamUnavailable=true → degraded. - disruptions ongoing filter: counts only endAt-falsy events. - Malformed hormuz response (missing status field) → degraded sentinel. - One rejected source MUST NOT cascade to fulfilled siblings (the core degraded-mode contract — pinned explicitly). Total: 35 tests in this file (was 24; +11 real-component cases). typecheck clean. * fix(energy-atlas): server-side disruptions filter + once-only style + panel name parity (PR2 review) Three Greptile P2 findings on PR #3398: - listEnergyDisruptions called with ongoingOnly:true so the server filters the historical 52-event payload server-side. The state builder still re-filters as defense-in-depth. - RISK_OVERVIEW_CSS injected once into <head> via injectRiskOverviewStylesOnce instead of being emitted into setContent on every render. The 60s freshness setInterval was tearing out and re-inserting the style tag every minute. - FULL_PANELS entry renamed from "Energy Risk Overview" to "Global Energy Risk Overview" to match ENERGY_PANELS and the CMD+K command.
Summary
Evidence the data is fine; it's pure wiring
All 5 backing Redis keys report OK via `/api/health` with expected record counts:
```
pipelinesGas OK 75
pipelinesOil OK 75
storageFacilities OK 200
fuelShortages OK 29
energyDisruptions OK 52
```
All 4 RPCs return real payloads (probed directly):
```
GET /api/supply-chain/v1/list-pipelines?commodityType= → 200 (100 KB)
GET /api/supply-chain/v1/list-storage-facilities → 200 (115 KB)
GET /api/supply-chain/v1/list-fuel-shortages → 200 (21 KB)
GET /api/supply-chain/v1/list-energy-disruptions → 200 (36 KB)
```
Why the panels self-fetch already
Each panel already implements the full data path: bootstrap-cache-first lookup, RPC fallback, `setCached…` back-propagation into the shared store so map layers pick up the same data, and error states. The panels are not missing any logic; they were simply never triggered because App.ts doesn't know about them.
Test plan