feat(dashboard): lazy routes, Needs-you strip, URL state, exact numbers
- Workspace, SettingsHub, SessionDetail and the Workflow sub-tab are lazy (main bundle 2,166 kB -> ~920 kB), wrapped in RouteErrorBoundary: a stale chunk after a self-update reloads once (vite:preloadError too), any other error shows a Reload panel instead of a blank page. - Live refresh: lib/throttle.ts + hooks/useLiveRefresh.ts replace trailing debounces that froze under a steady event stream; refresh skips hidden tabs and refetches after a WebSocket reconnect. Monitor loads only while shown, with one batched GET /api/agents?session_ids= instead of N+1; a failing endpoint is an inline error, not a blank Dashboard. - NeedsYouStrip on Monitor and above Analytics. Tabs, filters, page, sort and search live in the URL (Dashboard, Sessions, Activity, SessionDetail). - Sessions: tool / workspace / date-range filters, CSV/JSON export (formula-neutralised), focusable row links, Open in workspace (the dead /run links are gone), stale responses dropped, error + Retry. - Fixes: "NaNs" durations, 30/7-day charts skipping idle days, weekday labels west of UTC (now averages), cache hit rate denominator, billable tokens vs cache reads, exact tool-call counts, "To" date = end of day, invalid ?from= no longer throws, recent list no longer shrinks to 15, stable message keys, transcript switch races. - Light-theme-unreadable bg-black code panels and dark-only DAG fills use tokens; status configs and chart colours use theme tokens; Health tab and other literals translated. - Server (additive): GET /api/agents?session_ids=, GET /api/sessions awaiting=1&from=&to=, stats tool_call_count/distinct_tool_count, event rows carry session/agent labels.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @file useLiveRefresh.ts
|
||||
* @description Keeps a page's data fresh without hammering the server: re-runs
|
||||
* `load` on matching WebSocket messages through a leading+trailing throttle (a
|
||||
* steady event stream can never freeze or flood the page), on a poll that skips
|
||||
* hidden tabs, when the tab becomes visible again, and after the socket
|
||||
* reconnects (messages sent while it was down are lost). The caller still does
|
||||
* its own initial load.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { eventBus } from "../lib/eventBus";
|
||||
import { throttle } from "../lib/throttle";
|
||||
import type { WSMessage } from "../lib/types";
|
||||
|
||||
export interface LiveRefreshOptions {
|
||||
/** Which WebSocket messages warrant a reload; none when omitted. */
|
||||
match?: (msg: WSMessage) => boolean;
|
||||
/** Poll period while the tab is visible; no poll when omitted. */
|
||||
intervalMs?: number;
|
||||
/** Minimum gap between event-driven reloads (default 3 s). */
|
||||
throttleMs?: number;
|
||||
/** Off = no subscription, no poll (e.g. an inactive tab). Default true. */
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export function useLiveRefresh(load: () => void, opts: LiveRefreshOptions = {}): void {
|
||||
const { intervalMs, throttleMs = 3000, enabled = true } = opts;
|
||||
const loadRef = useRef(load);
|
||||
const matchRef = useRef(opts.match);
|
||||
loadRef.current = load;
|
||||
matchRef.current = opts.match;
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
const live = throttle(() => loadRef.current(), throttleMs);
|
||||
const visible = () => document.visibilityState === "visible";
|
||||
const offMsg = eventBus.subscribe((msg) => {
|
||||
if (matchRef.current?.(msg)) live();
|
||||
});
|
||||
let wasDown = !eventBus.connected;
|
||||
// (Guarded: test doubles of the bus often stub `subscribe` only.)
|
||||
const offConn =
|
||||
typeof eventBus.onConnection === "function"
|
||||
? eventBus.onConnection((up) => {
|
||||
if (!up) wasDown = true;
|
||||
else if (wasDown) {
|
||||
wasDown = false;
|
||||
live();
|
||||
}
|
||||
})
|
||||
: () => undefined;
|
||||
const onVis = () => {
|
||||
if (visible()) live();
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVis);
|
||||
const timer = intervalMs
|
||||
? setInterval(() => {
|
||||
if (visible()) loadRef.current();
|
||||
}, intervalMs)
|
||||
: undefined;
|
||||
return () => {
|
||||
live.cancel();
|
||||
offMsg();
|
||||
offConn();
|
||||
document.removeEventListener("visibilitychange", onVis);
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [enabled, intervalMs, throttleMs]);
|
||||
}
|
||||
Reference in New Issue
Block a user