Skip to content

SCRUM-249 — AdminUI service detail charts

Plan ref: UI-4 (docs/11-admin-plane-plan.md). Stacked on SCRUM-248. Reads the Dashboard series endpoint from SCRUM-236.

What exists

Piece Notes
Route /dashboard/services/:name viewer; breadcrumb "Overview / "; overview cards link here
Range picker 15m · 1h · 6h · 24h · 7d in the URL (?range=6h, optional ?to=), so a link reproduces the view; back button works
Charts Traffic by status class, Latency p50/p95/p99, Error ratio, CPU, Memory, Go runtime (goroutines, heap in use, GC pause max as three small charts — the units cannot share an axis); each fetched concurrently, a failed one shows its own error + retry; "showing the last 7 days" when the server clamped
TimeSeriesChart.vue uPlot 1.6.32 (new dependency, lockfile hand-merged to avoid unrelated churn); nulls are gaps; colours from new --ds-chart-1..6 tokens (light/dark), re-read on theme change; setData on updates; debounced ResizeObserver; y-axis sized to its longest label; legend below the canvas
Polling every 30 s for ranges ≤ 6 h, paused on hidden tabs; none for 24 h / 7 d
Mock series for every template and range, honouring the 1500-point cap and clamped

How to verify

cd services/adminui
npm ci
npm run typecheck && npm run lint && npm run format:check && npx vitest run && npm run build
npx playwright test

Results at time of writing

  • typecheck, lint, prettier, vitest (273 tests), build: pass; Playwright 10/10 incl. the card → detail navigation, range in the URL surviving a reload, and the 1500 × 4 latency chart's first render ≈ 319 ms (budget 500 ms).
  • Screenshots checked in light and dark at 1440 px.

How it was built

DeepSeek run scoped (Landlock) to services/adminui (846 s, ~70k output tokens, reasoning effort low); Claude installed uPlot beforehand. Claude review (screenshots): every chart's legend was clipped by a fixed-height container (Traffic showed three of its four series) and y-axis labels such as "0.70%" were cut at the panel edge; the container now grows with the legend and the axis is sized to its labels.