Files
worldmonitor/todos/158-complete-p2-scenario-heat-layer-set-alloc-every-build.md
Elie Habib 60e727679c feat(supply-chain): Sprint E — scenario visual completion + service parity (#2910)
* 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
2026-04-10 21:31:26 +04:00

2.9 KiB

status, priority, issue_id, tags, dependencies
status priority issue_id tags dependencies
complete p2 158
code-review
performance
supply-chain
deckgl

createScenarioHeatLayer Allocates New Set on Every buildLayers() Call

Problem Statement

DeckGLMap.createScenarioHeatLayer() constructs new Set(this.scenarioState.affectedIso2s) inside the method body. buildLayers() is called on every frame/render cycle by DeckGL when layers need to be rebuilt. The Set allocation is O(n) on the number of affected ISO-2 codes and runs inside the hot DeckGL render path.

Findings

  • File: src/components/DeckGLMap.ts
  • Code:
    private createScenarioHeatLayer(): GeoJsonLayer | null {
      if (!this.scenarioState?.affectedIso2s?.length || !this.countriesGeoJsonData) return null;
      const affected = new Set(this.scenarioState.affectedIso2s);  // ← allocated every call
      return new GeoJsonLayer({ ..., getFillColor: (feature) => { const code = ...; return affected.has(code) ? ... } });
    }
    
  • buildLayers() is called whenever deck viewport or layers change — potentially dozens of times per second during pan/zoom
  • The Set contents only change when setScenarioState() is called (rare)
  • Identified by performance-oracle during PR #2910 review

Proposed Solutions

private affectedIso2Set: Set<string> = new Set();

public setScenarioState(state: ScenarioVisualState | null): void {
  this.scenarioState = state;
  this.affectedIso2Set = new Set(state?.affectedIso2s ?? []);
  this.rebuildLayers();
}

private createScenarioHeatLayer(): GeoJsonLayer | null {
  if (!this.affectedIso2Set.size || !this.countriesGeoJsonData) return null;
  return new GeoJsonLayer({ ..., getFillColor: (feature) => {
    const code = feature.properties?.['ISO3166-1-Alpha-2'] as string | undefined;
    return (code && this.affectedIso2Set.has(code) ? [220, 60, 40, 80] : [0, 0, 0, 0]) as [number,number,number,number];
  }});
}

Pros: Set allocated once per state change (not per render), correct updateTriggers still invalidates DeckGL cache Cons: Small memory overhead for the cached Set field Effort: Small | Risk: Low

Option B: Keep as-is with a comment

Acceptable if buildLayers() is only called on state change. But DeckGL calls it more often. Effort: None | Risk: High (performance regression on active globe interactions)

Apply Option A — cache the Set in setScenarioState().

Technical Details

  • Affected files: src/components/DeckGLMap.ts
  • Add private affectedIso2Set: Set<string> = new Set() field
  • Move Set construction to setScenarioState()

Acceptance Criteria

  • createScenarioHeatLayer does not allocate a new Set on each call
  • setScenarioState() rebuilds the cached Set
  • npm run typecheck passes

Work Log

  • 2026-04-10: Identified by performance-oracle during PR #2910 review

Resources

  • PR: #2910