Skip to content

feat(dashboard): rewrite on React + Vite + TanStack stack#57

Merged
pratyush618 merged 42 commits intomasterfrom
feat/dashboard-next-scaffold
Apr 24, 2026
Merged

feat(dashboard): rewrite on React + Vite + TanStack stack#57
pratyush618 merged 42 commits intomasterfrom
feat/dashboard-next-scaffold

Conversation

@pratyush618
Copy link
Copy Markdown
Collaborator

Summary

Full rewrite of the web dashboard on a Vite + React + TanStack Router/Query + shadcn/ui + Tailwind v4 stack. Same Python entrypoint (`taskito dashboard --app myapp:queue`), zero Node runtime in production — the built SPA ships inside the wheel. Replaces the Preact single-file dashboard that preceded it.

What's new in the UI

  • Command palette (`⌘K`) via cmdk — nav + theme + refresh interval
  • URL-synced job filters — share a filtered view, back button works
  • Optimistic cancel / replay / pause / resume mutations with toast feedback
  • Recharts-backed metrics (throughput area + latency line), lazy-loaded so the main bundle stays at 179 kB gzipped
  • Virtualized live-tail logs via `@tanstack/react-virtual` with sticky auto-scroll and pause-on-scroll-up
  • Dead letters grouped by `(task, exception class)` extracted from the traceback — all `ValueError` variants collapse into one actionable group with a "Retry all" button
  • Type-to-confirm on destructive actions (DLQ purge)
  • Responsive shell — mobile sheet nav, breadcrumbs, error boundary, theme toggle (light/dark/system), last-refreshed indicator driven by `useIsFetching`
  • Keyboard-accessible DataTable rows (Enter/Space)
  • Defensive time formatters — null/NaN/0 render as `—` instead of crashing `Intl.RelativeTimeFormat`

Backend changes

  • `dashboard.py` serves a multi-file static tree at `py_src/taskito/static/dashboard/` with per-extension Content-Type, `Cache-Control: immutable` for hashed assets, and SPA fallback to `index.html` for client routes
  • Path traversal guards on the static server + 25 unit tests in `tests/python/test_dashboard_static.py`
  • Resource health now surfaces across process boundaries: when the dashboard runs in a different process than the worker, `/api/resources` aggregates `resource_health` snapshots from worker heartbeats (any unhealthy → unhealthy; all healthy → healthy; mixed → degraded)
  • Legacy single-file `templates/dashboard.html` path is gone; if assets aren't bundled the server returns a 503 with rebuild instructions
  • Fixed the universal timestamp-unit bug — backend is ms, frontend no longer ×1000 (was rendering dates as year 58282)

Architecture

  • `dashboard/src/routes/` — TanStack file-based routing (11 routes; metrics + logs are lazy chunks)
  • `dashboard/src/features//` — per-feature `api.ts` + `hooks.ts` + `components/` + `index.ts` barrel
  • `dashboard/src/components/ui/` — shadcn-style primitives (Button, Dialog, DataTable, Toaster, Command, etc.) with Radix behavior
  • `dashboard/src/lib/` — api-client, cn, time, number, status, api-types
  • Barrel imports everywhere externally (`@/components/ui`, `@/hooks`, `@/providers`, `@/features/*`); relative imports inside each barrel folder to avoid circular cycles

Bundle

Chunk Raw Gzipped Loaded
`index-*.js` 616 kB 187 kB every page
`metrics.lazy-*.js` 379 kB 111 kB only on /metrics
`logs.lazy-*.js` 20 kB 7 kB only on /logs
`index-*.css` 39 kB 8 kB every page

CI + pre-commit

  • Single `dashboard-lint` CI job running pnpm install (frozen-lockfile) + biome ci + tsc + vitest run + vite build
  • Pre-commit hooks cover the dashboard too — biome, tsc, and vitest all fire on `dashboard/src/` changes; ruff + mypy now explicitly target `py_src/` AND `tests/`

