Two small pre-existing bugs in the infrastructure gauges.
Arc overdraw. Gauge computed strokeLength from the raw `progress` prop
while the label and the fill colour both used `progressWithinRange`,
the value clamped to 0-100. A reading above 100% therefore swept the
arc past a full circle - the offset (circumference - strokeLength)
went negative - while the label still read "100.0%". A negative
reading drew the arc backwards for the same reason. strokeLength now
uses the clamped value, so out-of-range input stops at exactly one
turn and matches what the label and colour already showed. Values in
range are unaffected.
The effect that animates the arc also listed `progress` in its
dependency array while only reading `strokeLength`, so it re-ran the
100ms animation on changes that could not alter the arc (105% to 110%,
say). Now that strokeLength is derived from the clamped value, the
stale dependency is dropped.
Disk key fallback. `disk?.device || 0 + idx` parses as
`disk?.device || (0 + idx)` because + binds tighter than ||, so the
fallback was just `idx` and the leading 0 did nothing. It happened to
produce unique keys, but by accident rather than intent, and a disk
whose device is the empty string collided with index 0. Replaced with
a `disk-${idx}` template so the intent is explicit.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
The gauge row on the infrastructure details page laid out memory, CPU
and one gauge per disk in a single flex row with no wrapping and no
overflow handling. Each gauge is capped at 260px, so on a host with
enough disks the row ran past the content column.
RootLayout sets overflow: "hidden" on that column, so the excess was
clipped outright rather than becoming scrollable - the later disks
rendered but could not be reached by any means.
Three changes, all scoped to this row:
flexWrap moves the overflow onto additional rows.
useFlexGap is required alongside it. MUI's Stack implements `spacing`
as `& > :not(style) ~ :not(style) { marginLeft }`, and that selector is
DOM-order based rather than line-aware, so the first card of each
wrapped row would carry a spurious 16px left margin and misalign
against the row above. useFlexGap switches to a real flex gap.
flexBasis on the children is what actually makes the line break.
BaseChart sets `flex: 1`, which expands to `flex: 1 1 0%`; wrapping is
decided by the sum of items' hypothetical main sizes, so with a zero
basis that sum is 0 and the row never overflows. The cards would
instead shrink to min-content (roughly the 70px gauge plus the widest
label) and stay on one cramped line. Giving each child a 260px basis
makes the row wrap at the intended card width.
The basis is applied through the row's own `& > *` selector rather than
by changing BaseChart, which has eight other consumers that default to
maxWidth: "100%" and would have had their layout altered.
The repeated 260 literals are now a named constant.
Only affects viewports at or above the md breakpoint; below it the
stack is already a column, where flexWrap is inert.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd