Symptom
Every ~60s the dashboard appears to "reset": stat cards (LLM Calls, Total Cost, Cache Hit Rate, Avg Latency, Total Routed, Redirected, Kept Original, Unique Reasons) drop back to ... placeholders, charts become skeletons, and the Routing Reasons list collapses to skeleton bars. After the fetch finishes, values reappear. This makes the dashboard feel broken during every background refresh.
Reproduces on Overview and Routing tabs; also affects Sessions / Replay data loads.
Root cause
In every Tempo/Prometheus hook (dashboard/src/hooks/useTempo.ts, dashboard/src/hooks/usePrometheus.ts), the refetch pattern is:
const fetch_ = useCallback(async () => {
setLoading(true)
setError(null)
try {
// ...
setTraces(data.traces ?? [])
} catch (e) {
setError(...)
setTraces([])
} finally {
setLoading(false)
}
}, [query, timeRange, refreshKey])
Two issues:
setLoading(true) fires on every refetch (including the 60s auto-refresh), not just the initial load.
- On error,
setTraces([]) blows away the previously fetched data.
Consumers render empty/skeleton state whenever loading is true (App.tsx: const llmCallCount = tracesLoading ? null : traceRows.length), so every 60s the UI flashes through its empty state. Since several hooks (useTempoSearch x3 instances + usePrometheus*) refetch simultaneously on refreshKey bump from App.tsx:64-70, the whole page flashes together.
Proposed fix — stale-while-revalidate
Don't throw away data during background refetches:
- Split
loading into two flags:
loading — true only while we have no data yet (initial load)
refetching — true during any in-flight refetch after we already have data
- Never clear
traces/count on refetch start or on error — keep last-good data on screen.
- Consumers render skeletons only when
loading && !hasData; during refetching show a subtle indicator (maybe the existing refresh button spinner) but keep the cards.
- Optionally: expose
staleAt / refetchedAt so cards can show "last updated Xs ago" without forcing a loading state.
Affected files
dashboard/src/hooks/useTempo.ts — useTempoSearch, useTempoSearchCount, useSessionGraph
dashboard/src/hooks/usePrometheus.ts — both hooks
dashboard/src/App.tsx:88-165 — consumers using tracesLoading gates
- StatCards and any component doing
loading ? <Skeleton/> : <Data/> — should become (loading && !data) ? ...
Nice-to-have follow-ups
- Consider a tiny SWR-style cache keyed on
(query, timeRange) so tab switches don't force a full refetch either.
- Align refresh cadence with user activity (e.g. pause polling when tab is hidden via
document.visibilityState).
Discovered while debugging #174.
Symptom
Every ~60s the dashboard appears to "reset": stat cards (LLM Calls, Total Cost, Cache Hit Rate, Avg Latency, Total Routed, Redirected, Kept Original, Unique Reasons) drop back to
...placeholders, charts become skeletons, and the Routing Reasons list collapses to skeleton bars. After the fetch finishes, values reappear. This makes the dashboard feel broken during every background refresh.Reproduces on Overview and Routing tabs; also affects Sessions / Replay data loads.
Root cause
In every Tempo/Prometheus hook (
dashboard/src/hooks/useTempo.ts,dashboard/src/hooks/usePrometheus.ts), the refetch pattern is:Two issues:
setLoading(true)fires on every refetch (including the 60s auto-refresh), not just the initial load.setTraces([])blows away the previously fetched data.Consumers render empty/skeleton state whenever
loadingis true (App.tsx:const llmCallCount = tracesLoading ? null : traceRows.length), so every 60s the UI flashes through its empty state. Since several hooks (useTempoSearchx3 instances +usePrometheus*) refetch simultaneously onrefreshKeybump fromApp.tsx:64-70, the whole page flashes together.Proposed fix — stale-while-revalidate
Don't throw away data during background refetches:
loadinginto two flags:loading— true only while we have no data yet (initial load)refetching— true during any in-flight refetch after we already have datatraces/counton refetch start or on error — keep last-good data on screen.loading && !hasData; duringrefetchingshow a subtle indicator (maybe the existing refresh button spinner) but keep the cards.staleAt/refetchedAtso cards can show "last updated Xs ago" without forcing a loading state.Affected files
dashboard/src/hooks/useTempo.ts—useTempoSearch,useTempoSearchCount,useSessionGraphdashboard/src/hooks/usePrometheus.ts— both hooksdashboard/src/App.tsx:88-165— consumers usingtracesLoadinggatesloading ? <Skeleton/> : <Data/>— should become(loading && !data) ? ...Nice-to-have follow-ups
(query, timeRange)so tab switches don't force a full refetch either.document.visibilityState).Discovered while debugging #174.