logicalWidth/viewportHeight can go non-positive for a narrow container
on a heavily zoomed-out board, where outerCardInset (which grows as
canvasScale shrinks) can exceed the container's own un-inset size.
That non-positive scale then feeds into a division (Infinity/NaN into
--board-canvas-ui-scale) and is used as zoom directly (invalid or
collapsed content) - flooring it well above zero avoids both.
Removing it in favor of viewportHeight for the vertical fit ratio left
the declaration itself dead code, which the CI lint step flags as an
error rather than a warning.
- Guard canvasScale before dividing by it in outerCardInset, matching
calculateBoardUiScale's own fallback.
- Fix containerContentScale's vertical fit ratio for scrollable
containers: it was derived from the full (all-rows) content height,
which drifts toward 1 as more rows are added regardless of the fixed
inset, under-scaling relative to what the visible viewport actually
needs and clipping the bottom of the visible rows. Use
viewportHeight/viewportRowCount instead, which describe the real
visible card size in both the scrollable and non-scrollable case.
A container's own visible card is inset from its allocated board cell
by the board's standard per-item gap, but items inside it are
positioned in a coordinate space sized to the un-inset column/row
count - so without correcting for that, items (not just the grid's
own box) spilled past the container's right/bottom edge by that
inset amount, worse the busier/more zoomed-out the board is.
Scales the container's inner grid content down to fit the actual card
size, using the same zoom mechanism the board's own top-level canvas
uses for its whole-board zoom, just one level deeper. Corrects
--board-canvas-ui-scale to compensate so icon/text/custom-CSS sizing
inside the container isn't affected - computed as a plain JS number
rather than a CSS calc() referencing the property's own inherited
value, since custom properties don't have a sequential/temporal order
within the same element and doing so is a circular reference that
silently invalidates the whole property for every descendant.
Also fixes the collapsible container's label bar poking above the
card's rounded top border via a negative offset - it now sits flush
inside it.
- Render the chart tooltip via a portal so it isn't clipped by the
card's overflow:hidden (needed to stop the chart line itself
bleeding past its rounded corners).
- Move chartColumns' width breakpoints into a lookup table and
memoize the computed value, instead of an inline ternary.
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.
Waiters that lost the refresh lock polled the shared cache for only 750ms
before falling through to the upstream, but the refresh lock lives for 15s.
On a cold cache with an upstream slower than 750ms, every waiting pod gave up
and stampeded the origin (each also writing L2). Extend the poll window to
match the lock TTL so waiters return the entry the holder publishes instead.
Adds a regression test that fails without the fix.
The User and Status columns used hardcoded pixel widths with table-layout:
fixed, so they stayed narrow regardless of how wide the widget actually
rendered - text truncated even with plenty of unused space in the
"currently playing" column. Switch both to percentages so they scale with
the widget.
Emby's integration also hardcoded playback/metadata to null for every
session, so the widget's existing progress bar, resolution, and bitrate
display never had anything to show. Port the same TranscodingInfo/
MediaStreams parsing Jellyfin already does (same underlying /Sessions API
shape) so Emby sessions get real position/duration, resolution, bitrate,
and transcode-status data. Also fix ticksToMs treating a position of
exactly 0 as missing, and share the one implementation between the two
integrations instead of duplicating it.
The WidgetTimeProvider plumbed a server timestamp and the container's
timezone down to useWidgetNow so clock widgets would paint a real time in
the SSR HTML instead of "--:--". That traded a cosmetic first-paint win for
a wrong clock: the seeded timezone is the server's, so anyone whose timezone
differs from the container's saw an incorrect time for the whole hydration
window. Fixing that properly needs a client-set timezone cookie read during
RSC render, which is far more plumbing than the problem warrants.
Revert the clock to release/v2 behaviour ("--:--" until hydration) and drop
the provider, the @homarr/widgets/time export and their tests. The e2e clock
assertions now wait on hydration explicitly instead of the seeded paint.
Also restore the widget.beszel.getSystemStats persistence exclusion. #6289
added it deliberately to keep those large time-series payloads out of the
persisted cache, and this branch had removed it with no stated rationale.
Query-cache persistence and the background-refresh indicator are unchanged.