Test plan

  • `cargo check --workspace` passes
  • `uv run python -m pytest tests/python/ -v` passes (43 dashboard tests + existing suite)
  • `uv run ruff check py_src/ tests/` clean
  • `uv run mypy py_src/taskito/ tests/python/ --no-incremental` clean
  • `pnpm -C dashboard test` — 34/34 vitest
  • `pnpm -C dashboard typecheck` clean
  • `pnpm -C dashboard lint` — 0 errors, 0 warnings
  • `pnpm -C dashboard build` — lazy chunks produced
  • `pre-commit run --all-files` — 8/8 hooks pass
  • Manual walkthrough: seed demo (`test-junk/test1/demo.py`) → open all 11 screens → cancel/replay a job → pause/resume a queue → purge DLQ → verify timestamps, metrics charts, live-tailing logs

Notes for reviewers

  • Built `static/` assets are not committed (gitignored). CI + release tooling runs `pnpm -C dashboard build` before packaging so the wheel ships them. Fresh clones need `pnpm install && pnpm build` in `dashboard/` for the UI to appear.
  • Internally split into 33 focused commits — file-by-file history is preserved across the `dashboard-next/` → `dashboard/` rename.

Old Preact single-file dashboard is gone; the new React + Vite SPA
(formerly dashboard-next/) now lives at the canonical path. dashboard.py
drops the legacy templates/dashboard.html fallback and serves a 503 with
clear rebuild instructions if assets aren't bundled. Docs and CI updated.
- ruff/mypy now explicitly target py_src/ AND tests/ (tests/ was
  previously implicit via the .* glob — making it explicit ensures a
  regression in test code still trips the gate).
- Switched dashboard hooks from npx → pnpm exec so they use the pinned
  pnpm@10.30.3 toolchain consistently with CI.
- New dashboard-test hook runs vitest on any src/ change so the 34-test
  suite guards every commit touching the dashboard.
@github-advanced-security
Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

CI typecheck was failing with TS2345 errors across every route file
because src/routeTree.gen.ts is a build artifact the TanStack Router
Vite plugin emits during `vite build` — but tsc ran before build.
Without the file, every createFileRoute(...) collapsed to 'never' and
all the router-typed navigate calls failed.

Changes:
- Added @tanstack/router-cli devDep; new `pnpm routes` script invokes
  `tsr generate` standalone so typecheck doesn't depend on Vite.
- typecheck script now runs `tsr generate && tsc --noEmit`.
- CI has a dedicated 'Generate route tree' step before Biome/tsc/vitest.
- Renamed output to the CLI's default `routeTree.gen.ts` (camelCase);
  updated vite.config.ts + main.tsx + .gitignore to match.
- Typed an implicitly-any mouse handler in recent-jobs.tsx that was
  surfaced alongside the route errors.
The Python test matrix runs without pnpm, so py_src/taskito/static/
is empty and the new dashboard.py 503 fallback was firing on every
test_spa_html_served run in CI.

Rather than teaching the Python CI to run the Node build, keep the
tests self-contained: synthesise a minimal index.html in tmp_path and
monkeypatch the static-root cache. This also lets us explicitly cover
the 503 fallback with a dedicated test_spa_missing_assets_returns_503.

- test_spa_html_served: seeds a fake SPA, asserts 200 + HTML shell
- test_spa_missing_assets_returns_503: asserts 503 + rebuild hint in body

Both tests reset the module's memoised static root via monkeypatch so
they can't leak state to other tests.
…obals

The previous test fix reached into _-prefixed module globals
(_static_root, _static_root_resolved) via monkeypatch. That was
correct but coupled the tests to internal memoisation that could
shift under refactoring.

Replace with a small StaticAssets class:
- Encapsulates root + lookup + index-html resolution
- StaticAssets.from_package() does the lazy package probe
- Module-level _get_default_assets() memoises the package result
- _make_handler(queue, static_assets=...) takes an explicit instance
- serve_dashboard(..., static_assets=...) passes it through

Tests now construct StaticAssets(tmp_path) or StaticAssets(None)
directly — no monkeypatch, no internal-state coupling. The new
shape also lets embedders ship a customised dashboard bundle by
constructing their own StaticAssets, which the original design
couldn't accommodate.

Expanded SPA test coverage from 2 to 5 cases:
- bundled assets serve index.html
- hashed /assets/* resolve with immutable cache headers
- unknown route falls back to index for client-side routing
- missing /assets/* file returns 404 (never the index)
- no bundle returns 503 with rebuild hint
@pratyush618 pratyush618 merged commit 5571e98 into master Apr 24, 2026
15 checks passed
@pratyush618 pratyush618 deleted the feat/dashboard-next-scaffold branch April 24, 2026 22:14
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