From 5d907b0449c7d5df940ae691885318a5b6f8a84c Mon Sep 17 00:00:00 2001 From: imagineer99 Date: Thu, 5 Mar 2026 21:54:27 +0000 Subject: [PATCH 1/3] fix: guard recharts ResponsiveContainer behind measured container dimensions --- studio/frontend/src/components/ui/chart.tsx | 116 ++++++++++++------ .../studio/sections/training-section.tsx | 2 +- 2 files changed, 81 insertions(+), 37 deletions(-) diff --git a/studio/frontend/src/components/ui/chart.tsx b/studio/frontend/src/components/ui/chart.tsx index 071148982f..341f0b8fc1 100644 --- a/studio/frontend/src/components/ui/chart.tsx +++ b/studio/frontend/src/components/ui/chart.tsx @@ -46,22 +46,66 @@ function ChartContainer({ }) { const uniqueId = React.useId(); const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; + const containerRef = React.useRef(null); + const [containerSize, setContainerSize] = React.useState<{ + width: number; + height: number; + } | null>(null); + + React.useEffect(() => { + const element = containerRef.current; + if (!element) return; + + const updateSizeState = () => { + const { width, height } = element.getBoundingClientRect(); + if (width > 0 && height > 0) { + setContainerSize({ + width: Math.round(width), + height: Math.round(height), + }); + return; + } + setContainerSize(null); + }; + + updateSizeState(); + + if (typeof ResizeObserver === "undefined") { + return; + } + + const observer = new ResizeObserver(() => { + updateSizeState(); + }); + observer.observe(element); + + return () => observer.disconnect(); + }, []); return (
- - {children} - + {containerSize ? ( + + {children} + + ) : null}
); @@ -100,30 +144,30 @@ ${colorConfig ); }; -const ChartTooltip = RechartsPrimitive.Tooltip; - -function ChartTooltipContent({ - active, - payload, - className, +const ChartTooltip = RechartsPrimitive.Tooltip; + +function ChartTooltipContent({ + active, + payload, + className, indicator = "dot", hideLabel = false, hideIndicator = false, label, labelFormatter, labelClassName, - formatter, - color, - nameKey, - labelKey, -}: Partial> & - React.ComponentProps<"div"> & { - hideLabel?: boolean; - hideIndicator?: boolean; - indicator?: "line" | "dot" | "dashed"; - nameKey?: string; - labelKey?: string; - }) { + formatter, + color, + nameKey, + labelKey, +}: Partial> & + React.ComponentProps<"div"> & { + hideLabel?: boolean; + hideIndicator?: boolean; + indicator?: "line" | "dot" | "dashed"; + nameKey?: string; + labelKey?: string; + }) { const { config } = useChart(); const tooltipLabel = React.useMemo(() => { @@ -248,20 +292,20 @@ function ChartTooltipContent({ ); } -const ChartLegend = RechartsPrimitive.Legend; - -function ChartLegendContent({ - className, - hideIcon = false, - payload, - verticalAlign = "bottom", - nameKey, -}: React.ComponentProps<"div"> & - Pick & { - hideIcon?: boolean; - nameKey?: string; - }) { - const { config } = useChart(); +const ChartLegend = RechartsPrimitive.Legend; + +function ChartLegendContent({ + className, + hideIcon = false, + payload, + verticalAlign = "bottom", + nameKey, +}: React.ComponentProps<"div"> & + Pick & { + hideIcon?: boolean; + nameKey?: string; + }) { + const { config } = useChart(); if (!payload?.length) { return null; diff --git a/studio/frontend/src/features/studio/sections/training-section.tsx b/studio/frontend/src/features/studio/sections/training-section.tsx index af6a16fc44..2f99bea2b7 100644 --- a/studio/frontend/src/features/studio/sections/training-section.tsx +++ b/studio/frontend/src/features/studio/sections/training-section.tsx @@ -105,7 +105,7 @@ export function TrainingSection() {
From 005e8ac67150ddc3f8068cbaf4c60f30467bc92c Mon Sep 17 00:00:00 2001 From: imagineer99 Date: Fri, 6 Mar 2026 06:06:50 +0000 Subject: [PATCH 2/3] fix: preserve chart sizing updates without ResizeObserver --- studio/frontend/src/components/ui/chart.tsx | 68 ++++++++++++++++++--- 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/studio/frontend/src/components/ui/chart.tsx b/studio/frontend/src/components/ui/chart.tsx index 341f0b8fc1..dd0dcee48a 100644 --- a/studio/frontend/src/components/ui/chart.tsx +++ b/studio/frontend/src/components/ui/chart.tsx @@ -58,20 +58,68 @@ function ChartContainer({ const updateSizeState = () => { const { width, height } = element.getBoundingClientRect(); - if (width > 0 && height > 0) { - setContainerSize({ - width: Math.round(width), - height: Math.round(height), - }); - return; - } - setContainerSize(null); + const nextSize = + width > 0 && height > 0 + ? { + width: Math.round(width), + height: Math.round(height), + } + : null; + + setContainerSize((currentSize) => { + if (!currentSize && !nextSize) { + return currentSize; + } + if ( + currentSize && + nextSize && + currentSize.width === nextSize.width && + currentSize.height === nextSize.height + ) { + return currentSize; + } + return nextSize; + }); + + return nextSize !== null; }; - updateSizeState(); + const hasInitialSize = updateSizeState(); if (typeof ResizeObserver === "undefined") { - return; + const handleWindowResize = () => { + updateSizeState(); + }; + + window.addEventListener("resize", handleWindowResize); + window.addEventListener("orientationchange", handleWindowResize); + + let retryId: number | null = null; + if (!hasInitialSize) { + let retries = 0; + const maxRetries = 40; + retryId = window.setInterval(() => { + const hasMeasuredSize = updateSizeState(); + retries += 1; + if (hasMeasuredSize && retryId !== null) { + window.clearInterval(retryId); + retryId = null; + return; + } + if (retries >= maxRetries && retryId !== null) { + window.clearInterval(retryId); + retryId = null; + } + }, 250); + } + + return () => { + window.removeEventListener("resize", handleWindowResize); + window.removeEventListener("orientationchange", handleWindowResize); + if (retryId !== null) { + window.clearInterval(retryId); + } + }; } const observer = new ResizeObserver(() => { From 15efb0f235a508ce94830e2e9154e506d9edb6bd Mon Sep 17 00:00:00 2001 From: imagineer99 Date: Fri, 6 Mar 2026 07:13:44 +0000 Subject: [PATCH 3/3] fix: harden chart container sizing with legacy event rechecks --- studio/frontend/src/components/ui/chart.tsx | 46 ++++++--------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/studio/frontend/src/components/ui/chart.tsx b/studio/frontend/src/components/ui/chart.tsx index dd0dcee48a..427cede136 100644 --- a/studio/frontend/src/components/ui/chart.tsx +++ b/studio/frontend/src/components/ui/chart.tsx @@ -67,12 +67,12 @@ function ChartContainer({ : null; setContainerSize((currentSize) => { - if (!currentSize && !nextSize) { + if (!nextSize) { + // Keep the last valid size once mounted to avoid unmount/remount thrash. return currentSize; } if ( currentSize && - nextSize && currentSize.width === nextSize.width && currentSize.height === nextSize.height ) { @@ -80,45 +80,25 @@ function ChartContainer({ } return nextSize; }); - - return nextSize !== null; }; - const hasInitialSize = updateSizeState(); + updateSizeState(); if (typeof ResizeObserver === "undefined") { - const handleWindowResize = () => { - updateSizeState(); + const recheckSize = () => { + if (document.visibilityState === "visible") { + updateSizeState(); + } }; - window.addEventListener("resize", handleWindowResize); - window.addEventListener("orientationchange", handleWindowResize); - - let retryId: number | null = null; - if (!hasInitialSize) { - let retries = 0; - const maxRetries = 40; - retryId = window.setInterval(() => { - const hasMeasuredSize = updateSizeState(); - retries += 1; - if (hasMeasuredSize && retryId !== null) { - window.clearInterval(retryId); - retryId = null; - return; - } - if (retries >= maxRetries && retryId !== null) { - window.clearInterval(retryId); - retryId = null; - } - }, 250); - } + window.addEventListener("resize", recheckSize); + window.addEventListener("orientationchange", recheckSize); + document.addEventListener("visibilitychange", recheckSize); return () => { - window.removeEventListener("resize", handleWindowResize); - window.removeEventListener("orientationchange", handleWindowResize); - if (retryId !== null) { - window.clearInterval(retryId); - } + window.removeEventListener("resize", recheckSize); + window.removeEventListener("orientationchange", recheckSize); + document.removeEventListener("visibilitychange", recheckSize); }; }