Dashboard overhaul: 7 new analytics surfaces + 11 metric queries#147
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
Server-side analytics
11 new SQL queries in
src/orchestrator/metrics.ts, all exposed as/v1/*HTTP routes:listProjects/v1/projectsgetRunTrends/v1/trends/runsgetRunHeatmap/v1/trends/heatmapgetStorageGrowth/v1/trends/storagegetParallelismHistory/v1/trends/parallelismgetFlakiestTasks/v1/flakinessgetBottlenecks/v1/bottlenecksgetPrunableEntries/v1/cache/prunable7 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:
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:
singleFileVite plugin usedString.replace(re, "<script>...code...</script>")—$&,$`,$1in 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, () => ...).http://localhost:4321— broke the dashboard when served from any other port. Now defaults towindow.location.origin.formatBytesreturned"undefined"unit for fractional bytes (chart Y mid-ticks hit this constantly). Clampedi >= 0.Test plan
bun src/bin.ts run ci— full gate green (934 tests, +7 new)dist/vx-linux-x64in/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 originindex.html(35 KB gzipped)apps/ui/distremovedScreenshots
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