* fix(panels): always fire background RPC refresh after bootstrap render Bootstrap hydration (getHydratedData) is one-shot — once rendered from it, panels never refresh and can show stale or partial data indefinitely. Affected panels: MacroSignals, ETFFlows, Stablecoins, FuelPrices, GulfEconomies, GroceryBasket, BigMac. Pattern: render from bootstrap immediately for fast first paint, then fire a background RPC call that silently updates the panel with live data. Errors during background refresh are suppressed when bootstrap data is already visible (no error flash over valid data). * fix(panels): guard background RPC refresh against empty response overwriting bootstrap Empty RPC responses (200 + empty array) no longer overwrite valid bootstrap data with error/unavailable state across all 7 affected panels: - ETFFlowsPanel, StablecoinPanel: wrap this.data assignment in `if (fresh.xxx?.length || !this.data)` guard - FuelPricesPanel, GulfEconomiesPanel, GroceryBasketPanel, BigMacPanel: add `!data.xxx?.length` check in background .then() before calling render - MacroSignalsPanel: return false early when error suppressed to skip redundant renderPanel() call * fix(hormuz): fix noUncheckedIndexedAccess TypeScript errors * fix(todos): add blank lines around headings (markdownlint MD022) * fix(hormuz): add missing hormuz-tracker service + fix implicit any in HormuzPanel * revert: remove HormuzPanel.ts from this branch (belongs in PR #2210)
3.4 KiB
status, priority, issue_id, tags
| status | priority | issue_id | tags | ||||
|---|---|---|---|---|---|---|---|
| pending | p2 | 015 |
|
Two missing null guards + theater.label undefined in simulation package builders
Problem Statement
Three correctness gaps in the new simulation package builders produce silent degradation or misleading output without any log.
Findings
1. buildSimulationPackageEvaluationTargets: no guard when candidate is undefined
const candidate = candidates.find((c) => c.candidateStateId === theater.candidateStateId);
// No guard here — if candidate is undefined, actors silently falls back to 'key actors'
const actors = (candidate?.stateSummary?.actors || []).slice(0, 3).join(', ') || 'key actors';
buildSimulationPackageConstraints has an explicit if (!candidate) continue guard for the same find() pattern. buildSimulationPackageEvaluationTargets does not. Silent degradation produces a valid-looking evaluation target with generic actor text and no diagnostic.
2. theater.label has no fallback — produces "Simulate how a undefined (...)" when candidateStateLabel is missing
label: c.candidateStateLabel, // no fallback
// later:
return `Simulate how a ${theater.label} (${theater.stateKind || 'disruption'}...`;
If candidateStateLabel is absent, the simulationRequirement string contains literal "undefined". This propagates into R2 and downstream LLM consumers.
3. buildSimulationStructuralWorld: s.macroRegion (singular) matched against theaterRegions built from c.macroRegions (plural array)
If the signal schema stores an array in macroRegion, the Set.has() lookup against an array reference silently returns false for all such signals, producing empty touchingSignals. This code path has no test coverage (tests pass signals: []).
Proposed Solutions
// Fix 1: add candidate guard in buildSimulationPackageEvaluationTargets
const candidate = candidates.find((c) => c.candidateStateId === theater.candidateStateId);
if (!candidate) {
console.warn(`[SimulationPackage] No candidate for theaterId=${theater.theaterId} (evaluationTargets)`);
}
// Fix 2: label fallback in selectedTheaters map
label: c.candidateStateLabel || c.dominantRegion || 'unknown theater',
// Fix 3: handle both singular and array macroRegion in signal filter
.filter((s) => {
const sigMacro = s.macroRegion;
return theaterRegions.has(s.region)
|| (Array.isArray(sigMacro) ? sigMacro.some((r) => theaterRegions.has(r)) : theaterRegions.has(sigMacro))
|| theaterStateIds.has(s.situationId);
})
Effort: Small | Risk: Low
Acceptance Criteria
buildSimulationPackageEvaluationTargetslogs a warn when candidate is undefined for a theatertheater.labelis neverundefined— falls back todominantRegionor'unknown theater'buildSimulationStructuralWorldhandles both singular string and arraymacroRegionon signals- Test: a theater with
candidateStateLabel: undefinedproduces asimulationRequirementthat does NOT contain the string"undefined"
Technical Details
- File:
scripts/seed-forecasts.mjs—buildSimulationPackageEvaluationTargets,buildSimulationStructuralWorld,selectedTheatersmap inbuildSimulationPackageFromDeepSnapshot
Work Log
- 2026-03-24: Found by compound-engineering:review:kieran-typescript-reviewer in PR #2204 review