feat(web)!: migrate to SvelteKit 3 and Vite 8 with build-time DB driver#115

merged7
stack/sveltekit-2-70
stack/sveltekit-3
opened29d agobyOoliver
View on GitHubTwo-way sync enabledPR last imported 3d ago

Warning

🤖 Claude Opus 5.5 on behalf of Oliver. This moves the dashboard to a prerelease SvelteKit (3.0.0-next.27). That is intentional.

ELI5

This moves the dashboard to the next major version of its web framework. It also picks the database (a local SQLite file or Cloudflare D1) when the app is built, instead of on every request.

Why

Alchemy's SvelteKit support needs kit ≥ 3.0.0-next.27 and Vite 7 or 8. SvelteKit 3 also removes platform.env on Cloudflare, so D1 has to come from cloudflare:workers. That module can't be imported in a Node build, so the runtime platform?.env.DB check had to go.

Implementation

  • Versions:
    • SvelteKit 3.0.0-next.27 and adapter-cloudflare 8.0.0-next.7, pinned exactly.
    • Vite 8, vite-plugin-svelte 7, TypeScript 6 and vitest 5.
    • Node 24 in shell.nix and .node-version, because kit 3 needs Node ≥ 22.17 and nixpkgs only has 22.16. The Node version pins across the stack are reconciled in #117.
  • Config: svelte.config.js is gone. Its settings now live in sveltekit({...}) in vite.config.ts. $lib imports are now #lib subpath imports with file extensions, and env vars come from $app/env/private.
  • Build-time DB driver:
    • #db resolves to db/d1.ts under a custom atlantis-d1 condition, and to db/sqlite.ts otherwise.
    • ATLANTIS_DB_DRIVER is read only from the shell, never from .env. A stray .env value can't turn a deploy build into a SQLite worker.
    • vite build defaults to D1 and vite dev to SQLite. Routes no longer receive platform.
  • runed removed: URL state is read from page.url through the zod schema. All writes go through one updateSearch() called from event handlers, using goto(…, { reset: false }). The group-by clamp is a $derived, and URL sync no longer writes state from an effect.
  • Drill-down: Traffic Overview, the IP, protocol and spectrum breakdowns, Flow Characteristics and Unique Ports now each report a drill-down as one { groupBy, startDate, endDate } payload. The page applies it in a single updateSearch(). Previously the new group-by was sent before the new dates, so it was clamped against the old range.

Supported build and preview pairs

vite preview runs the server in Node, so it can only serve the SQLite build.

GoalCommandResult
Local dev on SQLitebun run dev:webVite dev server, no workerd
Local dev on D1ATLANTIS_DB_DRIVER=d1 bun run dev:webDev through the Workers runtime
Preview a production buildbun run --cwd apps/web previewAlways rebuilds with SQLite, then runs vite preview
Serve the deploy buildbun run build:web, then cd apps/web && bunx wrangler devD1 worker under wrangler

vite preview also ignores ATLANTIS_DB_DRIVER and always resolves SQLite. Running build:web and then preview used to load the Cloudflare bundle in Node and return 500. It now works.

Flows to exercise

Setup: a local SQLite dataset covering at least 90 days, with daily rows.

  1. Drill down from a wide daily range (Traffic Overview). Open /datasets/<id>?startDate=2025-01-01&endDate=2025-03-31&groupBy=date and click a daily point.
    • Expected: the URL becomes about a month around the clicked day with groupBy=hour.
    • Expected: the next /api/netflow/stats request has groupBy=hour.
    • Before this fix: the dates changed but groupBy stayed at date.
  2. Drill down in the IP, protocol or spectrum breakdown. Use the same URL and click a daily point.
    • Expected: groupBy=hour in the URL.
    • Expected: the next /api/ip/stats (or protocol/spectrum) request has granularity=1h.
  3. Clamping and history. Change the dates and the group-by, then pick a range longer than 62 days.
    • Expected: Day is forced and the finer options are disabled.
    • Expected: back and forward restore both the URL and the charts.
  4. Preview after a deploy build. Run bun run build:web, then bun run --cwd apps/web preview.
    • Expected: the dashboard serves local SQLite data. Before this fix it returned 500.
  5. Dev mode. Run bun run dev:web.
    • Expected: it reads local SQLite without starting workerd.

Edge cases and decisions

  • A URL with an invalid groupBy is no longer rewritten on load. The clamped value is shown, and the URL is corrected on the next filter change. The old effect pushed an extra history entry.
  • A start date that forces a clamp now sends one stats request instead of a stale request followed by a correct one.
  • A drill-down whose new range still allows the requested granularity keeps it. Clamping only applies when the target range is too wide.
  • preview always builds before serving, so it is slower than a bare vite preview. In exchange, it can't serve a stale or mismatched build.
  • The shadcn-svelte components.json still generates $lib imports. Fix them by hand when adding components.

Verification

Automated (all pass):

  • bun run format, lint, typecheck and test:web.
  • bun run test:e2e (8 tests). It includes the new dashboard-drilldown.spec.ts, which checks both the URL groupBy and the API granularity for Traffic Overview and the IP breakdown. Both tests fail without the fix.
  • The Playwright web server builds the default D1 worker first and then runs preview, so every e2e run covers build → preview. With the old preview script, all 8 tests fail.
  • build:web, which the e2e web server runs.

Checked manually on this branch before the fixes:

  • All 12 API responses are byte-identical to #114 on a production-sized dataset.
  • Playwright URL and history parity against #114.
  • The Cloudflare build contains no better-sqlite3, and the Node build contains no cloudflare:workers.
  • Local D1 under wrangler dev returns 200.

Remaining manual checks:

  • Drill-downs on the protocol and spectrum charts against real data.
  • wrangler dev against local D1 after build:web.

Claude Opus 5.5 · Claude Code (T3 Code)

Rebased onto the merged MAAD stack

  • $lib → #lib applied to all MAAD-stack files.
  • The SQLite driver uses the MAAD stack's schema gate, which skips obsolete databases. Both drivers return has_locality.
  • URL state (the runed replacement) keeps direction, ipVersion, measure and 10-minute grouping. The single drill-down callback also covers the MAAD Dimensions card.
  • The D1 driver interface is unchanged (four functions); the new MAAD queries are shared code. A new parity test runs 7 routes against both drivers, with D1 returning MAAD blobs as number[]. Breaking that decode fails 4 of the tests.
  • New e2e test for Dimensions drill-down. Local checks: 197 web tests and 17/17 e2e pass.

Checks

All checks passed

Added 7 commits

  • feat(web)!: migrate to SvelteKit 3 and Vite 8 with build-time DB driver1576b0d
  • refactor(web): replace runed with SvelteKit URL state3dffdaa
  • fix(web): build with SQLite before vite preview70bb59b
  • fix(web): apply dashboard drill-down grouping and dates in one URL updatebe736a3
  • test(web): check SQLite and D1 driver parity for MAAD routesf49f384
  • test(web): cover the MAAD dimensions drill-down in one URL update5d4d2c3
  • test(web): hoist the cloudflare:workers mock in the driver parity testb72af58