From 26d12d9fad4f3701cdb16d3ab77b872a3e0e3ccc Mon Sep 17 00:00:00 2001 From: Bergasha Date: Thu, 27 Aug 2026 09:07:05 +1000 Subject: [PATCH] Fix system resources widget overflow and initial chart flicker The chart card and its outer widget shell both had overflow:visible, letting chart lines bleed past their rounded borders into neighboring widgets. Compact-mode charts also stretched to fill the widget's full resized height instead of staying compact like other widgets. Additionally, the history used to build the chart needs 2+ points to render a line - it started empty and only got its first real point on the next poll (up to 30s later), so the chart would flash between a single-value fallback and the actual graph. Both the initial render and the first state update now seed with the current value duplicated, so a flat line renders immediately with no flicker. --- .../system-resources/chart/common-chart.tsx | 2 +- .../src/system-resources/component.tsx | 33 +++++++++++-------- packages/widgets/src/widget-card-shell.tsx | 1 - 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/widgets/src/system-resources/chart/common-chart.tsx b/packages/widgets/src/system-resources/chart/common-chart.tsx index 121cd1182..cc396f8e5 100644 --- a/packages/widgets/src/system-resources/chart/common-chart.tsx +++ b/packages/widgets/src/system-resources/chart/common-chart.tsx @@ -53,7 +53,7 @@ export const CommonChart = ({ ref={ref} h={"100%"} pos={"relative"} - style={{ overflow: "visible", border: `1px solid ${borderColor}` }} + style={{ overflow: "hidden", border: `1px solid ${borderColor}` }} p={0} bg={backgroundColor} radius={board.itemRadius} diff --git a/packages/widgets/src/system-resources/component.tsx b/packages/widgets/src/system-resources/component.tsx index f17b1cfab..6ef2f6419 100644 --- a/packages/widgets/src/system-resources/component.tsx +++ b/packages/widgets/src/system-resources/component.tsx @@ -110,14 +110,13 @@ export default function SystemResources({ lastUpdatedAt.current = dataUpdatedAt; setHistoryByIntegration((previous) => Object.fromEntries( - data.map((entry) => [ - entry.integrationId, - appendBoundedHistory( - previous[entry.integrationId] ?? [], - toChartItem(entry.healthInfo), - ADVANCED_HISTORY_SIZE, - ), - ]), + data.map((entry) => { + const currentItem = toChartItem(entry.healthInfo); + // Seed with the current item when there's no history yet, so the first real + // update lands at 2 points instead of dropping back to the single-value fallback. + const seed = previous[entry.integrationId] ?? [currentItem]; + return [entry.integrationId, appendBoundedHistory(seed, currentItem, ADVANCED_HISTORY_SIZE)]; + }), ), ); }, [dataUpdatedAt, data]); @@ -126,7 +125,9 @@ export default function SystemResources({ const panelWidth = width / panelColumns; const renderCharts = (entry: (typeof data)[number], availableWidth: number, availableHeight: number) => { const currentItem = toChartItem(entry.healthInfo); - const fullHistory = historyByIntegration[entry.integrationId] ?? [currentItem]; + // Duplicate the first point so the chart has two points to draw a line from + // immediately, instead of showing the single-value fallback until the next poll. + const fullHistory = historyByIntegration[entry.integrationId] ?? [currentItem, currentItem]; const history = isAdvanced ? fullHistory : fullHistory.slice(-COMPACT_HISTORY_SIZE); return ( @@ -225,20 +226,24 @@ const SystemCharts = ({ const showNetwork = visibleCharts.includes("network"); const labelDisplayMode = isAdvanced ? "textWithIcon" : options.labelDisplayMode; const chartCount = visibleCharts.length; - const chartColumns = isAdvanced && width >= 560 ? 2 : 1; + const chartColumns = isAdvanced ? (width >= 1100 ? 4 : width >= 560 ? 2 : 1) : 1; const compactChartGap = 8; + const compactRowHeight = 56; const chartHeight = isAdvanced - ? 180 - : `calc((100% - ${Math.max(0, chartCount - 1) * compactChartGap}px) / ${Math.max(1, chartCount)})`; + ? 110 + : Math.max( + 28, + Math.min(compactRowHeight, (height - Math.max(0, chartCount - 1) * compactChartGap) / Math.max(1, chartCount)), + ); return ( - + {showTitle && ( {integrationName} )} - + {chartCount === 0 && (
diff --git a/packages/widgets/src/widget-card-shell.tsx b/packages/widgets/src/widget-card-shell.tsx index a8843c5c4..c5b9f93f4 100644 --- a/packages/widgets/src/widget-card-shell.tsx +++ b/packages/widgets/src/widget-card-shell.tsx @@ -11,7 +11,6 @@ import type { BoardItemAdvancedOptions } from "@homarr/validation/shared"; import classes from "./widget-card-shell.module.css"; const getOverflow = (kind: WidgetKind, hasCustomCssClasses: boolean): CSSProperties => { - if (kind === "systemResources") return { overflowX: "visible", overflowY: "visible" }; if (kind === "iframe" || kind === "assistant") return { overflowX: "hidden", overflowY: "hidden" }; if (hasCustomCssClasses) return {}; return { overflowX: "hidden", overflowY: "auto" };