# Turbo Component Library 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:** Convert the single-package Dimension Lab website into a Turborepo workspace where `apps/web` consumes a compiled reusable React component library from `packages/ui`. **Architecture:** The root becomes a private Bun workspace with Turbo orchestration. `packages/ui` owns generic reusable UI components, CSS, theme helpers, Storybook, and package-level tests. `apps/web` owns the website runtime, model/server/database logic, the model-to-UI adapter, e2e tests, and deployment container. **Tech Stack:** Bun workspaces, Turborepo, Vite, React 19, TypeScript, Storybook React Vite, Tailwind CSS v4, shadcn CSS, Vitest, Playwright, Drizzle ORM, Bun SQLite. --- ## File Structure - Create root `turbo.json`: cacheable task graph for `build`, `check`, `test:unit`, `build-storybook`, `test:e2e`, and `test:qa`. - Create root `tsconfig.base.json`: shared strict TypeScript defaults. - Modify root `package.json`: private workspace root with `apps/*` and `packages/*`, Turbo scripts, and `turbo` dev dependency only. - Create `packages/ui/package.json`: compiled `@dimensionlab/ui` package with code and CSS exports. - Create `packages/ui/tsconfig.json` and `packages/ui/tsconfig.build.json`: package typecheck and declaration/JS build config. - Create `packages/ui/src/styles.css`: library style entry importing font, uPlot CSS, tokens, and component CSS. - Move `src/lib/ui/components/**` to `packages/ui/src/components/**`. - Move `src/lib/ui/stories/**` to `packages/ui/src/stories/**`. - Move `src/lib/ui/tokens.css` to `packages/ui/src/tokens.css`. - Move `src/lib/ui/theme.ts`, `types.ts`, `format.ts`, `fixtures.ts`, and `index.ts` to `packages/ui/src/**`. - Move `src/lib/ui/components/styles.css` to `packages/ui/src/components/styles.css`. - Move `.storybook/**` to `packages/ui/.storybook/**`. - Move unused shadcn primitives from `src/lib/components/ui/**` to `packages/ui/src/primitives/**` and update their `cn` import to package-local `src/utils.ts`. - Move `src/lib/utils.ts` to `packages/ui/src/utils.ts`. - Move app runtime files into `apps/web`: `src/App.tsx`, `src/main.tsx`, `src/app.css`, `src/server/**`, `src/lib/model/**`, `src/lib/server/**`, `src/lib/testing/**`, `src/vite-env.d.ts`, `src/page.test.tsx`, `src/server/dev.ts`, `tests/**`, `drizzle/**`, `Containerfile`, `index.html`, `playwright.config.ts`, `vite.config.ts`, `drizzle.config.ts`, and app-specific README/deployment files. - Move `src/lib/ui/model-renderer.ts` and `model-renderer.test.ts` into `apps/web/src/lib/ui-adapter/**`. - Create `apps/web/package.json`, `apps/web/tsconfig.json`, `apps/web/vite.config.ts`, and `apps/web/playwright.config.ts`. - Update `apps/web/src/App.tsx` to import components/types from `@dimensionlab/ui` and import the adapter from `$lib/ui-adapter/model-renderer`. - Update `apps/web/src/app.css` to import `@dimensionlab/ui/styles.css` instead of local UI CSS files. - Update tests that read paths so package boundary tests inspect `packages/ui` and app tests inspect `apps/web`. - Update `README.md` to describe the workspace commands, package boundaries, Storybook location, and deployment path. ## Task 1: Workspace And Boundary Tests **Files:** - Modify: `package.json` - Create: `turbo.json` - Create: `tsconfig.base.json` - Create: `packages/ui/package.json` - Create: `packages/ui/tsconfig.json` - Create: `packages/ui/tsconfig.build.json` - Create: `apps/web/package.json` - Create: `apps/web/tsconfig.json` - Create: `apps/web/src/lib/workspace-boundary.test.ts` - [ ] **Step 1: Write the failing workspace boundary test** Create `apps/web/src/lib/workspace-boundary.test.ts`: ```ts import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, test } from "vitest"; const root = join(import.meta.dir, "../../../.."); describe("workspace boundaries", () => { test("declares the root as a turbo-managed bun workspace", () => { const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { private?: boolean; scripts?: Record; workspaces?: string[]; }; expect(packageJson.private).toBe(true); expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]); expect(packageJson.scripts?.build).toBe("turbo build"); expect(existsSync(join(root, "turbo.json"))).toBe(true); }); test("keeps the website app and reusable UI library as separate packages", () => { const webPackage = JSON.parse( readFileSync(join(root, "apps/web/package.json"), "utf8"), ) as { dependencies?: Record; name?: string }; const uiPackage = JSON.parse( readFileSync(join(root, "packages/ui/package.json"), "utf8"), ) as { exports?: Record; name?: string }; expect(webPackage.name).toBe("@dimensionlab/web"); expect(webPackage.dependencies?.["@dimensionlab/ui"]).toBe("workspace:*"); expect(uiPackage.name).toBe("@dimensionlab/ui"); expect(uiPackage.exports).toHaveProperty("."); expect(uiPackage.exports).toHaveProperty("./styles.css"); }); }); ``` - [ ] **Step 2: Run the test to verify it fails** Run: ```sh bunx vitest run apps/web/src/lib/workspace-boundary.test.ts ``` Expected: FAIL because `apps/web`, `packages/ui`, and `turbo.json` do not exist. - [ ] **Step 3: Add minimal workspace manifests** Create root `package.json` as the workspace orchestrator: ```json { "name": "dimensionlab", "version": "0.0.1", "private": true, "type": "module", "packageManager": "bun@1.3.14", "workspaces": ["apps/*", "packages/*"], "scripts": { "dev": "turbo dev --filter=@dimensionlab/web", "build": "turbo build", "preview": "bun run --cwd apps/web preview", "storybook": "turbo storybook --filter=@dimensionlab/ui", "build-storybook": "turbo build-storybook --filter=@dimensionlab/ui", "check": "turbo check", "test": "turbo test:unit", "test:unit": "turbo test:unit", "test:e2e": "turbo test:e2e --filter=@dimensionlab/web", "test:qa": "turbo test:qa", "db:generate": "bun run --cwd apps/web db:generate", "db:check": "bun run --cwd apps/web db:check" }, "devDependencies": { "turbo": "^2.5.0" } } ``` Create `turbo.json`: ```json { "$schema": "https://turbo.build/schema.json", "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", "build/**"] }, "check": { "dependsOn": ["^build"], "outputs": [] }, "test:unit": { "dependsOn": ["^build"], "outputs": [] }, "build-storybook": { "dependsOn": ["^build"], "outputs": ["storybook-static/**"] }, "test:e2e": { "dependsOn": ["build", "^build"], "outputs": ["test-results/**", "playwright-report/**"] }, "test:qa": { "dependsOn": ["check", "test:unit", "build", "build-storybook", "test:e2e"], "outputs": [] }, "dev": { "cache": false, "persistent": true }, "storybook": { "cache": false, "persistent": true } } } ``` Create minimal `packages/ui/package.json` with `@dimensionlab/ui` exports and minimal `apps/web/package.json` with `@dimensionlab/ui` as a workspace dependency. Move the full dependency lists in Task 2 and Task 3. - [ ] **Step 4: Run the boundary test** Run: ```sh bunx vitest run apps/web/src/lib/workspace-boundary.test.ts ``` Expected: PASS. - [ ] **Step 5: Commit** ```sh git add package.json turbo.json tsconfig.base.json apps/web/package.json apps/web/tsconfig.json apps/web/src/lib/workspace-boundary.test.ts packages/ui/package.json packages/ui/tsconfig.json packages/ui/tsconfig.build.json git commit -m "build: add turbo workspace manifests" ``` ## Task 2: Extract The UI Package **Files:** - Move: `src/lib/ui/components/**` to `packages/ui/src/components/**` - Move: `src/lib/ui/stories/**` to `packages/ui/src/stories/**` - Move: `src/lib/ui/{index.ts,types.ts,theme.ts,format.ts,fixtures.ts,tokens.css}` to `packages/ui/src/**` - Move: `.storybook/**` to `packages/ui/.storybook/**` - Move: `src/lib/components/ui/**` to `packages/ui/src/primitives/**` - Move: `src/lib/utils.ts` to `packages/ui/src/utils.ts` - Create: `packages/ui/src/styles.css` - Modify: `packages/ui/src/index.ts` - Modify: `packages/ui/src/storybook.test.ts` - Modify: `packages/ui/src/content-boundary.test.ts` - [ ] **Step 1: Write the failing UI isolation assertion** Update the package boundary tests to read from `packages/ui/src` and assert no imports from `apps/web`, `$lib/server`, or `$lib/model` exist: ```ts expect(source).not.toMatch(/from ["'](?:apps\/web|\$lib\/server|\$lib\/model)/); ``` Run: ```sh bunx vitest run packages/ui/src/content-boundary.test.ts ``` Expected: FAIL until the files move and the app-specific adapter is removed. - [ ] **Step 2: Move generic UI files** Run mechanical moves with `git mv`. Move `model-renderer.ts` out of the UI package in Task 3 rather than into `packages/ui`. - [ ] **Step 3: Add the UI style entry** Create `packages/ui/src/styles.css`: ```css @import "@fontsource-variable/geist"; @import "uplot/dist/uPlot.min.css"; @import "./tokens.css"; @import "./components/styles.css"; ``` - [ ] **Step 4: Make package imports relative or package-local** Update moved primitive files to import `cn` from `../utils`. Remove `dashboardDocumentToUiDashboard` from `packages/ui/src/index.ts`. - [ ] **Step 5: Build the package** Run: ```sh bun run --cwd packages/ui build bun run --cwd packages/ui check bun run --cwd packages/ui test:unit ``` Expected: PASS and `packages/ui/dist` contains `index.js`, `index.d.ts`, and CSS files. - [ ] **Step 6: Commit** ```sh git add packages/ui src/lib/ui src/lib/components src/lib/utils.ts git commit -m "refactor(ui): extract reusable component package" ``` ## Task 3: Move The Web App Workspace **Files:** - Move: `src/**` app files that are not reusable UI to `apps/web/src/**` - Move: `tests/**` to `apps/web/tests/**` - Move: `drizzle/**` to `apps/web/drizzle/**` - Move: `Containerfile` to `apps/web/Containerfile` - Move: `index.html`, `vite.config.ts`, `playwright.config.ts`, `drizzle.config.ts` to `apps/web/**` - Create: `apps/web/src/lib/ui-adapter/model-renderer.ts` - Modify: `apps/web/src/App.tsx` - Modify: `apps/web/src/app.css` - [ ] **Step 1: Move the model adapter out of UI** Move `src/lib/ui/model-renderer.ts` to `apps/web/src/lib/ui-adapter/model-renderer.ts` and update imports from `$lib/model` plus UI types from `@dimensionlab/ui`. - [ ] **Step 2: Move app runtime and tests** Use `git mv` for app/server/model/test/deployment files into `apps/web`. - [ ] **Step 3: Update app imports** In `apps/web/src/App.tsx`, import UI components and types from `@dimensionlab/ui`, and import `dashboardDocumentToUiDashboard` from `$lib/ui-adapter/model-renderer`. In `apps/web/src/app.css`, replace local UI imports with: ```css @import "tailwindcss"; @import "tw-animate-css"; @import "shadcn/tailwind.css"; @import "@dimensionlab/ui/styles.css"; ``` - [ ] **Step 4: Run app typecheck and unit tests** Run: ```sh bun run --cwd apps/web check bun run --cwd apps/web test:unit ``` Expected: PASS. - [ ] **Step 5: Commit** ```sh git add apps/web src tests drizzle Containerfile index.html vite.config.ts playwright.config.ts drizzle.config.ts git commit -m "refactor(web): move website into app workspace" ``` ## Task 4: Wire Turbo, Storybook, Playwright, And Container **Files:** - Modify: `apps/web/playwright.config.ts` - Modify: `apps/web/Containerfile` - Modify: `apps/web/vite.config.ts` - Modify: `packages/ui/.storybook/main.ts` - Modify: `packages/ui/.storybook/preview.ts` - Modify: `tests/e2e/storybook-server.ts` after move to `apps/web/tests/e2e/storybook-server.ts` - Modify: `README.md` - [ ] **Step 1: Update Playwright commands for workspaces** In `apps/web/playwright.config.ts`, make the web server command build from the workspace root or app directory consistently: ```ts command: `DISABLE_LIVE_DATASOURCES=1 bun run build && DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js` ``` This command runs inside `apps/web` when invoked through the app package script. - [ ] **Step 2: Update Storybook package paths** `packages/ui/.storybook/main.ts` should use `../src/**/*.stories.@(js|ts|tsx)`. `packages/ui/.storybook/preview.ts` should import `../src/styles.css` and use generic MSW handlers only if they are moved into the UI package. - [ ] **Step 3: Update the container build** `apps/web/Containerfile` should build from the repository root context or copy only the workspace files it needs. Preserve the runtime command: ```dockerfile CMD ["bun", "build/index.js"] ``` - [ ] **Step 4: Run full root checks** Run: ```sh bun install bun run check bun run test:unit bun run build bun run build-storybook bun run test:e2e ``` Expected: PASS. - [ ] **Step 5: Commit** ```sh git add README.md apps/web packages/ui package.json turbo.json bun.lock git commit -m "build: wire turbo workspace qa" ``` ## Task 5: Final QA, PR, Review, Merge, Deploy **Files:** - No planned source edits unless verification finds blockers. - [ ] **Step 1: Run release gate** Run: ```sh bun run test:qa ``` Expected: PASS. - [ ] **Step 2: Push and open PR** Run: ```sh git push -u origin codex/turbo-component-library ``` Open a ready PR against `main` titled: ```text refactor: migrate dashboard to turbo component library ``` - [ ] **Step 3: Independent review** Send an independent reviewer to inspect the PR diff against the objective: Turbo repo, `apps/web`, compiled `packages/ui`, reusable components removed from app, Storybook with UI package, root QA passing, no server behavior change. - [ ] **Step 4: Resolve blockers** For each blocking review finding, write or update a failing test first, verify the failure, implement the fix, run targeted checks, commit, push, and re-review. - [ ] **Step 5: Merge and deploy** When review and checks are clean, merge the PR into `main`, sync the production checkout, rebuild the Podman image, restart `dimensionlab-website.service`, and verify `https://dimensionlab.net/` with a browser smoke check. ## Self-Review - Spec coverage: every completion criterion in the design maps to Task 1 through Task 5. - Placeholder scan: no task says TBD, TODO, or "add tests" without commands. - Type consistency: package names are consistently `@dimensionlab/ui` and `@dimensionlab/web`; the app adapter path is consistently `$lib/ui-adapter/model-renderer`.