perf(cluster): throttle the grid refreshCells to 200ms (coalesce RBN status bursts)

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.
This commit is contained in:
2026-08-07 00:13:23 +02:00
parent 9a72afd467
commit 3cdf16b501
+14 -7
View File
@@ -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<number | undefined>(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.