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:
@@ -14,7 +14,7 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { api } from "../../lib/api";
|
||||
import { eventBus } from "../../lib/eventBus";
|
||||
import { useLiveRefresh } from "../../hooks/useLiveRefresh";
|
||||
import type { PipelineStats } from "../../lib/types";
|
||||
|
||||
/** Percentage of `n` out of `total`, 0 when nothing was recorded. */
|
||||
@@ -26,7 +26,12 @@ function pct(n: number, total: number): number {
|
||||
* Per-node pass statistics for the selected pipeline template.
|
||||
* @returns The panel element, or an empty-state hint when nothing was recorded.
|
||||
*/
|
||||
export function LanePipelinePanel() {
|
||||
export function LanePipelinePanel({
|
||||
onAvailability,
|
||||
}: {
|
||||
/** Told whether any pipeline template is loaded (pipelines are dormant by default). */
|
||||
onAvailability?: (hasPipelines: boolean) => void;
|
||||
} = {}) {
|
||||
const { t } = useTranslation("workflows");
|
||||
const [data, setData] = useState<PipelineStats | null>(null);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
@@ -46,18 +51,14 @@ export function LanePipelinePanel() {
|
||||
void refresh();
|
||||
}, [refresh]);
|
||||
|
||||
// Stages move as agents declare them; debounce so a burst costs one fetch.
|
||||
// Stages move as agents declare them; throttled (not debounced) so a burst
|
||||
// costs one fetch and a steady stream still refreshes every 3 s.
|
||||
useLiveRefresh(() => void refresh(), { match: (msg) => msg.type === "lane_update" });
|
||||
|
||||
const hasPipelines = !!data && data.pipelines.length > 0;
|
||||
useEffect(() => {
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
const unsub = eventBus.subscribe(() => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => void refresh(), 3000);
|
||||
});
|
||||
return () => {
|
||||
unsub();
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [refresh]);
|
||||
if (data) onAvailability?.(hasPipelines);
|
||||
}, [data, hasPipelines, onAvailability]);
|
||||
|
||||
if (error) return <p className="text-xs text-status-danger">{error}</p>;
|
||||
if (!data) return null;
|
||||
@@ -71,7 +72,7 @@ export function LanePipelinePanel() {
|
||||
<select
|
||||
data-testid="pipeline-stats-lane-picker"
|
||||
aria-label={t("lanes.laneLabel")}
|
||||
className="max-w-[14rem] truncate rounded border border-border bg-surface-1 px-2 py-0.5 text-xs text-fg-secondary"
|
||||
className="select max-w-[14rem] truncate py-0.5 text-xs"
|
||||
value={laneId ?? ""}
|
||||
onChange={(e) => setLaneId(e.target.value ? Number(e.target.value) : null)}
|
||||
>
|
||||
@@ -113,7 +114,7 @@ export function LanePipelinePanel() {
|
||||
<select
|
||||
data-testid="pipeline-stats-picker"
|
||||
aria-label={t("lanes.pipelineLabel")}
|
||||
className="rounded border border-border bg-surface-1 px-2 py-0.5 text-xs text-fg-secondary"
|
||||
className="select w-auto py-0.5 text-xs"
|
||||
value={selected.id}
|
||||
onChange={(e) => setSelectedId(e.target.value)}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user