# 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<string, string>;
      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<string, string>; name?: string };
    const uiPackage = JSON.parse(
      readFileSync(join(root, "packages/ui/package.json"), "utf8"),
    ) as { exports?: Record<string, unknown>; 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`.
