Skip to content

Commit c7d0603

Browse files
FayezBastkoala73
andauthored
fix(energy): wire energy variant atlas panels and renewable coverage (koala73#3445)
* fix(energy): wire energy variant atlas panels and renewable coverage * fix(settings): scope happy-only categories so energy variant doesn't render "Happy Planet" pill Two follow-ups from review of koala73#3445: - panels.ts: tag `happyNews` and `happyPlanet` with `variants: ['happy']`. ENERGY_PANELS now enables `renewable`, so without this the `categoryMatchesVariant` filter passed `happyPlanet` through (its `panelKeys` includes `renewable`) and energy users saw a "Happy Planet" category pill containing only the Renewable Energy toggle. - panel-layout.ts: drop the `&& !this.ctx.panels['renewable']` clause on the shared lazyPanel guard. `lazyPanel` writes `this.ctx.panels[key]` only after the dynamic-import promise resolves, so the check never protects against a synchronous double-call — and `lazyPanel` already calls `shouldCreatePanel(key)` internally. --------- Co-authored-by: Elie Habib <elie.habib@gmail.com>
1 parent bd237a2 commit c7d0603

11 files changed

Lines changed: 451 additions & 18 deletions

e2e/map-harness.spec.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ type OverlaySnapshot = {
1111

1212
type VisualScenarioSummary = {
1313
id: string;
14-
variant: 'both' | 'full' | 'tech' | 'finance';
14+
variant: 'both' | 'full' | 'tech' | 'finance' | 'energy';
1515
};
1616

1717
type HarnessWindow = Window & {
1818
__mapHarness?: {
1919
ready: boolean;
20-
variant: 'full' | 'tech' | 'finance';
20+
variant: 'full' | 'tech' | 'finance' | 'energy';
2121
seedAllDynamicData: () => void;
2222
setProtestsScenario: (scenario: 'alpha' | 'beta') => void;
2323
setPulseProtestsScenario: (
@@ -126,6 +126,27 @@ const EXPECTED_FINANCE_DECK_LAYERS = [
126126
'gulf-investments-layer',
127127
];
128128

129+
const EXPECTED_ENERGY_DECK_LAYERS = [
130+
'pipelines-layer',
131+
'storage-facilities-layer',
132+
'fuel-shortages-layer',
133+
'live-tankers-layer',
134+
'ais-density-layer',
135+
'ais-disruptions-layer',
136+
'commodity-hubs-layer',
137+
'commodity-ports-layer',
138+
'trade-routes-layer',
139+
'trade-chokepoints-layer',
140+
'waterways-layer',
141+
'weather-layer',
142+
'outages-layer',
143+
'earthquakes-layer',
144+
'natural-events-layer',
145+
'minerals-layer',
146+
'fires-layer',
147+
'climate-heatmap-layer',
148+
];
149+
129150
const waitForHarnessReady = async (
130151
page: import('@playwright/test').Page
131152
): Promise<void> => {
@@ -177,6 +198,8 @@ test.describe('DeckGL map harness', () => {
177198

178199
const expectedVariant = process.env.VITE_VARIANT === 'tech'
179200
? 'tech'
201+
: process.env.VITE_VARIANT === 'energy'
202+
? 'energy'
180203
: process.env.VITE_VARIANT === 'finance'
181204
? 'finance'
182205
: 'full';
@@ -232,6 +255,8 @@ test.describe('DeckGL map harness', () => {
232255

233256
const expectedDeckLayers = variant === 'tech'
234257
? EXPECTED_TECH_DECK_LAYERS
258+
: variant === 'energy'
259+
? EXPECTED_ENERGY_DECK_LAYERS
235260
: variant === 'finance'
236261
? EXPECTED_FINANCE_DECK_LAYERS
237262
: EXPECTED_FULL_DECK_LAYERS;
@@ -434,6 +459,10 @@ test.describe('DeckGL map harness', () => {
434459
const w = window as HarnessWindow;
435460
return w.__mapHarness?.variant ?? 'full';
436461
});
462+
// Energy currently reuses the shared "both" visual scenarios; until we
463+
// record Atlas-only golden scenes, compare those shared scenarios against
464+
// the existing full baselines rather than silently coercing the runtime.
465+
const screenshotVariant = variant === 'energy' ? 'full' : variant;
437466

438467
const scenarios = await page.evaluate(() => {
439468
const w = window as HarnessWindow;
@@ -449,7 +478,7 @@ test.describe('DeckGL map harness', () => {
449478
await test.step(`visual baseline: ${scenario.id}`, async () => {
450479
await prepareVisualScenario(page, scenario.id);
451480
await expect(mapWrapper).toHaveScreenshot(
452-
`layer-${variant}-${scenario.id}.png`,
481+
`layer-${screenshotVariant}-${scenario.id}.png`,
453482
{
454483
animations: 'disabled',
455484
caret: 'hide',

src/App.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,48 @@ export class App {
15691569
() => this.isPanelNearViewport('oil-inventories')
15701570
);
15711571

1572+
this.refreshScheduler.scheduleRefresh(
1573+
'pipeline-status',
1574+
() => (this.state.panels['pipeline-status'] as PipelineStatusPanel).fetchData(),
1575+
REFRESH_INTERVALS.pipelineStatus,
1576+
() => this.isPanelNearViewport('pipeline-status')
1577+
);
1578+
1579+
this.refreshScheduler.scheduleRefresh(
1580+
'storage-facility-map',
1581+
() => (this.state.panels['storage-facility-map'] as StorageFacilityMapPanel).fetchData(),
1582+
REFRESH_INTERVALS.storageFacilityMap,
1583+
() => this.isPanelNearViewport('storage-facility-map')
1584+
);
1585+
1586+
this.refreshScheduler.scheduleRefresh(
1587+
'fuel-shortages',
1588+
() => (this.state.panels['fuel-shortages'] as FuelShortagePanel).fetchData(),
1589+
REFRESH_INTERVALS.fuelShortages,
1590+
() => this.isPanelNearViewport('fuel-shortages')
1591+
);
1592+
1593+
this.refreshScheduler.scheduleRefresh(
1594+
'energy-disruptions',
1595+
() => (this.state.panels['energy-disruptions'] as EnergyDisruptionsPanel).fetchData(),
1596+
REFRESH_INTERVALS.energyDisruptions,
1597+
() => this.isPanelNearViewport('energy-disruptions')
1598+
);
1599+
1600+
this.refreshScheduler.scheduleRefresh(
1601+
'energy-risk-overview',
1602+
() => (this.state.panels['energy-risk-overview'] as EnergyRiskOverviewPanel).fetchData(),
1603+
REFRESH_INTERVALS.energyRiskOverview,
1604+
() => this.isPanelNearViewport('energy-risk-overview')
1605+
);
1606+
1607+
this.refreshScheduler.scheduleRefresh(
1608+
'chokepoint-strip',
1609+
() => (this.state.panels['chokepoint-strip'] as ChokepointStripPanel).fetchData(),
1610+
REFRESH_INTERVALS.chokepointStrip,
1611+
() => this.isPanelNearViewport('chokepoint-strip')
1612+
);
1613+
15721614
this.refreshScheduler.scheduleRefresh(
15731615
'climate-news',
15741616
() => (this.state.panels['climate-news'] as ClimateNewsPanel).fetchData(),

src/app/data-loader.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -501,12 +501,6 @@ export class DataLoaderManager implements AppModule {
501501
task: runGuarded('species', () => this.loadSpeciesData()),
502502
});
503503
}
504-
if (shouldLoad('renewable')) {
505-
tasks.push({
506-
name: 'renewable',
507-
task: runGuarded('renewable', () => this.loadRenewableData()),
508-
});
509-
}
510504
tasks.push({
511505
name: 'happinessMap',
512506
task: runGuarded('happinessMap', async () => {
@@ -523,6 +517,14 @@ export class DataLoaderManager implements AppModule {
523517
});
524518
}
525519

520+
// Renewable panel is shared by happy and energy variants.
521+
if (shouldLoad('renewable')) {
522+
tasks.push({
523+
name: 'renewable',
524+
task: runGuarded('renewable', () => this.loadRenewableData()),
525+
});
526+
}
527+
526528
if (shouldLoad('giving')) {
527529
tasks.push({
528530
name: 'giving',

src/app/panel-layout.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,6 +1330,10 @@ export class PanelLayoutManager implements AppModule {
13301330
}),
13311331
);
13321332

1333+
}
1334+
1335+
// Renewable Energy is shared by happy and energy variants.
1336+
if (this.shouldCreatePanel('renewable')) {
13331337
this.lazyPanel('renewable', () =>
13341338
import('@/components/RenewableEnergyPanel').then(m => {
13351339
const p = new m.RenewableEnergyPanel();

src/components/UnifiedSettings.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,13 +598,18 @@ export class UnifiedSettings {
598598
});
599599
}
600600

601+
private categoryMatchesVariant(catDef: { variants?: string[] }): boolean {
602+
return !catDef.variants || catDef.variants.includes(SITE_VARIANT);
603+
}
604+
601605
private getAvailablePanelCategories(): Array<{ key: string; label: string }> {
602606
const settings = this.config.getPanelSettings();
603607
const categories: Array<{ key: string; label: string }> = [
604608
{ key: 'all', label: t('header.sourceRegionAll') }
605609
];
606610

607611
for (const [catKey, catDef] of Object.entries(PANEL_CATEGORY_MAP)) {
612+
if (!this.categoryMatchesVariant(catDef)) continue;
608613
const hasEnabledPanel = catDef.panelKeys.some(pk => settings[pk]?.enabled);
609614
if (hasEnabledPanel) {
610615
categories.push({ key: catKey, label: t(catDef.labelKey) });
@@ -623,6 +628,9 @@ export class UnifiedSettings {
623628
if (this.activePanelCategory !== 'all') {
624629
const catDef = PANEL_CATEGORY_MAP[this.activePanelCategory];
625630
if (catDef) {
631+
if (!this.categoryMatchesVariant(catDef)) {
632+
return [];
633+
}
626634
const allowed = new Set(catDef.panelKeys);
627635
entries = entries.filter(([key]) => allowed.has(key));
628636
}

src/config/panels.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,10 +1325,12 @@ export const PANEL_CATEGORY_MAP: Record<string, { labelKey: string; panelKeys: s
13251325
happyNews: {
13261326
labelKey: 'header.panelCatHappyNews',
13271327
panelKeys: ['positive-feed', 'progress', 'counters', 'spotlight', 'breakthroughs', 'digest'],
1328+
variants: ['happy'],
13281329
},
13291330
happyPlanet: {
13301331
labelKey: 'header.panelCatHappyPlanet',
13311332
panelKeys: ['species', 'renewable', 'giving'],
1333+
variants: ['happy'],
13321334
},
13331335
};
13341336

src/config/variants/base.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ export const REFRESH_INTERVALS = {
5858
hormuzTracker: 60 * 60 * 1000, // 1h — data updates daily
5959
hyperliquidFlow: 5 * 60 * 1000, // 5min — matches Railway seed cadence
6060
energyCrisis: 6 * 60 * 60 * 1000, // 6h — policy data updates infrequently
61+
pipelineStatus: 24 * 60 * 60 * 1000, // curated registry reseeds weekly; daily poll keeps long-lived sessions fresh
62+
storageFacilityMap: 24 * 60 * 60 * 1000, // curated registry reseeds weekly; daily poll keeps long-lived sessions fresh
63+
fuelShortages: 60 * 60 * 1000, // active shortage alerts can change intra-day
64+
energyDisruptions: 60 * 60 * 1000, // disruption log is low-volume but needs intra-day freshness
65+
energyRiskOverview: 15 * 60 * 1000, // mixed market + supply-chain overview; refresh more often than the underlying weekly registries
66+
chokepointStrip: 90 * 60 * 1000, // matches chokepoint client cache TTL / freshness budget
6167
macroTiles: 30 * 60 * 1000,
6268
fsi: 30 * 60 * 1000,
6369
yieldCurve: 30 * 60 * 1000,

0 commit comments

Comments
 (0)