From 3cdf16b50190643ff523dbac1ae6524a3094064b Mon Sep 17 00:00:00 2001 From: Gregory Salaun Date: Fri, 7 Aug 2026 00:13:23 +0200 Subject: [PATCH] perf(cluster): throttle the grid refreshCells to 200ms (coalesce RBN status bursts) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spotStatus updates ~20x/second under an RBN firehose; firing a full refreshCells on each was pure churn on a slow PC. Coalesce into one refresh per 200ms. Keeps the NEW/WORKED badge colours and the 'represents nothing' dimming — the dimming itself is a trivial pure read now that the worked-index scan is cached, so it's no longer a cost worth removing. --- frontend/src/components/ClusterGrid.tsx | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/ClusterGrid.tsx b/frontend/src/components/ClusterGrid.tsx index 2a5995b..d00fd00 100644 --- a/frontend/src/components/ClusterGrid.tsx +++ b/frontend/src/components/ClusterGrid.tsx @@ -424,15 +424,22 @@ export function ClusterGrid({ rows, spotStatus, onSpotClick }: Props) { const context = useMemo(() => ({ spotStatus }), [spotStatus]); // Spot statuses arrive asynchronously (~after the rows render). The Call/Band/ - // Mode cellStyles depend on them but their cell VALUE doesn't change, so ag-grid - // won't re-render those cells on its own — force a refresh so e.g. a worked call - // turns blue once its status loads. + // Mode cellStyles and the dimmed "represents nothing" class depend on them but + // the cell VALUE doesn't change, so ag-grid won't re-render on its own — force a + // refresh so e.g. a worked call turns blue once its status loads. + // + // THROTTLED: under an RBN firehose spotStatus updates ~20×/second, and firing a + // full refreshCells that often is pure churn on a slow PC. Coalesce the bursts + // into one refresh every 200 ms (still imperceptible) instead of one per update. + const refreshPending = useRef(undefined); useEffect(() => { - // Light refresh so status-dependent styling — the Call/Band/Mode colours AND - // the dimmed "represents nothing" class (cellClassRules above) — re-applies - // once a status lands, WITHOUT redrawing whole rows (which pegged the CPU). - gridRef.current?.api?.refreshCells({ force: true }); + if (refreshPending.current !== undefined) return; // a refresh is already queued + refreshPending.current = window.setTimeout(() => { + refreshPending.current = undefined; + gridRef.current?.api?.refreshCells({ force: true }); + }, 200); }, [spotStatus]); + useEffect(() => () => { if (refreshPending.current !== undefined) window.clearTimeout(refreshPending.current); }, []); // Restore AFTER the profile scope is known — this grid has no key= remount to // save it from reading the wrong (unscoped) cache key at first paint.