Skip to content

Dashboard overhaul: 7 new analytics surfaces + 11 metric queries#147

Merged
Exelord merged 2 commits into
mainfrom
claude/ui-overhaul
Jun 23, 2026
Merged

Dashboard overhaul: 7 new analytics surfaces + 11 metric queries#147
Exelord merged 2 commits into
mainfrom
claude/ui-overhaul

Conversation

@Exelord

@Exelord Exelord commented Jun 21, 2026

Copy link
Copy Markdown
Member

Summary

User: "redo completely the UI. please use latest greatest UI patterns and deliver extremely good value, analytics of projects tasks cache, times, size, speed, bottlenecks everything possible".

A full UI redo on top of the embedded-SPA work in #146.

⚠️ Stacks on #146 (embed dashboard + drop "insights" naming). Will rebase cleanly when that merges.

Server-side analytics

11 new SQL queries in src/orchestrator/metrics.ts, all exposed as /v1/* HTTP routes:

Query Endpoint Purpose
listProjects /v1/projects per-project: tasks, runs, failures, hit rate, total time, cache bytes, est. time saved
getRunTrends /v1/trends/runs densified hour/day time-series of runs / hits / failures / duration
getRunHeatmap /v1/trends/heatmap 7×24 grid: when in the week you actually build
getStorageGrowth /v1/trends/storage daily bytes/entries added
getParallelismHistory /v1/trends/parallelism cpu_sum / wall_time per invocation
getFlakiestTasks /v1/flakiness failure rate + p99/p50 tail ratio composite rank
getBottlenecks /v1/bottlenecks "if you cut p50 25%, you'd save Xms/week" extrapolated
getPrunableEntries /v1/cache/prunable unused ≥ N days, largest first

7 new query tests (24 total in the file, all pass).

Client-side

App shell: sidebar nav, sticky topbar, breadcrumb, connection chip with live status dot, Cmd/Ctrl-K command palette that filters across navigation + projects + tasks.

4 new pages:

  • Projects — sortable leaderboard with horizontal-bar ranking visualization
  • ProjectDetail — per-project rollup + filtered task table
  • Bottlenecks — "where to invest" + flaky tasks + prunable entries side-by-side
  • Trends — runs/duration charts, when-you-build heatmap, storage growth, parallelism factor over time

Overview rewritten: 4-card hero (time saved, hit rate, failures, footprint), 24h activity chart with hover crosshair + tooltip, live SSE activity ticker, top time-burners + recent failures, cache treemap by project + project leaderboard, recent invocations.

Tasks / TaskDetail / Cache / RunDetail rewritten on the new design system: Card / MetricCard / EmptyState / StatusBadge / TrendDelta / Skeleton / StatusDot primitives.

Charts: Pure inline SVG, no chart library. LineChart (multi-series, hover crosshair, tooltip), Treemap (squarified layout), Heatmap, HBar, Sparkline.

Design tokens: 4-step surface gradient, 4-step text scale, semantic + 8-step categorical chart palette, custom scrollbar, focus rings, Inter typography with feature settings.

Bugs found by verification + fixed in this PR

Playwright caught three real bugs in my own work:

  1. CRITICAL. The singleFile Vite plugin used String.replace(re, "<script>...code...</script>")$&, $`, $1 in the bundled JS were interpreted as substitution patterns, splicing chunks of HTML into the script body. Every previous "embedded UI works" build (incl. vx serve --ui: bundle SPA, delete vx insights #145, Embed dashboard in the binary; drop "insights" naming #146) was actually shipping HTML the browser silently failed to mount. Curl tests passed because the bytes still contained "vx dashboard"; the script never ran. Fixed by switching to the function form .replace(re, () => ...).
  2. SPA connection default was hardcoded http://localhost:4321 — broke the dashboard when served from any other port. Now defaults to window.location.origin.
  3. formatBytes returned "undefined" unit for fractional bytes (chart Y mid-ticks hit this constantly). Clamped i >= 0.

Test plan

  • bun src/bin.ts run ci — full gate green (934 tests, +7 new)
  • Verified with Playwright against a compiled dist/vx-linux-x64 in /tmp/uitest3 (workspace with 2 runs): all 6 pages render, Cmd-K palette works, no console/page errors, embedded HTML mounts and connects to its own origin
  • SPA still builds as a single 128 KB self-contained index.html (35 KB gzipped)
  • Compiled binary serves embedded UI with apps/ui/dist removed

Screenshots

Six pages + the Cmd-K palette captured from a real Chromium against the compiled binary. Sent to the user with the verify report.


Generated by Claude Code

claude added 2 commits June 21, 2026 20:37
vx serve --ui failed from a compiled binary ("--ui requires
apps/insights/dist") because it resolved the SPA from disk. A binary
must be self-contained — so the dashboard is now embedded.

Embedding:
- apps/ui builds to a single self-contained dist/index.html (JS + CSS
  inlined via a small generateBundle plugin in vite.config.ts).
- src/cli/ui-asset.ts imports it with `with { type: 'file' }`, so
  `bun build --compile` embeds the bytes; the import resolves to a
  bunfs path Bun.file() reads. Dynamically imported only on --ui, so
  a source checkout that hasn't built the UI doesn't break `vx run`.
- vx serve --ui serves that one file for every non-API GET (the SPA
  is a hash router). Drops the old on-disk dir walk + VX_INSIGHTS_DIST.
- build.ui task (cd apps/ui && bun run build, boundary-free
  workspaceFiles I/O) builds the SPA; each build.bun.* depends on it
  so the binary embeds a fresh UI and a UI change cascades into the
  binary cache key. Same-project dep keeps test/CI unpolluted.

Verified: compiled a --minify --bytecode binary, removed apps/ui/dist
entirely, vx serve --ui still served the embedded dashboard.

Rename (insights → ui / metrics / dashboard):
- apps/insights → apps/ui; @vzn/vx-insights → @vzn/vx-ui
- src/orchestrator/insights-queries.ts → metrics.ts (+ test)
- guide insights.md → dashboard.md; brand, localStorage key, help,
  README, cli, landing, introduction copy
- VX_INSIGHTS_DIST removed (nothing on disk to point at)

module-boundaries test gains a .html asset carve-out (mirrors .json).
No CACHE_VERSION impact. Full gate green (925 tests).
A full UI redo on top of the embedded-SPA work. The user asked for
"latest greatest UI patterns and extreme value — analytics of projects,
tasks, cache, times, size, speed, bottlenecks, everything possible."

Server-side analytics (src/orchestrator/metrics.ts):
- listProjects  — per-project: tasks, runs, failures, hit rate, total
                  time, cache bytes, est. time saved
- getRunTrends  — densified time-series (hour/day buckets) of runs,
                  hits, failures, duration
- getRunHeatmap — 7×24 grid: when in the week you actually build
- getFlakiestTasks — failure rate + p99/p50 tail ratio composite rank
- getBottlenecks — "if you cut p50 25%, you'd save Xms/week" extrapol.
- getParallelismHistory — cpu_sum / wall_time per invocation
- getStorageGrowth — daily bytes/entries added (last N days)
- getPrunableEntries — unused ≥ N days, largest first
11 new /v1/* HTTP routes + 7 new metrics-query tests (24 total in the
file, all pass).

Client-side (apps/ui):
- App shell: sidebar nav, sticky topbar, breadcrumb, connection chip
  with live status dot, Cmd/Ctrl-K command palette (filtering across
  navigation / projects / tasks).
- 4 new pages: Projects (sortable leaderboard), ProjectDetail
  (per-project rollup + task table), Bottlenecks (where to invest +
  flaky + prunable side-by-side), Trends (run trends + duration +
  heatmap + storage + parallelism).
- Overview rewritten: 4-card hero, live SSE activity ticker, runs
  chart, top time-burners + recent failures, cache treemap + project
  leaderboard, recent invocations.
- Tasks / TaskDetail / Cache / RunDetail rewritten on the new design
  system (Card / MetricCard / EmptyState / StatusBadge / TrendDelta /
  Skeleton / StatusDot).
- New chart primitives in pure inline SVG (no chart lib):
  LineChart with hover crosshair + tooltip, Treemap (squarified
  layout), Heatmap, HBar, Sparkline.
- Refreshed design tokens: 4-step surface gradient, 4-step text scale,
  semantic + 8-step categorical chart palette, custom scrollbar,
  focus rings.

Bugs found by Playwright verification + fixed in this PR:
- CRITICAL: singleFile Vite plugin used String.replace(re, "<script>
  ... code ...</script>") which interprets $&, $`, $1 in the JS body
  as substitution patterns — splicing chunks of HTML INTO the script.
  Every previous "embedded UI works" build was actually shipping HTML
  the browser silently failed to mount. Fixed by switching to the
  function form .replace(re, () => ...).
- SPA connection default was hardcoded http://localhost:4321 — broke
  the dashboard when served from any other port. Now defaults to
  window.location.origin.
- formatBytes returned "undefined" unit for fractional bytes (chart
  Y mid-ticks hit this constantly). Clamped i >= 0.

Verified: real Chromium against compiled binary in a workspace with
2 runs — all 6 pages render, Cmd-K palette works, no console/page
errors, embedded HTML mounts and connects to its own origin. 934
tests pass.
@Exelord Exelord merged commit c409d73 into main Jun 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants