fix(web): stop Sidebar re-render loop from cache-event subscriber

setNow(Date.now()) was called on every query-cache event, including
events dispatched synchronously during ServerWorkspaceRows' render.
On CI, consecutive Date.now() calls cross millisecond boundaries so
each call returns a new value — React always re-renders Sidebar, which
re-renders the layout, which re-renders ServerWorkspaceRows, which
fires more cache events. After ~25 cycles React throws "Maximum update
depth exceeded."

Fix: remove setNow from the cache subscriber. setNow now fires only
from the 30s interval timer, where it correctly refreshes the "X ago"
display text without triggering a render loop.

Also adds a deterministic CI-repro test that mocks Date.now to return
incrementing values and asserts the filter-change path completes without
looping.
This commit is contained in:
Offending Commit
2026-06-03 18:11:30 -05:00
parent 3b88a41afd
commit 7e529c8c44
2 changed files with 23 additions and 2 deletions

View File

@@ -60,7 +60,9 @@ function useLastDataUpdate(): string {
useEffect(() => {
function refresh() {
setNow(Date.now());
// No setNow here — calling setNow on every cache event causes a render loop on
// CI (each Date.now() call crosses a ms boundary → new value → React re-renders
// Sidebar → cache events fire again → loop). setNow belongs only in the interval.
const latest = queryClient
.getQueryCache()
.getAll()
@@ -70,7 +72,10 @@ function useLastDataUpdate(): string {
refresh();
const unsubscribe = queryClient.getQueryCache().subscribe(refresh);
const interval = window.setInterval(refresh, 30_000);
const interval = window.setInterval(() => {
setNow(Date.now()); // refresh relative-time display ("X ago") every 30s
refresh();
}, 30_000);
return () => {
unsubscribe();
window.clearInterval(interval);