dimensionlab-website/docs/superpowers/plans/2026-06-19-react-runtime-migration.md

19 KiB

React Runtime Migration Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Replace the SvelteKit dashboard app with a React-based Vite app and Bun production server while preserving model-driven dashboard behavior and reusable presentation boundaries.

Architecture: The browser runtime becomes React mounted from src/main.tsx. A Bun server at src/server/index.ts serves the static React build and exposes JSON API routes for dashboard reads and agent dashboard mutations. Existing model, persistence, datasource, and agent-config modules remain framework-agnostic TypeScript with import path updates as needed.

Tech Stack: Bun, Vite, React, React DOM, TypeScript, Tailwind CSS v4, shadcn/ui, Vitest, Storybook React Vite, Playwright, Drizzle ORM, Bun SQLite, MSW, uPlot, Iconify React.


File Structure

  • Create index.html: Vite app shell with <div id="root"></div> and /src/main.tsx.
  • Create src/main.tsx: React DOM bootstrap and global CSS import.
  • Create src/App.tsx: Dashboard fetch, refresh interval, ready/empty/loading/invalid rendering.
  • Create src/App.test.tsx: React server-rendering and hook behavior tests for dashboard states.
  • Create src/server/index.ts: Bun HTTP server, static asset serving, API router, production entry.
  • Create src/server/routes/dashboard.ts: GET /api/dashboard runtime loader and datasource resolver.
  • Create src/server/routes/agent-dashboard.ts: POST /api/agent/dashboard adapter for handleAgentDashboardRequest.
  • Create src/server/routes/dashboard.test.ts: dashboard API state and datasource-disable tests.
  • Create src/server/routes/agent-dashboard.test.ts: agent API delegation/auth tests.
  • Create src/lib/ui/components/*.tsx: React ports of current reusable Svelte components.
  • Create src/lib/ui/components/styles.css: component CSS migrated from Svelte style blocks.
  • Create components.json: shadcn/ui configuration for Vite, Radix, Nova, Tailwind v4, and $lib aliases.
  • Create src/lib/components/ui/*.tsx: selected shadcn primitives, not the full registry.
  • Create src/lib/utils.ts: cn() helper for shadcn and dashboard components.
  • Replace src/lib/ui/components/render.test.ts: React renderToString component tests.
  • Replace src/lib/ui/stories/*.stories.svelte: React .stories.tsx stories.
  • Modify .storybook/main.ts: use @storybook/react-vite and React story globs.
  • Modify .storybook/preview.ts: use React Storybook types and keep MSW setup/global CSS.
  • Modify vite.config.ts: use React and Tailwind plugins, alias $lib to src/lib, build client, and keep Vitest config.
  • Modify tsconfig.json: remove .svelte-kit inheritance, enable JSX, and define path aliases.
  • Modify package.json: swap Svelte/SvelteKit dependencies for React/Tailwind/shadcn tooling and update scripts.
  • Modify playwright.config.ts: build React client and Bun server before e2e.
  • Modify Containerfile: copy React/Bun build artifacts and keep bun build/index.js command.
  • Modify README.md: document React runtime, Bun server, scripts, QA gate, deployment.
  • Remove svelte.config.js, src/app.html, src/routes/**, and all .svelte files after replacements pass.

Task 1: React Toolchain And Typecheck Scaffold

Files:

  • Modify: package.json

  • Modify: bun.lock

  • Modify: tsconfig.json

  • Modify: vite.config.ts

  • Create: index.html

  • Create: src/main.tsx

  • Create: src/App.tsx

  • Create: src/App.test.tsx

  • Step 1: Write the failing React scaffold test

Create src/App.test.tsx:

import { renderToString } from "react-dom/server";
import { describe, expect, test } from "vitest";
import { AppStateView } from "./App";

describe("React app dashboard state view", () => {
  test("renders loading dashboard state", () => {
    const html = renderToString(
      <AppStateView
        dashboard={{
          state: "loading",
          title: "Loading Dashboard",
          subtitle: "Fetching active model",
          message: "Waiting for the active dashboard document.",
        }}
      />,
    );

    expect(html).toContain("Loading Dashboard");
    expect(html).toContain("Fetching active model");
  });
});
  • Step 2: Run the test to verify it fails

Run: bun run test:unit src/App.test.tsx

Expected: FAIL because React dependencies and src/App.tsx do not exist.

  • Step 3: Add React dependencies and scaffold files

Update package.json scripts and dependencies:

{
  "scripts": {
    "dev": "vite --host 0.0.0.0",
    "build": "vite build && bun build src/server/index.ts --target bun --outdir build",
    "preview": "HOST=0.0.0.0 PORT=4173 bun build/index.js",
    "storybook": "storybook dev -p 6006 --host 0.0.0.0",
    "build-storybook": "storybook build",
    "check": "tsc --noEmit",
    "test": "vitest run",
    "test:unit": "vitest run",
    "test:e2e": "env -u NO_COLOR playwright test",
    "test:qa": "bun run check && bun run test:unit && bun run build && bun run build-storybook && bun run test:e2e",
    "db:generate": "drizzle-kit generate",
    "db:check": "drizzle-kit check"
  }
}

Install React, Tailwind, and shadcn packages with Bun so bun.lock updates:

bun add @iconify/react @vitejs/plugin-react react react-dom
bun add class-variance-authority clsx lucide-react radix-ui tailwind-merge tw-animate-css @fontsource-variable/geist
bun add -d @storybook/react-vite @tailwindcss/vite @types/react @types/react-dom shadcn tailwindcss

Create index.html:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Dimension Lab</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Create minimal src/App.tsx:

import type { DashboardRuntimeState } from "$lib/server/dashboard";

export function AppStateView({ dashboard }: { dashboard: DashboardRuntimeState }) {
  if (dashboard.state === "ready") {
    return <main>{dashboard.document.metadata.title}</main>;
  }

  return (
    <main className="state-shell" data-dashboard-state={dashboard.state}>
      <h1>{dashboard.title}</h1>
      <p>{dashboard.subtitle}</p>
      <p>{dashboard.message}</p>
    </main>
  );
}

export default function App() {
  return (
    <AppStateView
      dashboard={{
        state: "loading",
        title: "Loading Dashboard",
        subtitle: "Fetching active model",
        message: "Waiting for the active dashboard document.",
      }}
    />
  );
}

Create src/main.tsx:

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./app.css";

const root = document.getElementById("root");
if (!root) throw new Error("Missing React root element");

createRoot(root).render(
  <StrictMode>
    <App />
  </StrictMode>,
);

Update tsconfig.json with React JSX and path aliases.

Update vite.config.ts to use @vitejs/plugin-react, @tailwindcss/vite, and the $lib alias. Initialize shadcn with:

bunx --bun shadcn@latest init --template vite --base radix --preset nova --yes --css-variables
bunx --bun shadcn@latest add badge card progress separator skeleton alert

Do not run bunx --bun shadcn@latest add --all; the dashboard should only vendor primitives it actually uses.

  • Step 4: Run checks for the scaffold

Run: bun run test:unit src/App.test.tsx && bun run check

Expected: PASS.

  • Step 5: Commit
git add package.json bun.lock tsconfig.json vite.config.ts index.html src/main.tsx src/App.tsx src/App.test.tsx
git commit -m "build: add react vite scaffold"

Task 2: Bun Server And Dashboard API

Files:

  • Create: src/server/index.ts

  • Create: src/server/routes/dashboard.ts

  • Create: src/server/routes/dashboard.test.ts

  • Create: src/server/routes/agent-dashboard.ts

  • Create: src/server/routes/agent-dashboard.test.ts

  • Modify: src/App.tsx

  • Modify: playwright.config.ts

  • Step 1: Write failing API route tests

Create src/server/routes/dashboard.test.ts:

import { describe, expect, test } from "vitest";
import { loadDashboardResponse } from "./dashboard";

describe("dashboard API route", () => {
  test("returns ready dashboard runtime state from the existing model loader", async () => {
    const response = await loadDashboardResponse({
      disableLiveDatasources: true,
      refreshSeedDocument: true,
      seedIfEmpty: true,
    });

    expect(response.state).toBe("ready");
    if (response.state !== "ready") throw new Error("expected ready dashboard");
    expect(response.document.metadata.title).toContain("Dimension Lab");
  });
});

Create src/server/routes/agent-dashboard.test.ts:

import { describe, expect, test } from "vitest";
import { handleAgentDashboardRoute } from "./agent-dashboard";

describe("agent dashboard API route", () => {
  test("delegates unauthorized requests to the existing agent handler", async () => {
    const response = await handleAgentDashboardRoute(
      new Request("http://localhost/api/agent/dashboard", { method: "POST" }),
    );

    expect(response.status).toBe(401);
  });
});
  • Step 2: Run tests to verify they fail

Run: bun run test:unit src/server/routes/dashboard.test.ts src/server/routes/agent-dashboard.test.ts

Expected: FAIL because the route modules do not exist.

  • Step 3: Implement server route modules and server entry

Implement loadDashboardResponse() by calling loadDashboardRuntime() and resolveDashboardDatasources() exactly like the current SvelteKit load function. Implement handleAgentDashboardRoute() by returning handleAgentDashboardRequest(request). Implement src/server/index.ts with Bun.serve routes for /api/dashboard, /api/agent/dashboard, static Vite assets, and SPA fallback to index.html.

  • Step 4: Update React app to fetch /api/dashboard

src/App.tsx should export AppStateView for tests and make the default App fetch dashboard state with useEffect. It should clear refresh timers when state changes and on unmount.

  • Step 5: Run route and app tests

Run:

bun run test:unit src/server/routes/dashboard.test.ts src/server/routes/agent-dashboard.test.ts src/App.test.tsx

Expected: PASS.

  • Step 6: Commit
git add src/server src/App.tsx src/App.test.tsx playwright.config.ts
git commit -m "feat(server): add bun dashboard api"

Task 3: React UI Component Library

Files:

  • Create: src/lib/ui/components/*.tsx

  • Create: src/lib/ui/components/styles.css

  • Modify: src/lib/ui/components/render.test.ts

  • Modify: src/lib/ui/index.ts

  • Keep: src/lib/ui/types.ts

  • Keep: src/lib/ui/model-renderer.ts

  • Keep: src/lib/ui/fixtures.ts

  • Step 1: Replace Svelte SSR tests with failing React render tests

Rewrite src/lib/ui/components/render.test.ts to import React components and renderToString from react-dom/server. Keep the current assertions for:

  • dashboard fixture content
  • generic secondary fixture content
  • optional service/status links
  • stable model IDs
  • progress bar behavior
  • uPlot chart surface marker
  • native attributes on Button and IconButton

Run: bun run test:unit src/lib/ui/components/render.test.ts

Expected: FAIL because React component files do not exist yet.

  • Step 2: Port atomic components

Create React equivalents for Badge, Button, IconGlyph, IconButton, ProgressMeter, Separator, Sparkline, SignalTrace, LineChart, and StatusBadge. Preserve class names and data-* attributes from Svelte.

Run: bun run test:unit src/lib/ui/components/render.test.ts

Expected: remaining FAILs only for dashboard composite components.

  • Step 3: Port layout and card components

Create React equivalents for Panel, CornerBracketFrame, GridFrame, DiagonalStripeField, ModuleCard, TelemetryCard, TelemetryGrid, FooterCell, FooterStatusCell, and StatusStrip.

Run: bun run test:unit src/lib/ui/components/render.test.ts

Expected: remaining FAILs only for service/dashboard shell components.

  • Step 4: Port service and dashboard shell components

Create React equivalents for ServiceRow, ServicePanel, ServiceGroupPanel, SystemState, DashboardHeader, DashboardFrame, TelemetryStrip, and WeatherModule.

Run: bun run test:unit src/lib/ui/components/render.test.ts

Expected: PASS.

  • Step 5: Export React components

Update src/lib/ui/index.ts to export .tsx React components and continue exporting fixtures, renderer, and UI types.

Run:

bun run test:unit src/lib/ui/components/render.test.ts src/lib/ui/model-renderer.test.ts src/lib/ui/content-boundary.test.ts

Expected: PASS.

  • Step 6: Commit
git add src/lib/ui/components src/lib/ui/index.ts
git commit -m "feat(ui): port dashboard components to react"

Task 4: React Dashboard App Rendering

Files:

  • Modify: src/App.tsx

  • Modify: src/App.test.tsx

  • Modify: src/app.css

  • Delete after src/page.test.tsx passes: src/routes/page.test.ts

  • Create: src/page.test.tsx

  • Step 1: Write failing React page tests

Create src/page.test.tsx with React renderToString assertions equivalent to the current Svelte src/routes/page.test.ts:

  • ready dashboard renders model content
  • invalid model state renders validation errors
  • empty and loading states render without crashing

Run: bun run test:unit src/page.test.tsx

Expected: FAIL until AppStateView uses the React DashboardFrame and SystemState components.

  • Step 2: Implement app state rendering

Use dashboardDocumentToUiDashboard() and DashboardFrame for ready state. Use SystemState for empty/loading/invalid states. Preserve state shell CSS and validation error list markup.

  • Step 3: Run page tests

Run: bun run test:unit src/page.test.tsx src/App.test.tsx

Expected: PASS.

  • Step 4: Commit
git add src/App.tsx src/App.test.tsx src/page.test.tsx src/app.css
git commit -m "feat(app): render dashboard with react"

Task 5: React Storybook

Files:

  • Modify: .storybook/main.ts

  • Modify: .storybook/preview.ts

  • Create: src/lib/ui/stories/*.stories.tsx

  • Delete after replacement: src/lib/ui/stories/*.stories.svelte

  • Delete after replacement: src/lib/ui/stories/FocusPreview.svelte

  • Modify: src/lib/ui/storybook.test.ts

  • Step 1: Update storybook boundary test first

Change src/lib/ui/storybook.test.ts so it requires React .stories.tsx files and rejects .stories.svelte files.

Run: bun run test:unit src/lib/ui/storybook.test.ts

Expected: FAIL while Svelte stories still exist.

  • Step 2: Configure React Storybook

Update .storybook/main.ts to use @storybook/react-vite, React story globs, and the existing addons. Update .storybook/preview.ts type imports to React Storybook while preserving global CSS, MSW setup, backgrounds, controls, and fullscreen layout.

  • Step 3: Port stories to React

Create .stories.tsx files for each existing Svelte story. Import React components from src/lib/ui and generic story data from src/lib/ui/stories/story-data.ts.

  • Step 4: Remove Svelte stories and run Storybook checks

Run:

bun run test:unit src/lib/ui/storybook.test.ts
bun run build-storybook

Expected: PASS.

  • Step 5: Commit
git add .storybook src/lib/ui/stories src/lib/ui/storybook.test.ts
git commit -m "feat(storybook): migrate stories to react"

Task 6: Remove SvelteKit Runtime

Files:

  • Delete: svelte.config.js

  • Delete: src/app.html

  • Delete: src/routes/**

  • Delete: all remaining *.svelte

  • Modify: package.json

  • Modify: bun.lock

  • Modify: README.md

  • Modify: Containerfile

  • Modify: playwright.config.ts

  • Modify: src/lib/presentation-boundary.test.ts

  • Step 1: Write/adjust cleanup tests

Add assertions to presentation or storybook boundary tests that no .svelte files remain under src/.

Run: bun run test:unit src/lib/presentation-boundary.test.ts src/lib/ui/storybook.test.ts

Expected: FAIL while Svelte files remain.

  • Step 2: Delete Svelte runtime and dependencies

Remove all Svelte files and Svelte dependencies. Run bun install --frozen-lockfile only after package.json and bun.lock are consistent, or run bun remove commands to update both together:

bun remove @iconify/svelte @storybook/addon-svelte-csf @storybook/sveltekit @sveltejs/adapter-node @sveltejs/kit @sveltejs/vite-plugin-svelte svelte svelte-check
  • Step 3: Update docs, container, and e2e build command

README should describe React, Vite, Bun server, and unchanged persistence. Containerfile should copy the Vite client output and Bun server output. playwright.config.ts should build and start the Bun server.

  • Step 4: Run cleanup checks

Run:

rg -n "\\.svelte|svelte" package.json src .storybook vite.config.ts tsconfig.json README.md Containerfile
bun run check
bun run test:unit

Expected: rg finds no Svelte app/runtime references except historical docs in the committed design/plan, and checks pass.

  • Step 5: Commit
git add -A
git commit -m "refactor: remove svelte runtime"

Task 7: QA Gate, PR, Review, And Merge

Files:

  • Modify only files needed to fix failures found by this task.

  • Step 1: Run full QA gate

Run: bun run test:qa

Expected: PASS for check, unit tests, production build, Storybook build, and Playwright desktop/mobile tests.

  • Step 2: Inspect current diff

Run:

git status --short
git diff --stat main...HEAD
git diff --name-only main...HEAD

Expected: only React migration files and docs changed.

  • Step 3: Push and open ready PR

Run:

git push -u origin codex/react-migration

Open a ready PR against main with title:

refactor: migrate dashboard runtime to react
  • Step 4: Independent review

Dispatch an independent reviewer to inspect the issue goal, spec, plan, and PR diff in code-review mode. Blocking findings must be fixed on the same branch.

  • Step 5: Fix review findings and re-run QA

For each blocking finding, write or update the relevant failing test first, make the minimal fix, and run the focused test plus bun run test:qa.

  • Step 6: Merge only after green checks and no blocking review findings

Merge the PR into main, sync the worktree back to main, and mark the goal complete only after the completion criteria in the spec are proven by current state.

Plan Self-Review

  • Spec coverage: Tasks cover React scaffold, Bun API server, UI component migration, app rendering, Storybook migration, Svelte removal, QA, PR, independent review, and merge.
  • Red-flag scan: The plan has no incomplete-work markers and no unspecified acceptance gates.
  • Type consistency: Public names used across tasks are AppStateView, loadDashboardResponse, and handleAgentDashboardRoute.