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/dashboardruntime loader and datasource resolver. - Create
src/server/routes/agent-dashboard.ts:POST /api/agent/dashboardadapter forhandleAgentDashboardRequest. - 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$libaliases. - 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: ReactrenderToStringcomponent tests. - Replace
src/lib/ui/stories/*.stories.svelte: React.stories.tsxstories. - Modify
.storybook/main.ts: use@storybook/react-viteand 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$libtosrc/lib, build client, and keep Vitest config. - Modify
tsconfig.json: remove.svelte-kitinheritance, 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 keepbun build/index.jscommand. - Modify
README.md: document React runtime, Bun server, scripts, QA gate, deployment. - Remove
svelte.config.js,src/app.html,src/routes/**, and all.sveltefiles 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.tsxpasses: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, andhandleAgentDashboardRoute.