diff --git a/docs/superpowers/plans/2026-06-19-react-runtime-migration.md b/docs/superpowers/plans/2026-06-19-react-runtime-migration.md new file mode 100644 index 0000000..c0a1a13 --- /dev/null +++ b/docs/superpowers/plans/2026-06-19-react-runtime-migration.md @@ -0,0 +1,579 @@ +# 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, Vitest, Storybook React Vite, Playwright, Drizzle ORM, Bun SQLite, MSW, uPlot, Iconify React. + +--- + +## File Structure + +- Create `index.html`: Vite app shell with `
` 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. +- 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 plugin, 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 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`: + +```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( + , + ); + + 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: + +```json +{ + "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 packages with Bun so `bun.lock` updates: + +```sh +bun add @iconify/react @vitejs/plugin-react react react-dom +bun add -d @storybook/react-vite @types/react @types/react-dom +``` + +Create `index.html`: + +```html + + + + + + Dimension Lab + + +
+ + + +``` + +Create minimal `src/App.tsx`: + +```tsx +import type { DashboardRuntimeState } from "$lib/server/dashboard"; + +export function AppStateView({ dashboard }: { dashboard: DashboardRuntimeState }) { + if (dashboard.state === "ready") { + return
{dashboard.document.metadata.title}
; + } + + return ( +
+

{dashboard.title}

+

{dashboard.subtitle}

+

{dashboard.message}

+
+ ); +} + +export default function App() { + return ( + + ); +} +``` + +Create `src/main.tsx`: + +```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( + + + , +); +``` + +Update `tsconfig.json` with React JSX and path aliases. + +Update `vite.config.ts` to use `@vitejs/plugin-react` and `$lib` alias. + +- [ ] **Step 4: Run checks for the scaffold** + +Run: `bun run test:unit src/App.test.tsx && bun run check` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```sh +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`: + +```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`: + +```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: + +```sh +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** + +```sh +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: + +```sh +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** + +```sh +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** + +```sh +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: + +```sh +bun run test:unit src/lib/ui/storybook.test.ts +bun run build-storybook +``` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```sh +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: + +```sh +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: + +```sh +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** + +```sh +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: + +```sh +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: + +```sh +git push -u origin codex/react-migration +``` + +Open a ready PR against `main` with title: + +```text +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`.