* feat(supply-chain): Sprint E — scenario visual completion + service parity - E1: fetchSectorDependency exported from supply-chain service index - E2: PRO gate + all-renderer dispatch in MapContainer.activateScenario - E3: scenario summary banner in SupplyChainPanel (dismiss wired) - E4: "Simulate Closure" trigger button in expanded chokepoint cards - E5: affectedIso2s heat layer in DeckGLMap (GeoJsonLayer, red tint) - E6: SVG renderer setScenarioState (best-effort iso2 fill) - E7: Globe renderer scenario polygons via flushPolygons - E8: integration tests for scenario run/status endpoints * fix(supply-chain): address PR #2910 review findings (P1 + P2 + P3) - Wire setOnScenarioActivate + setOnDismissScenario in panel-layout.ts (todo #155) - Rename shadow variable t→tmpl in SCENARIO_TEMPLATES.find (todo #152) - Add statusResp.ok guard in scenario polling loop (todo #153) - Replace status.result! non-null assertion with shape guard (todo #154) - Add AbortController to prevent concurrent polling races (todo #162) - Add polygonStrokeColor scenario branch (transparent) in GlobeMap (todo #156) - Re-export SCENARIO_TEMPLATES via src/config/scenario-templates.ts (todo #157) - Cache affectedIso2Set in DeckGLMap.setScenarioState (todo #158) - Add scenario paths to PREMIUM_RPC_PATHS for auth injection (todo #160) - Show template name in scenario banner instead of raw ID (todo #163) * fix(supply-chain): address PR #2910 review findings - Add auth headers to scenario fetch calls in SupplyChainPanel - Reset button state on scenario dismiss - Poll status immediately on first iteration (no 2s delay) - Pre-compute scenario polygons in GlobeMap.setScenarioState - Use scenarioId for DeckGL updateTriggers precision * fix(supply-chain): wire panel instance to MapContainer, stop button click propagation - Call setSupplyChainPanel() in panel-layout.ts so scenario banner renders - Add stopPropagation() to Simulate Closure button to prevent card collapse
3.0 KiB
status, priority, issue_id, tags, dependencies
| status | priority | issue_id | tags | dependencies | ||||
|---|---|---|---|---|---|---|---|---|
| pending | p2 | 159 |
|
flushPolygons Iterates All 250 GeoJSON Features on Every Flush — Pre-compute Scenario Polygons
Problem Statement
GlobeMap.flushPolygons() iterates over countriesGeoJsonData.features to build scenario GlobePolygon objects on every call. flushPolygons() is called whenever ANY polygon layer changes (CII update, conflict update, imagery toggle, storm cone render). With ~250 GeoJSON features, this is ~250 object allocations + ISO-2 Set lookups on every flush, even when the scenario state hasn't changed.
Findings
- File:
src/components/GlobeMap.ts flushPolygons()constructs scenario polygons inline fromthis.scenarioState.affectedIso2s+countriesGeoJsonDataon every call- Every other polygon layer (CII, conflict, imagery) pre-builds its polygon array before
flushPolygons()and simply concatenates setScenarioState()callsflushPolygons()but does not cache the pre-built polygon array- Identified by performance-oracle during PR #2910 review
Proposed Solutions
Option A: Pre-compute scenario polygons in setScenarioState() (Recommended)
private scenarioPolygons: GlobePolygon[] = [];
public setScenarioState(state: ScenarioVisualState | null): void {
this.scenarioState = state;
if (!state?.affectedIso2s?.length || !this.countriesGeoJsonData) {
this.scenarioPolygons = [];
} else {
const affected = new Set(state.affectedIso2s);
this.scenarioPolygons = this.countriesGeoJsonData.features
.filter(f => affected.has(f.properties?.['ISO3166-1-Alpha-2'] as string))
.map(f => ({ ...buildGlobePolygon(f), _kind: 'scenario' as const }));
}
this.flushPolygons();
}
Then in flushPolygons():
const polys = [...this.ciiPolygons, ...this.conflictPolygons, ...this.scenarioPolygons, ...];
(this.globe as any).polygonsData(polys);
Pros: O(1) in flushPolygons() for scenario layer, consistent with existing pattern for other polygon types
Cons: Slightly larger memory footprint (cached array)
Effort: Small | Risk: Low
Option B: Keep inline, add early-exit guard
if (!this.scenarioState?.affectedIso2s?.length) { /* skip scenario loop */ }
Reduces cost to near-zero when no scenario active, but still O(n) when active. Effort: Trivial | Risk: None (acceptable trade-off)
Recommended Action
Apply Option A if the inline loop is confirmed to run frequently (profile first). Option B is an acceptable minimal fix if profiling shows low impact.
Technical Details
- Affected files:
src/components/GlobeMap.ts - Add
private scenarioPolygons: GlobePolygon[] = [] - Move construction into
setScenarioState()
Acceptance Criteria
flushPolygons()does not iterate GeoJSON features when scenario state is unchangedsetScenarioState()pre-buildsscenarioPolygonsnpm run typecheckpasses
Work Log
- 2026-04-10: Identified by performance-oracle during PR #2910 review
Resources
- PR: #2910