From b3b5c15c821c69f082ff2477ad1f80cc002b2f9b Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Fri, 17 Jul 2026 15:14:05 +0200 Subject: [PATCH] ticker: also skip seeding display + reset it when value exceeds ceiling Follow-up to #1258. The initial fix rejected future ticks above the ceiling but left two paths for the garbage to stick: 1. useState was seeded with the raw value on mount, so the very first render painted 8.26e79 before any effect ran. The monotonic guard then rejected every healthy value that followed as a down tick. 2. Even after flushing lastRef, the display state still held the garbage, and the raf tick was returning early on lastRef=null so setDisplay was never called with a smaller-but-healthy value. Fix: guard the useState seed too, and reset display to undefined when an above-ceiling value arrives. A subsequent healthy push then seeds both lastRef and display fresh. --- src/components/live/live-number.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/live/live-number.tsx b/src/components/live/live-number.tsx index 1abb4927..6508076c 100644 --- a/src/components/live/live-number.tsx +++ b/src/components/live/live-number.tsx @@ -43,7 +43,14 @@ export function LiveNumber({ maxValue?: number; className?: string; }) { - const [display, setDisplay] = useState(value); + // Never SEED the display with a value that already exceeds the + // ceiling. Otherwise the very first render (before any useEffect runs) + // would paint the garbage, then the monotonic guard would reject + // every healthy value that follows as a "down tick" and the garbage + // would stick until the tab was closed. + const seed = + value == null || (maxValue != null && value > maxValue) ? undefined : value; + const [display, setDisplay] = useState(seed); const lastRef = useRef<{ value: number; ts: number } | null>(null); const prevRef = useRef<{ value: number; ts: number } | null>(null); const rafRef = useRef(null); @@ -51,13 +58,18 @@ export function LiveNumber({ useEffect(() => { if (value == null || !Number.isFinite(value)) return; if (maxValue != null && value > maxValue) { - // Also flush a poisoned lastRef so a healthy value can seed the - // pair on the next push instead of being rejected by the - // monotonic guard against the stale garbage. + // Flush a poisoned lastRef AND the displayed value so a healthy + // push can seed the ticker fresh on the next tick. Without + // resetting `display`, the monotonic guard below would keep + // rejecting the healthy value as smaller than the garbage still + // shown to the user. if (lastRef.current && lastRef.current.value > maxValue) { lastRef.current = null; prevRef.current = null; } + setDisplay((d) => + d != null && maxValue != null && d > maxValue ? undefined : d, + ); return; } const now = performance.now();