diff --git a/app/src/components/widgets/IntervalHeatmap.tsx b/app/src/components/widgets/IntervalHeatmap.tsx index 684d240..802a7fc 100644 --- a/app/src/components/widgets/IntervalHeatmap.tsx +++ b/app/src/components/widgets/IntervalHeatmap.tsx @@ -125,26 +125,35 @@ export function IntervalHeatmap({ } > - <> - {/* Peak-demand readout caption (#77): value + when. Hidden if no peak. */} - {peakReadout && ( -
- {peakReadout} + {/* #150 regression fix: `children` is evaluated EAGERLY by React when + IntervalHeatmap renders — BEFORE IntervalWidgetBody decides to show the + loading skeleton. During the loading tick useIntervalPayload returns + payload === null, so dereferencing payload.grid here would throw an + unguarded render-time TypeError and take the WHOLE dashboard down. Gate + the populated subtree on `payload &&` so children is falsy until the + payload exists; IntervalWidgetBody renders its skeleton/empty meanwhile. */} + {payload && ( + <> + {/* Peak-demand readout caption (#77): value + when. Hidden if no peak. */} + {peakReadout && ( +
+ {peakReadout} +
+ )} + {/* The grid fills the remaining height. HeatmapViz draws a scalable SVG + inside a box of the height we pass; in the fill cell we let it take + the flex remainder via a min-h-0 flex-1 wrapper. We pass the + SERVER-computed grid + rowLabels (no client re-aggregation). */} +
+
- )} - {/* The grid fills the remaining height. HeatmapViz draws a scalable SVG - inside a box of the height we pass; in the fill cell we let it take - the flex remainder via a min-h-0 flex-1 wrapper. We pass the - SERVER-computed grid + rowLabels (no client re-aggregation). */} -
- -
- + + )} ); };