Fold the brain and health screens into Home

Home is the one overview now: the brain graph flat across the top, the flow
switches, then the health sections. The /brain and /health routes and their
sidebar entries are gone.

- charts report their cursor and clicks, so hovering one filters the list
  beside it to that minute and a click pins it until Escape or Clear
- chart values round to about three significant digits, the legend mounts
  under the plot so it can wrap without leaving the card, and axis ticks
  shorten past a thousand
- the embedded brain leaves the wheel to the page rather than zooming
- number fields no longer draw their up/down spinner (NOTEPAD)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017MeiWk3Yq12n2pTvnQWYvt
This commit is contained in:
2026-08-17 00:17:14 +02:00
co-authored by Claude Fable 5
parent fe346ea7a3
commit affb0a5f8c
15 changed files with 835 additions and 592 deletions
@@ -0,0 +1,195 @@
import { useQuery } from "@tanstack/react-query"
import { Link } from "@tanstack/react-router"
import type { FlowRollup, HistoryPoint } from "@/client"
import { shape } from "@/components/Flow/MessageSparkline"
import { PANEL_SECTION } from "@/components/Flow/SidePanel"
import { Badge } from "@/components/ui/badge"
import { compact } from "@/lib/utils"
import {
ago,
CARD,
failuresQueryOptions,
flowRollupsQueryOptions,
summaryQueryOptions,
} from "./queries"
function Tile({
label,
value,
note,
}: {
label: string
value: string
note?: string
}) {
return (
<div className={CARD}>
<div className={PANEL_SECTION}>{label}</div>
<div className="mt-1 text-2xl">{value}</div>
{note ? (
<div className="text-xs text-muted-foreground">{note}</div>
) : null}
</div>
)
}
/** A flow's execution trend, drawn from the 60 slices the rollup carries. */
function Spark({ counts }: { counts: number[] }) {
const points: HistoryPoint[] = counts.map((value, index) => ({
ts: index,
value,
}))
if (points.every((point) => point.value === 0)) {
return <span className="text-xs text-muted-foreground">nothing yet</span>
}
const { line } = shape(points)
return (
<svg
viewBox="0 0 100 100"
preserveAspectRatio="none"
className="h-6 w-24 overflow-visible"
aria-hidden="true"
>
<path
d={line}
fill="none"
stroke="var(--chart-1)"
strokeWidth="1.5"
vectorEffect="non-scaling-stroke"
/>
</svg>
)
}
/**
* How the engine is doing, and how each flow has been doing for a day.
*
* The tiles are the standing state; the table below is the same day the charts
* cover, one row per flow.
*/
export function HealthOverview() {
const { data: summary } = useQuery(summaryQueryOptions())
const { data: flows } = useQuery(flowRollupsQueryOptions())
// Shares the list below's cache entry, for the one tile that dates them.
const { data: failures } = useQuery(failuresQueryOptions())
const queue = (summary?.queue ?? {}) as Record<string, number>
const degraded = summary?.status === "degraded"
return (
<>
<section className="grid gap-3">
<div className="flex items-center gap-3">
<h2 className={PANEL_SECTION}>Health</h2>
<Badge variant={degraded ? "destructive" : "secondary"}>
{degraded ? "Degraded" : "Running normally"}
</Badge>
</div>
{summary?.problems.length ? (
<p className="text-sm text-muted-foreground">
{summary.problems.join(" · ")}
</p>
) : null}
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">
<Tile
label="Nodes"
value={String(summary?.nodes.total ?? 0)}
note={
summary?.nodes.error
? `${summary.nodes.error} failed to load`
: "all loaded"
}
/>
<Tile
label="Flows running"
value={`${summary?.flows.running ?? 0}/${summary?.flows.total ?? 0}`}
note={
summary?.flows.quarantined
? `${summary.flows.quarantined} quarantined`
: summary?.flows.paused
? `${summary.flows.paused} paused`
: "none paused"
}
/>
<Tile
label="Failures (24h)"
value={String(summary?.failures_24h ?? 0)}
note={
failures?.length
? `latest ${ago(failures[0].ts)}`
: "nothing recorded"
}
/>
<Tile
label="Queue in flight"
value={String(queue.pending ?? 0)}
note={`${queue.delayed ?? 0} waiting · ${queue.parked ?? 0} parked`}
/>
<Tile
label="Loop lag"
value={`${compact(summary?.loop_lag.ewma ?? 0)} ms`}
note={`peak ${compact(summary?.loop_lag.max_60s ?? 0)} ms in the last minute`}
/>
</div>
</section>
<section className="grid gap-3">
<h2 className={PANEL_SECTION}>Flow activity (24h)</h2>
<div className={`${CARD} overflow-x-auto`}>
<table className="w-full text-sm">
<thead className="text-xs text-muted-foreground">
<tr className="text-left">
<th className="pb-2 font-medium">Flow</th>
<th className="pb-2 font-medium">Executions</th>
<th className="pb-2 font-medium">Errors</th>
<th className="pb-2 font-medium">Avg</th>
<th className="pb-2 font-medium">Lag</th>
<th className="pb-2 font-medium">Trend</th>
</tr>
</thead>
<tbody>
{(flows ?? []).map((row: FlowRollup) => (
<tr key={row.flow} className="border-t border-border">
<td className="py-2">
<Link
to="/flows/$flowName"
params={{ flowName: row.flow }}
className="font-mono hover:underline"
>
{row.flow || "—"}
</Link>
</td>
<td className="py-2">{row.executions}</td>
<td className="py-2">
{row.errors ? (
<Badge variant="destructive">{row.errors} failed</Badge>
) : (
<span className="text-muted-foreground">none</span>
)}
</td>
<td className="py-2">{compact(row.avg_ms)} ms</td>
<td className="py-2">{compact(row.avg_lag_ms)} ms</td>
<td className="py-2">
<Spark counts={row.spark} />
</td>
</tr>
))}
{flows?.length === 0 ? (
<tr>
<td
colSpan={6}
className="py-6 text-center text-muted-foreground"
>
No flow has run in the last day.
</td>
</tr>
) : null}
</tbody>
</table>
</div>
</section>
</>
)
}