fix(status): slide the analytics window on refresh instead of re-polling a frozen range - #1464
Conversation
…ing a frozen range The [startTime, endTime] analytics window was computed once and baked into every panel's query key; refetchInterval then re-fetched that same fixed window forever, so charts never advanced and every tick was pure load on the customer's Harper while the freshness pill implied live data. A single clock in StatusTabsInner now slides the window forward each refresh period — skipping ticks while the browser tab is hidden (one catch-up tick on return), coalescing near-simultaneous refresh sources, and holding off while a fetch batch is still in flight. Manual refresh shares the same path. useAnalyticsRecords no longer polls: a fixed window is an immutable snapshot (staleTime Infinity, gcTime bounded to a minute so stranded snapshots don't accumulate), and keepPreviousData bridges the key swap. The now-unused refreshIntervalMs is removed from AnalyticsContext and its consumers. Fixes #1439 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review suggestion (gemini): with tick in the effect deps the interval is recreated after every refresh, so the next scheduled tick is always exactly refreshMs after the last actual refresh — the elapsed-time coalescing guard becomes unnecessary and a manual refresh no longer delays the next auto-refresh by up to two periods. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kriszyp
left a comment
There was a problem hiding this comment.
Approving — the sliding-window fix is sound and safe to merge. Replacing the frozen [startTime, endTime] window with a clock that actually advances on each refresh is the right fix, net-neutral on fetch volume (same one-fetch-per-panel-per-period cadence as before, now returning new data instead of re-polling an identical range), and the tick self-referencing effect dep is a clean way to keep the next scheduled tick exactly refreshMs after the last real refresh.
One follow-up worth a quick look, not blocking:
- The in-flight gate at
StatusTabs.tsx:54matches onqueryClient.isFetching({ queryKey: [ANALYTICS_QUERY_KEY_PREFIX] }), which is a prefix match — so any in-flight analytics query for any instance defers this tab's tick, not just this instance's. It's also untested: the regression suite mocksuseAnalyticsRecordsaway entirely, so the in-flight/dogpile guard — one of the three documented safety mechanisms — has zero assertions on it. Scoping the key byinstanceParams.entityIdplus one test with the hook left unmocked would close both gaps cheaply.
Two smaller suggestions, each easy: scope that same in-flight query key by instance (as above), and gcTime: 60_000 means a panel revisited after >60s pays a fresh loading spinner — intentional per the code comment, just flagging the visible UX cost.
Nice work on this one — the core mechanism is genuinely clean, and the "immutable window ⇒ immutable query key" property nicely sidesteps needing explicit request cancellation on window slides.
— Claude (Sonnet 5), reviewed via review-queue
Fixes #1439 — Status analytics auto-refresh re-polls a frozen time window.
Summary
The analytics
[startTime, endTime]window was computed once (Date.now()in a memo) and baked into every panel's react-query key;refetchIntervalthen re-fetched that same fixed window forever. Charts never advanced past their mount time, and each tick was pure load on the customer's Harper while the "Updated Xs ago" pill implied live data.Auto-refresh now works by sliding the window: a single clock in
StatusTabsInnerbumps the existingtickmechanism every refresh period, producing a new window (new query keys → one fresh fetch per panel,keepPreviousDatabridging the swap).useAnalyticsRecordsno longer polls at all — a fixed window is an immutable snapshot (staleTime: Infinity,gcTimebounded to 60s so stranded snapshots don't pile up). The now-deadrefreshIntervalMsis removed fromAnalyticsContextand its three consumers.The clock has three guards (each commented in the effect): hidden browser tabs pause ticking with one catch-up tick on return; an elapsed check coalesces near-simultaneous refresh sources (manual refresh / catch-up vs. scheduled tick); and ticks are skipped while a
get_analyticsbatch is still in flight so a slow Harper doesn't accumulate overlapping POST bursts.Where to look
StatusTabs.tsx— the interval/visibilitychange effect and its guards; this is the behavioral core.useAnalyticsRecords.ts— the no-polling model (staleTime/gcTimereasoning is in comments).__tests__/StatusTabs.sliding-window.test.tsx(window advances per tick,refresh=0never ticks, hidden-pause + single catch-up + coalescing).Open notes from cross-model review
get_analytics_rawcache prefix, so with two instances' Status pages open simultaneously, either page's in-flight batch defers the other's tick by one period. Conservative by design.Generated by kAIle (Claude Fable 5).