From 786a283f3165d19af1be75d69dad5b3ae2535d4f Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 05:17:28 +0200 Subject: [PATCH 01/50] docs: specify turbo component library migration --- ...26-06-20-turbo-component-library-design.md | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-20-turbo-component-library-design.md diff --git a/docs/superpowers/specs/2026-06-20-turbo-component-library-design.md b/docs/superpowers/specs/2026-06-20-turbo-component-library-design.md new file mode 100644 index 0000000..ba0fefc --- /dev/null +++ b/docs/superpowers/specs/2026-06-20-turbo-component-library-design.md @@ -0,0 +1,186 @@ +# Turbo Component Library Migration Design + +## Context + +The current Dimension Lab website is a single Bun/Vite React package. It owns +the browser app, Bun production server, dashboard model, persistence, datasource +adapters, reusable UI components, Storybook, Playwright checks, and container +deployment from one `package.json`. + +The UI components are already mostly generic and content-free under +`src/lib/ui`, but they are not reusable by another project because they live +inside the app package, depend on app path aliases, and share the app build, +test, and Storybook configuration. One file in that area, +`src/lib/ui/model-renderer.ts`, imports the Dimension Lab dashboard model and +therefore is an app adapter, not reusable component-library code. + +The migration goal is to turn the repository into a Turborepo workspace where +the Dimension Lab website consumes a separate reusable React component package. + +## Target Repository Shape + +Use one application workspace and one component-library workspace: + +```text +. +├── apps/ +│ └── web/ +│ ├── src/ +│ ├── tests/ +│ ├── drizzle/ +│ ├── Containerfile +│ └── package.json +├── packages/ +│ └── ui/ +│ ├── src/ +│ ├── .storybook/ +│ ├── package.json +│ └── tsconfig.json +├── package.json +├── turbo.json +├── tsconfig.base.json +└── bun.lock +``` + +The root package is private and contains only workspace orchestration: +workspaces, Turbo scripts, shared dev dependencies where useful, and the lock +file. Runtime dependencies belong to the workspace that imports them. + +## Package Ownership + +`packages/ui` is a compiled React library named `@dimensionlab/ui`. + +It owns: + +- Reusable React components currently under `src/lib/ui/components`. +- UI CSS tokens and component styles. +- Theme helpers currently under `src/lib/ui/theme.ts`. +- Generic UI prop/data types currently under `src/lib/ui/types.ts`. +- Generic formatting helpers currently under `src/lib/ui/format.ts`. +- Generic Storybook stories and story fixtures. +- UI render tests, boundary tests, and Storybook inventory tests. + +It must not import from the website app, the dashboard model, server modules, +database modules, datasource modules, or deployment files. + +The library publishes explicit package exports: + +- `@dimensionlab/ui` for component and type exports. +- `@dimensionlab/ui/styles.css` for the combined token/component CSS entry. +- Optional explicit subpath exports for future direct imports where useful. + +The compiled output goes to `packages/ui/dist` and includes JavaScript, +declaration files, and copied CSS. The package stays private for now but is +structured so it can later be published or moved into another Dimension Lab repo +without taking the website runtime with it. + +`apps/web` owns: + +- The Vite React browser app. +- The Bun production server and API routes. +- Dashboard model, fixtures, schema, validation, and model migrations. +- Drizzle/SQLite persistence and checked-in SQL migrations. +- Datasource adapters and runtime dashboard loading. +- Agent dashboard configuration endpoint. +- The `dashboardDocumentToUiDashboard` adapter that maps the app model to UI + package props. +- Playwright e2e tests and deployment container. + +## Why A Compiled Package + +The component package should be compiled rather than a just-in-time source +package. This is slightly more setup, but it better fits reuse outside the +current app because consumers can import stable JavaScript and declarations +instead of relying on their bundler to transpile this repo's TypeScript source. +It also gives Turbo a cacheable `@dimensionlab/ui#build` task. + +## Build And Task Graph + +Root scripts delegate through Turbo: + +- `bun run dev` runs the web dev server and any required dependency tasks. +- `bun run build` runs package builds in dependency order. +- `bun run check` runs TypeScript checks for all workspaces. +- `bun run test:unit` runs Vitest unit tests for all workspaces. +- `bun run build-storybook` builds Storybook from `packages/ui`. +- `bun run test:e2e` runs the web app Playwright suite. +- `bun run test:qa` is the full release gate. + +`turbo.json` defines `build`, `check`, `test:unit`, `build-storybook`, +`test:e2e`, and `test:qa` tasks. Build outputs include `dist/**`, +`storybook-static/**`, and `build/**` as appropriate. + +The web app depends on `@dimensionlab/ui` using Bun workspace syntax. The web +Vite config aliases `$lib` to `apps/web/src/lib`; the UI package should not use +that app alias. + +## Storybook + +Storybook moves with the component package. It should load +`@dimensionlab/ui/styles.css`, use React Vite Storybook, and keep the existing +generic story inventory. Environment-specific Dimension Lab labels, hostnames, +links, fallback values, and datasource names remain forbidden in package UI +source and stories. + +The repository should no longer have a root Storybook tied to the web app. + +## Deployment + +The deployed website remains the same service from the outside: + +- Production command remains `bun build/index.js` inside the runtime image. +- The container still exposes port `3000` and mounts `/data`. +- Runtime environment variables and database behavior remain unchanged. + +The `Containerfile` moves to `apps/web/Containerfile` or remains root with +updated workspace-aware copy/build steps. The chosen layout must preserve the +existing Podman service contract used by `dimensionlab-website.service`. + +## Testing Strategy + +The migration must add or update tests that prove the new boundaries: + +- The root package is a Bun workspace with `apps/*` and `packages/*`. +- The web app imports UI from `@dimensionlab/ui`, not from local copied + component files. +- `packages/ui` does not import from `apps/web`, `$lib/server`, `$lib/model`, + or any website runtime module. +- `dashboardDocumentToUiDashboard` lives in `apps/web` and is tested there. +- Storybook inventory is evaluated against `packages/ui`. +- The full QA gate still covers typecheck, unit tests, app build, Storybook + build, and Playwright desktop/mobile checks. + +## Completion Criteria + +The migration is complete only when current evidence proves all of these: + +- Root `package.json` is a private workspace root with Turbo scripts. +- `turbo.json` exists and models the workspace task graph. +- The web application lives under `apps/web`. +- The reusable React component library lives under `packages/ui`. +- `packages/ui/package.json` is named `@dimensionlab/ui` and has compiled + exports for code, types, and CSS. +- The web app depends on `@dimensionlab/ui` through the workspace. +- Reusable components and Storybook have been removed from the web app package. +- App-specific model/server/database/datasource code has not moved into + `packages/ui`. +- `dashboardDocumentToUiDashboard` is outside the UI package. +- `bun run check` passes from the root. +- `bun run test:unit` passes from the root. +- `bun run build` passes from the root. +- `bun run build-storybook` passes from the root. +- `bun run test:e2e` passes from the root. +- The production container can still be built and run with the same service + contract. + +## Migration Approach + +Implement this on branch `codex/turbo-component-library` in focused commits: + +1. Add the workspace/Turbo scaffolding and boundary tests. +2. Move UI code and Storybook to `packages/ui`. +3. Move the app runtime into `apps/web` and wire it to `@dimensionlab/ui`. +4. Move the model-to-UI adapter into the web app. +5. Update build, test, Playwright, Storybook, README, and container paths. +6. Run the full QA gate, push a ready PR, perform independent review, resolve + blockers, merge to `main`, and deploy only when checks and review are clean. From 4eadd21f7153790b6c177722614405d12f7681c6 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 05:20:31 +0200 Subject: [PATCH 02/50] docs: plan turbo component library migration --- .../2026-06-20-turbo-component-library.md | 436 ++++++++++++++++++ 1 file changed, 436 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-20-turbo-component-library.md diff --git a/docs/superpowers/plans/2026-06-20-turbo-component-library.md b/docs/superpowers/plans/2026-06-20-turbo-component-library.md new file mode 100644 index 0000000..e53586c --- /dev/null +++ b/docs/superpowers/plans/2026-06-20-turbo-component-library.md @@ -0,0 +1,436 @@ +# 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 --cwd apps/web run 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 --cwd apps/web run db:generate", + "db:check": "bun --cwd apps/web run 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 --cwd packages/ui run build +bun --cwd packages/ui run check +bun --cwd packages/ui run 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 --cwd apps/web run check +bun --cwd apps/web run 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`. From 87261b5a3f01f1aa4c157d2d5b121959df48fae8 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 05:23:04 +0200 Subject: [PATCH 03/50] build: add turbo workspace manifests --- apps/web/package.json | 49 ++++++++++++++ apps/web/src/lib/workspace-boundary.test.ts | 37 +++++++++++ apps/web/tsconfig.json | 19 ++++++ package.json | 73 ++++++--------------- packages/ui/package.json | 50 ++++++++++++++ packages/ui/tsconfig.build.json | 18 +++++ packages/ui/tsconfig.json | 8 +++ tsconfig.base.json | 18 +++++ turbo.json | 43 ++++++++++++ 9 files changed, 261 insertions(+), 54 deletions(-) create mode 100644 apps/web/package.json create mode 100644 apps/web/src/lib/workspace-boundary.test.ts create mode 100644 apps/web/tsconfig.json create mode 100644 packages/ui/package.json create mode 100644 packages/ui/tsconfig.build.json create mode 100644 packages/ui/tsconfig.json create mode 100644 tsconfig.base.json create mode 100644 turbo.json diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..285a94b --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,49 @@ +{ + "name": "@dimensionlab/web", + "version": "0.0.1", + "private": true, + "type": "module", + "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", + "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 test:e2e", + "db:generate": "drizzle-kit generate", + "db:check": "drizzle-kit check" + }, + "dependencies": { + "@dimensionlab/ui": "workspace:*", + "@sinclair/typebox": "^0.34.49", + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1", + "drizzle-orm": "^0.45.2", + "react": "^19.2.7", + "react-dom": "^19.2.7" + }, + "devDependencies": { + "@axe-core/playwright": "^4.11.3", + "@playwright/test": "^1.61.0", + "@tailwindcss/vite": "^4.3.1", + "@types/bun": "^1.3.14", + "@types/node": "^25.9.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.2", + "drizzle-kit": "^0.31.10", + "msw": "^2.14.6", + "shadcn": "^4.11.0", + "tailwindcss": "^4.3.1", + "typescript": "^6.0.3", + "vite": "^8.0.16", + "vitest": "^4.1.9" + }, + "msw": { + "workerDirectory": [ + "static" + ] + } +} diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts new file mode 100644 index 0000000..21c4020 --- /dev/null +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -0,0 +1,37 @@ +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, test } from "vitest"; + +const root = process.cwd(); + +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"); + }); +}); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..120931b --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "$lib/*": ["src/lib/*"] + }, + "types": ["node", "bun-types", "react", "react-dom", "vite/client"] + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "tests/**/*.ts", + "vite.config.ts", + "playwright.config.ts", + "drizzle.config.ts" + ], + "exclude": ["build", "dist", "node_modules"] +} diff --git a/package.json b/package.json index f1cf4ab..ba20908 100644 --- a/package.json +++ b/package.json @@ -1,63 +1,28 @@ { - "name": "dimensionlab-website", + "name": "dimensionlab", "version": "0.0.1", "private": true, "type": "module", + "packageManager": "bun@1.3.14", + "workspaces": [ + "apps/*", + "packages/*" + ], "scripts": { - "dev": "bun src/server/dev.ts", - "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" - }, - "dependencies": { - "@fontsource-variable/geist": "^5.2.9", - "@iconify/react": "^6.0.2", - "@sinclair/typebox": "^0.34.49", - "ajv": "^8.20.0", - "ajv-formats": "^3.0.1", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "drizzle-orm": "^0.45.2", - "lucide-react": "^1.21.0", - "radix-ui": "^1.6.0", - "react": "^19.2.7", - "react-dom": "^19.2.7", - "tailwind-merge": "^3.6.0", - "tw-animate-css": "^1.4.0", - "uplot": "^1.6.32" + "dev": "turbo dev --filter=@dimensionlab/web", + "build": "turbo build", + "preview": "bun --cwd apps/web run 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 --cwd apps/web run db:generate", + "db:check": "bun --cwd apps/web run db:check" }, "devDependencies": { - "@axe-core/playwright": "^4.11.3", - "@playwright/test": "^1.61.0", - "@storybook/addon-a11y": "^10.4.6", - "@storybook/addon-vitest": "^10.4.6", - "@storybook/react-vite": "^10.4.6", - "@tailwindcss/vite": "^4.3.1", - "@types/bun": "^1.3.14", - "@types/node": "^25.9.3", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.2", - "drizzle-kit": "^0.31.10", - "msw": "^2.14.6", - "shadcn": "^4.11.0", - "storybook": "^10.4.6", - "tailwindcss": "^4.3.1", - "typescript": "^6.0.3", - "vite": "^8.0.16", - "vitest": "^4.1.9" - }, - "msw": { - "workerDirectory": [ - "static" - ] + "turbo": "^2.5.0" } } diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..3afa92e --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,50 @@ +{ + "name": "@dimensionlab/ui", + "version": "0.0.1", + "private": true, + "type": "module", + "sideEffects": ["*.css", "**/*.css"], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./styles.css": "./dist/styles.css", + "./tokens.css": "./dist/tokens.css" + }, + "scripts": { + "build": "rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/components && cp src/styles.css dist/styles.css && cp src/tokens.css dist/tokens.css && cp src/components/styles.css dist/components/styles.css", + "check": "tsc --noEmit", + "test": "vitest run", + "test:unit": "vitest run", + "storybook": "storybook dev -p 6006 --host 0.0.0.0", + "build-storybook": "storybook build" + }, + "dependencies": { + "@fontsource-variable/geist": "^5.2.9", + "@iconify/react": "^6.0.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.21.0", + "radix-ui": "^1.6.0", + "tailwind-merge": "^3.6.0", + "tw-animate-css": "^1.4.0", + "uplot": "^1.6.32" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@storybook/addon-a11y": "^10.4.6", + "@storybook/addon-vitest": "^10.4.6", + "@storybook/react-vite": "^10.4.6", + "@types/bun": "^1.3.14", + "@types/node": "^25.9.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "typescript": "^6.0.3", + "vite": "^8.0.16", + "vitest": "^4.1.9" + } +} diff --git a/packages/ui/tsconfig.build.json b/packages/ui/tsconfig.build.json new file mode 100644 index 0000000..d942d59 --- /dev/null +++ b/packages/ui/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": false, + "noEmit": false, + "outDir": "dist", + "rootDir": "src" + }, + "exclude": [ + "dist", + "node_modules", + "storybook-static", + "src/**/*.test.ts", + "src/**/*.test.tsx", + "src/stories/**" + ] +} diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json new file mode 100644 index 0000000..2ad97bc --- /dev/null +++ b/packages/ui/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "." + }, + "include": ["src/**/*.ts", "src/**/*.tsx", ".storybook/**/*.ts"], + "exclude": ["dist", "node_modules", "storybook-static"] +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..6e74a60 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "ignoreDeprecations": "6.0", + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ES2022", + "types": ["node", "bun-types", "react", "react-dom", "vite/client"] + } +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..ee027a8 --- /dev/null +++ b/turbo.json @@ -0,0 +1,43 @@ +{ + "$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 + } + } +} From 2664804e91a56705ec8ba099b01f9da2e5809f8d Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 05:27:09 +0200 Subject: [PATCH 04/50] refactor(ui): extract reusable component package --- .../ui/.storybook}/main.ts | 4 - .../ui/.storybook}/preview.ts | 22 ++---- .../ui/src}/components/Badge.tsx | 0 .../ui/src}/components/Button.tsx | 0 .../ui/src}/components/CornerBracketFrame.tsx | 0 .../ui/src}/components/DashboardFrame.tsx | 0 .../ui/src}/components/DashboardHeader.tsx | 0 .../src}/components/DiagonalStripeField.tsx | 0 .../ui/src}/components/FooterCell.tsx | 0 .../ui/src}/components/FooterStatusCell.tsx | 0 .../ui/src}/components/GridFrame.tsx | 0 .../ui/src}/components/IconButton.tsx | 0 .../ui/src}/components/IconGlyph.tsx | 0 .../ui/src}/components/LineChart.tsx | 0 .../ui/src}/components/ModuleCard.tsx | 0 .../ui/src}/components/Panel.tsx | 0 .../ui/src}/components/ProgressMeter.tsx | 0 .../ui/src}/components/ScanlineField.tsx | 0 .../ui/src}/components/Separator.tsx | 0 .../ui/src}/components/ServiceGroupPanel.tsx | 0 .../ui/src}/components/ServicePanel.tsx | 0 .../ui/src}/components/ServiceRow.tsx | 0 .../ui/src}/components/SignalTrace.tsx | 0 .../ui/src}/components/Sparkline.tsx | 0 .../ui/src}/components/StatusBadge.tsx | 0 .../ui/src}/components/StatusStrip.tsx | 0 .../ui/src}/components/SystemState.tsx | 0 .../ui/src}/components/TelemetryCard.tsx | 0 .../ui/src}/components/TelemetryGrid.tsx | 0 .../ui/src}/components/TelemetryStrip.tsx | 0 .../ui/src}/components/ThemeToggle.tsx | 0 .../ui/src}/components/WeatherModule.tsx | 0 .../ui/src}/components/render.test.tsx | 0 .../ui/src}/components/styles.css | 0 packages/ui/src/content-boundary.test.ts | 74 +++++++++++++++++++ {src/lib/ui => packages/ui/src}/fixtures.ts | 0 {src/lib/ui => packages/ui/src}/format.ts | 0 {src/lib/ui => packages/ui/src}/index.ts | 1 - .../ui/src/primitives}/alert.tsx | 2 +- .../ui/src/primitives}/badge.tsx | 2 +- .../ui/src/primitives}/button.tsx | 2 +- .../ui/src/primitives}/card.tsx | 2 +- .../ui/src/primitives}/progress.tsx | 2 +- .../ui/src/primitives}/separator.tsx | 2 +- .../ui/src/primitives}/skeleton.tsx | 2 +- .../ui/src}/stories/Badge.stories.tsx | 0 .../ui/src}/stories/Button.stories.tsx | 0 .../stories/CornerBracketFrame.stories.tsx | 0 .../src}/stories/DashboardFrame.stories.tsx | 0 .../src}/stories/DashboardHeader.stories.tsx | 0 .../stories/DashboardOnePager.stories.tsx | 0 .../stories/DiagonalStripeField.stories.tsx | 0 .../ui/src}/stories/FooterCell.stories.tsx | 0 .../src}/stories/FooterStatusCell.stories.tsx | 0 .../ui/src}/stories/GridFrame.stories.tsx | 0 .../ui/src}/stories/IconButton.stories.tsx | 0 .../ui/src}/stories/IconGlyph.stories.tsx | 0 .../ui/src}/stories/LineChart.stories.tsx | 0 .../ui/src}/stories/ModuleCard.stories.tsx | 0 .../ui/src}/stories/Panel.stories.tsx | 0 .../ui/src}/stories/ProgressMeter.stories.tsx | 0 .../ui/src}/stories/ScanlineField.stories.tsx | 0 .../ui/src}/stories/Separator.stories.tsx | 0 .../stories/ServiceGroupPanel.stories.tsx | 0 .../ui/src}/stories/ServicePanel.stories.tsx | 0 .../ui/src}/stories/ServiceRow.stories.tsx | 0 .../ui/src}/stories/SignalTrace.stories.tsx | 0 .../ui/src}/stories/Sparkline.stories.tsx | 0 .../ui/src}/stories/StatusBadge.stories.tsx | 0 .../ui/src}/stories/StatusStrip.stories.tsx | 0 .../ui/src}/stories/SystemState.stories.tsx | 0 .../ui/src}/stories/TelemetryCard.stories.tsx | 0 .../ui/src}/stories/TelemetryGrid.stories.tsx | 0 .../src}/stories/TelemetryStrip.stories.tsx | 0 .../ui/src}/stories/ThemeToggle.stories.tsx | 0 .../ui/src}/stories/WeatherModule.stories.tsx | 0 .../ui/src}/stories/story-data.ts | 0 .../ui => packages/ui/src}/storybook.test.ts | 18 +++-- packages/ui/src/styles.css | 4 + {src/lib/ui => packages/ui/src}/theme.test.ts | 0 {src/lib/ui => packages/ui/src}/theme.ts | 0 {src/lib/ui => packages/ui/src}/tokens.css | 0 {src/lib/ui => packages/ui/src}/types.ts | 0 {src/lib => packages/ui/src}/utils.ts | 0 packages/ui/tsconfig.build.json | 1 + src/lib/ui/content-boundary.test.ts | 49 ------------ 86 files changed, 102 insertions(+), 85 deletions(-) rename {.storybook => packages/ui/.storybook}/main.ts (83%) rename {.storybook => packages/ui/.storybook}/preview.ts (63%) rename {src/lib/ui => packages/ui/src}/components/Badge.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/Button.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/CornerBracketFrame.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/DashboardFrame.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/DashboardHeader.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/DiagonalStripeField.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/FooterCell.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/FooterStatusCell.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/GridFrame.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/IconButton.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/IconGlyph.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/LineChart.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/ModuleCard.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/Panel.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/ProgressMeter.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/ScanlineField.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/Separator.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/ServiceGroupPanel.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/ServicePanel.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/ServiceRow.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/SignalTrace.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/Sparkline.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/StatusBadge.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/StatusStrip.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/SystemState.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/TelemetryCard.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/TelemetryGrid.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/TelemetryStrip.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/ThemeToggle.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/WeatherModule.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/render.test.tsx (100%) rename {src/lib/ui => packages/ui/src}/components/styles.css (100%) create mode 100644 packages/ui/src/content-boundary.test.ts rename {src/lib/ui => packages/ui/src}/fixtures.ts (100%) rename {src/lib/ui => packages/ui/src}/format.ts (100%) rename {src/lib/ui => packages/ui/src}/index.ts (96%) rename {src/lib/components/ui => packages/ui/src/primitives}/alert.tsx (98%) rename {src/lib/components/ui => packages/ui/src/primitives}/badge.tsx (98%) rename {src/lib/components/ui => packages/ui/src/primitives}/button.tsx (99%) rename {src/lib/components/ui => packages/ui/src/primitives}/card.tsx (98%) rename {src/lib/components/ui => packages/ui/src/primitives}/progress.tsx (95%) rename {src/lib/components/ui => packages/ui/src/primitives}/separator.tsx (95%) rename {src/lib/components/ui => packages/ui/src/primitives}/skeleton.tsx (88%) rename {src/lib/ui => packages/ui/src}/stories/Badge.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/Button.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/CornerBracketFrame.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/DashboardFrame.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/DashboardHeader.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/DashboardOnePager.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/DiagonalStripeField.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/FooterCell.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/FooterStatusCell.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/GridFrame.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/IconButton.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/IconGlyph.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/LineChart.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/ModuleCard.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/Panel.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/ProgressMeter.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/ScanlineField.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/Separator.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/ServiceGroupPanel.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/ServicePanel.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/ServiceRow.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/SignalTrace.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/Sparkline.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/StatusBadge.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/StatusStrip.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/SystemState.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/TelemetryCard.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/TelemetryGrid.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/TelemetryStrip.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/ThemeToggle.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/WeatherModule.stories.tsx (100%) rename {src/lib/ui => packages/ui/src}/stories/story-data.ts (100%) rename {src/lib/ui => packages/ui/src}/storybook.test.ts (86%) create mode 100644 packages/ui/src/styles.css rename {src/lib/ui => packages/ui/src}/theme.test.ts (100%) rename {src/lib/ui => packages/ui/src}/theme.ts (100%) rename {src/lib/ui => packages/ui/src}/tokens.css (100%) rename {src/lib/ui => packages/ui/src}/types.ts (100%) rename {src/lib => packages/ui/src}/utils.ts (100%) delete mode 100644 src/lib/ui/content-boundary.test.ts diff --git a/.storybook/main.ts b/packages/ui/.storybook/main.ts similarity index 83% rename from .storybook/main.ts rename to packages/ui/.storybook/main.ts index cfd3a61..34237be 100644 --- a/.storybook/main.ts +++ b/packages/ui/.storybook/main.ts @@ -2,7 +2,6 @@ import type { StorybookConfig } from "@storybook/react-vite"; const config: StorybookConfig = { stories: ["../src/**/*.stories.@(js|ts|tsx)"], - staticDirs: ["../static"], addons: [ "@storybook/addon-a11y", "@storybook/addon-vitest", @@ -11,9 +10,6 @@ const config: StorybookConfig = { name: "@storybook/react-vite", options: {}, }, - docs: { - autodocs: "tag", - }, }; export default config; diff --git a/.storybook/preview.ts b/packages/ui/.storybook/preview.ts similarity index 63% rename from .storybook/preview.ts rename to packages/ui/.storybook/preview.ts index 964f47b..87df132 100644 --- a/.storybook/preview.ts +++ b/packages/ui/.storybook/preview.ts @@ -1,17 +1,5 @@ -import "../src/app.css"; +import "../src/styles.css"; import type { Preview } from "@storybook/react-vite"; -import { setupWorker } from "msw/browser"; -import { externalApiHandlers } from "../src/lib/testing/external-api-mocks"; - -if (typeof window !== "undefined") { - const worker = setupWorker(...externalApiHandlers); - void worker.start({ - onUnhandledRequest: "bypass", - serviceWorker: { - url: "/mockServiceWorker.js", - }, - }); -} const preview: Preview = { decorators: [ @@ -44,10 +32,10 @@ const preview: Preview = { backgrounds: { default: "canvas", values: [ - { name: "canvas", value: "#020302" }, - { name: "raised", value: "#0b0d0c" }, - { name: "light canvas", value: "#f3f5ed" }, - { name: "light raised", value: "#eef1e7" }, + { name: "canvas", value: "#0b0f0d" }, + { name: "raised", value: "#151d18" }, + { name: "light canvas", value: "#eef2e7" }, + { name: "light raised", value: "#f1f5ea" }, ], }, controls: { diff --git a/src/lib/ui/components/Badge.tsx b/packages/ui/src/components/Badge.tsx similarity index 100% rename from src/lib/ui/components/Badge.tsx rename to packages/ui/src/components/Badge.tsx diff --git a/src/lib/ui/components/Button.tsx b/packages/ui/src/components/Button.tsx similarity index 100% rename from src/lib/ui/components/Button.tsx rename to packages/ui/src/components/Button.tsx diff --git a/src/lib/ui/components/CornerBracketFrame.tsx b/packages/ui/src/components/CornerBracketFrame.tsx similarity index 100% rename from src/lib/ui/components/CornerBracketFrame.tsx rename to packages/ui/src/components/CornerBracketFrame.tsx diff --git a/src/lib/ui/components/DashboardFrame.tsx b/packages/ui/src/components/DashboardFrame.tsx similarity index 100% rename from src/lib/ui/components/DashboardFrame.tsx rename to packages/ui/src/components/DashboardFrame.tsx diff --git a/src/lib/ui/components/DashboardHeader.tsx b/packages/ui/src/components/DashboardHeader.tsx similarity index 100% rename from src/lib/ui/components/DashboardHeader.tsx rename to packages/ui/src/components/DashboardHeader.tsx diff --git a/src/lib/ui/components/DiagonalStripeField.tsx b/packages/ui/src/components/DiagonalStripeField.tsx similarity index 100% rename from src/lib/ui/components/DiagonalStripeField.tsx rename to packages/ui/src/components/DiagonalStripeField.tsx diff --git a/src/lib/ui/components/FooterCell.tsx b/packages/ui/src/components/FooterCell.tsx similarity index 100% rename from src/lib/ui/components/FooterCell.tsx rename to packages/ui/src/components/FooterCell.tsx diff --git a/src/lib/ui/components/FooterStatusCell.tsx b/packages/ui/src/components/FooterStatusCell.tsx similarity index 100% rename from src/lib/ui/components/FooterStatusCell.tsx rename to packages/ui/src/components/FooterStatusCell.tsx diff --git a/src/lib/ui/components/GridFrame.tsx b/packages/ui/src/components/GridFrame.tsx similarity index 100% rename from src/lib/ui/components/GridFrame.tsx rename to packages/ui/src/components/GridFrame.tsx diff --git a/src/lib/ui/components/IconButton.tsx b/packages/ui/src/components/IconButton.tsx similarity index 100% rename from src/lib/ui/components/IconButton.tsx rename to packages/ui/src/components/IconButton.tsx diff --git a/src/lib/ui/components/IconGlyph.tsx b/packages/ui/src/components/IconGlyph.tsx similarity index 100% rename from src/lib/ui/components/IconGlyph.tsx rename to packages/ui/src/components/IconGlyph.tsx diff --git a/src/lib/ui/components/LineChart.tsx b/packages/ui/src/components/LineChart.tsx similarity index 100% rename from src/lib/ui/components/LineChart.tsx rename to packages/ui/src/components/LineChart.tsx diff --git a/src/lib/ui/components/ModuleCard.tsx b/packages/ui/src/components/ModuleCard.tsx similarity index 100% rename from src/lib/ui/components/ModuleCard.tsx rename to packages/ui/src/components/ModuleCard.tsx diff --git a/src/lib/ui/components/Panel.tsx b/packages/ui/src/components/Panel.tsx similarity index 100% rename from src/lib/ui/components/Panel.tsx rename to packages/ui/src/components/Panel.tsx diff --git a/src/lib/ui/components/ProgressMeter.tsx b/packages/ui/src/components/ProgressMeter.tsx similarity index 100% rename from src/lib/ui/components/ProgressMeter.tsx rename to packages/ui/src/components/ProgressMeter.tsx diff --git a/src/lib/ui/components/ScanlineField.tsx b/packages/ui/src/components/ScanlineField.tsx similarity index 100% rename from src/lib/ui/components/ScanlineField.tsx rename to packages/ui/src/components/ScanlineField.tsx diff --git a/src/lib/ui/components/Separator.tsx b/packages/ui/src/components/Separator.tsx similarity index 100% rename from src/lib/ui/components/Separator.tsx rename to packages/ui/src/components/Separator.tsx diff --git a/src/lib/ui/components/ServiceGroupPanel.tsx b/packages/ui/src/components/ServiceGroupPanel.tsx similarity index 100% rename from src/lib/ui/components/ServiceGroupPanel.tsx rename to packages/ui/src/components/ServiceGroupPanel.tsx diff --git a/src/lib/ui/components/ServicePanel.tsx b/packages/ui/src/components/ServicePanel.tsx similarity index 100% rename from src/lib/ui/components/ServicePanel.tsx rename to packages/ui/src/components/ServicePanel.tsx diff --git a/src/lib/ui/components/ServiceRow.tsx b/packages/ui/src/components/ServiceRow.tsx similarity index 100% rename from src/lib/ui/components/ServiceRow.tsx rename to packages/ui/src/components/ServiceRow.tsx diff --git a/src/lib/ui/components/SignalTrace.tsx b/packages/ui/src/components/SignalTrace.tsx similarity index 100% rename from src/lib/ui/components/SignalTrace.tsx rename to packages/ui/src/components/SignalTrace.tsx diff --git a/src/lib/ui/components/Sparkline.tsx b/packages/ui/src/components/Sparkline.tsx similarity index 100% rename from src/lib/ui/components/Sparkline.tsx rename to packages/ui/src/components/Sparkline.tsx diff --git a/src/lib/ui/components/StatusBadge.tsx b/packages/ui/src/components/StatusBadge.tsx similarity index 100% rename from src/lib/ui/components/StatusBadge.tsx rename to packages/ui/src/components/StatusBadge.tsx diff --git a/src/lib/ui/components/StatusStrip.tsx b/packages/ui/src/components/StatusStrip.tsx similarity index 100% rename from src/lib/ui/components/StatusStrip.tsx rename to packages/ui/src/components/StatusStrip.tsx diff --git a/src/lib/ui/components/SystemState.tsx b/packages/ui/src/components/SystemState.tsx similarity index 100% rename from src/lib/ui/components/SystemState.tsx rename to packages/ui/src/components/SystemState.tsx diff --git a/src/lib/ui/components/TelemetryCard.tsx b/packages/ui/src/components/TelemetryCard.tsx similarity index 100% rename from src/lib/ui/components/TelemetryCard.tsx rename to packages/ui/src/components/TelemetryCard.tsx diff --git a/src/lib/ui/components/TelemetryGrid.tsx b/packages/ui/src/components/TelemetryGrid.tsx similarity index 100% rename from src/lib/ui/components/TelemetryGrid.tsx rename to packages/ui/src/components/TelemetryGrid.tsx diff --git a/src/lib/ui/components/TelemetryStrip.tsx b/packages/ui/src/components/TelemetryStrip.tsx similarity index 100% rename from src/lib/ui/components/TelemetryStrip.tsx rename to packages/ui/src/components/TelemetryStrip.tsx diff --git a/src/lib/ui/components/ThemeToggle.tsx b/packages/ui/src/components/ThemeToggle.tsx similarity index 100% rename from src/lib/ui/components/ThemeToggle.tsx rename to packages/ui/src/components/ThemeToggle.tsx diff --git a/src/lib/ui/components/WeatherModule.tsx b/packages/ui/src/components/WeatherModule.tsx similarity index 100% rename from src/lib/ui/components/WeatherModule.tsx rename to packages/ui/src/components/WeatherModule.tsx diff --git a/src/lib/ui/components/render.test.tsx b/packages/ui/src/components/render.test.tsx similarity index 100% rename from src/lib/ui/components/render.test.tsx rename to packages/ui/src/components/render.test.tsx diff --git a/src/lib/ui/components/styles.css b/packages/ui/src/components/styles.css similarity index 100% rename from src/lib/ui/components/styles.css rename to packages/ui/src/components/styles.css diff --git a/packages/ui/src/content-boundary.test.ts b/packages/ui/src/content-boundary.test.ts new file mode 100644 index 0000000..80b85ce --- /dev/null +++ b/packages/ui/src/content-boundary.test.ts @@ -0,0 +1,74 @@ +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, test } from "vitest"; + +const root = process.cwd(); +const uiSourceRoot = existsSync(join(root, "packages/ui/src")) + ? join(root, "packages/ui/src") + : join(root, "src"); + +const forbiddenTerms = [ + "dimensionlab", + "dimension lab", + "vaultwarden", + "forgejo", + "grafana", + "uptime kuma", + "prometheus", + "backrest", + "open webui", + "comfyui", + "adminer", + "cockpit", + "ollama", +]; + +describe("UI package content boundary", () => { + test("contains the reusable dashboard component inventory", () => { + expect(existsSync(join(uiSourceRoot, "index.ts"))).toBe(true); + expect(existsSync(join(uiSourceRoot, "components/DashboardFrame.tsx"))).toBe( + true, + ); + expect(existsSync(join(uiSourceRoot, "components/ThemeToggle.tsx"))).toBe( + true, + ); + expect(existsSync(join(uiSourceRoot, "styles.css"))).toBe(true); + }); + + test("keeps environment-specific content out of reusable UI source", () => { + const source = readUiSource(uiSourceRoot).toLowerCase(); + + expect(forbiddenTerms.filter((term) => source.includes(term))).toEqual([]); + }); + + test("does not import website runtime modules", () => { + const source = readUiSource(uiSourceRoot); + + expect(source).not.toMatch( + /from ["'](?:apps\/web|\$lib\/server|\$lib\/model)/, + ); + expect(source).not.toContain("../web/"); + }); + + test("keeps icon rendering driven by icon identifiers", () => { + const source = readUiSource(uiSourceRoot); + + expect(source).not.toContain("@iconify-json/"); + expect(source).not.toContain("/icons/"); + }); +}); + +function readUiSource(path: string): string { + if (!existsSync(path)) return ""; + + const stats = statSync(path); + if (stats.isFile()) { + if (path.endsWith(".test.ts") || path.endsWith(".test.tsx")) return ""; + if (!/\.(tsx|ts|css)$/.test(path)) return ""; + return readFileSync(path, "utf8"); + } + + return readdirSync(path) + .map((entry) => readUiSource(join(path, entry))) + .join("\n"); +} diff --git a/src/lib/ui/fixtures.ts b/packages/ui/src/fixtures.ts similarity index 100% rename from src/lib/ui/fixtures.ts rename to packages/ui/src/fixtures.ts diff --git a/src/lib/ui/format.ts b/packages/ui/src/format.ts similarity index 100% rename from src/lib/ui/format.ts rename to packages/ui/src/format.ts diff --git a/src/lib/ui/index.ts b/packages/ui/src/index.ts similarity index 96% rename from src/lib/ui/index.ts rename to packages/ui/src/index.ts index 9eb5448..a9a83df 100644 --- a/src/lib/ui/index.ts +++ b/packages/ui/src/index.ts @@ -29,7 +29,6 @@ export { TelemetryStrip } from "./components/TelemetryStrip"; export { ThemeToggle } from "./components/ThemeToggle"; export { WeatherModule } from "./components/WeatherModule"; export { dashboardPreviewFixtures } from "./fixtures"; -export { dashboardDocumentToUiDashboard } from "./model-renderer"; export { getNextUiTheme, isUiTheme, diff --git a/src/lib/components/ui/alert.tsx b/packages/ui/src/primitives/alert.tsx similarity index 98% rename from src/lib/components/ui/alert.tsx rename to packages/ui/src/primitives/alert.tsx index fc34841..d5d1609 100644 --- a/src/lib/components/ui/alert.tsx +++ b/packages/ui/src/primitives/alert.tsx @@ -1,7 +1,7 @@ import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "$lib/utils" +import { cn } from "../utils" const alertVariants = cva( "group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", diff --git a/src/lib/components/ui/badge.tsx b/packages/ui/src/primitives/badge.tsx similarity index 98% rename from src/lib/components/ui/badge.tsx rename to packages/ui/src/primitives/badge.tsx index a3d91d7..baa1535 100644 --- a/src/lib/components/ui/badge.tsx +++ b/packages/ui/src/primitives/badge.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { Slot } from "radix-ui" -import { cn } from "$lib/utils" +import { cn } from "../utils" const badgeVariants = cva( "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", diff --git a/src/lib/components/ui/button.tsx b/packages/ui/src/primitives/button.tsx similarity index 99% rename from src/lib/components/ui/button.tsx rename to packages/ui/src/primitives/button.tsx index 8dda5f2..e37f9b0 100644 --- a/src/lib/components/ui/button.tsx +++ b/packages/ui/src/primitives/button.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { Slot } from "radix-ui" -import { cn } from "$lib/utils" +import { cn } from "../utils" const buttonVariants = cva( "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", diff --git a/src/lib/components/ui/card.tsx b/packages/ui/src/primitives/card.tsx similarity index 98% rename from src/lib/components/ui/card.tsx rename to packages/ui/src/primitives/card.tsx index bcb9e07..e5e5996 100644 --- a/src/lib/components/ui/card.tsx +++ b/packages/ui/src/primitives/card.tsx @@ -1,6 +1,6 @@ import * as React from "react" -import { cn } from "$lib/utils" +import { cn } from "../utils" function Card({ className, diff --git a/src/lib/components/ui/progress.tsx b/packages/ui/src/primitives/progress.tsx similarity index 95% rename from src/lib/components/ui/progress.tsx rename to packages/ui/src/primitives/progress.tsx index 65d0a6b..59fac6c 100644 --- a/src/lib/components/ui/progress.tsx +++ b/packages/ui/src/primitives/progress.tsx @@ -1,7 +1,7 @@ import * as React from "react" import { Progress as ProgressPrimitive } from "radix-ui" -import { cn } from "$lib/utils" +import { cn } from "../utils" function Progress({ className, diff --git a/src/lib/components/ui/separator.tsx b/packages/ui/src/primitives/separator.tsx similarity index 95% rename from src/lib/components/ui/separator.tsx rename to packages/ui/src/primitives/separator.tsx index 84e3c64..ee9f6cb 100644 --- a/src/lib/components/ui/separator.tsx +++ b/packages/ui/src/primitives/separator.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { Separator as SeparatorPrimitive } from "radix-ui" -import { cn } from "$lib/utils" +import { cn } from "../utils" function Separator({ className, diff --git a/src/lib/components/ui/skeleton.tsx b/packages/ui/src/primitives/skeleton.tsx similarity index 88% rename from src/lib/components/ui/skeleton.tsx rename to packages/ui/src/primitives/skeleton.tsx index 61466a8..a1df193 100644 --- a/src/lib/components/ui/skeleton.tsx +++ b/packages/ui/src/primitives/skeleton.tsx @@ -1,4 +1,4 @@ -import { cn } from "$lib/utils" +import { cn } from "../utils" function Skeleton({ className, ...props }: React.ComponentProps<"div">) { return ( diff --git a/src/lib/ui/stories/Badge.stories.tsx b/packages/ui/src/stories/Badge.stories.tsx similarity index 100% rename from src/lib/ui/stories/Badge.stories.tsx rename to packages/ui/src/stories/Badge.stories.tsx diff --git a/src/lib/ui/stories/Button.stories.tsx b/packages/ui/src/stories/Button.stories.tsx similarity index 100% rename from src/lib/ui/stories/Button.stories.tsx rename to packages/ui/src/stories/Button.stories.tsx diff --git a/src/lib/ui/stories/CornerBracketFrame.stories.tsx b/packages/ui/src/stories/CornerBracketFrame.stories.tsx similarity index 100% rename from src/lib/ui/stories/CornerBracketFrame.stories.tsx rename to packages/ui/src/stories/CornerBracketFrame.stories.tsx diff --git a/src/lib/ui/stories/DashboardFrame.stories.tsx b/packages/ui/src/stories/DashboardFrame.stories.tsx similarity index 100% rename from src/lib/ui/stories/DashboardFrame.stories.tsx rename to packages/ui/src/stories/DashboardFrame.stories.tsx diff --git a/src/lib/ui/stories/DashboardHeader.stories.tsx b/packages/ui/src/stories/DashboardHeader.stories.tsx similarity index 100% rename from src/lib/ui/stories/DashboardHeader.stories.tsx rename to packages/ui/src/stories/DashboardHeader.stories.tsx diff --git a/src/lib/ui/stories/DashboardOnePager.stories.tsx b/packages/ui/src/stories/DashboardOnePager.stories.tsx similarity index 100% rename from src/lib/ui/stories/DashboardOnePager.stories.tsx rename to packages/ui/src/stories/DashboardOnePager.stories.tsx diff --git a/src/lib/ui/stories/DiagonalStripeField.stories.tsx b/packages/ui/src/stories/DiagonalStripeField.stories.tsx similarity index 100% rename from src/lib/ui/stories/DiagonalStripeField.stories.tsx rename to packages/ui/src/stories/DiagonalStripeField.stories.tsx diff --git a/src/lib/ui/stories/FooterCell.stories.tsx b/packages/ui/src/stories/FooterCell.stories.tsx similarity index 100% rename from src/lib/ui/stories/FooterCell.stories.tsx rename to packages/ui/src/stories/FooterCell.stories.tsx diff --git a/src/lib/ui/stories/FooterStatusCell.stories.tsx b/packages/ui/src/stories/FooterStatusCell.stories.tsx similarity index 100% rename from src/lib/ui/stories/FooterStatusCell.stories.tsx rename to packages/ui/src/stories/FooterStatusCell.stories.tsx diff --git a/src/lib/ui/stories/GridFrame.stories.tsx b/packages/ui/src/stories/GridFrame.stories.tsx similarity index 100% rename from src/lib/ui/stories/GridFrame.stories.tsx rename to packages/ui/src/stories/GridFrame.stories.tsx diff --git a/src/lib/ui/stories/IconButton.stories.tsx b/packages/ui/src/stories/IconButton.stories.tsx similarity index 100% rename from src/lib/ui/stories/IconButton.stories.tsx rename to packages/ui/src/stories/IconButton.stories.tsx diff --git a/src/lib/ui/stories/IconGlyph.stories.tsx b/packages/ui/src/stories/IconGlyph.stories.tsx similarity index 100% rename from src/lib/ui/stories/IconGlyph.stories.tsx rename to packages/ui/src/stories/IconGlyph.stories.tsx diff --git a/src/lib/ui/stories/LineChart.stories.tsx b/packages/ui/src/stories/LineChart.stories.tsx similarity index 100% rename from src/lib/ui/stories/LineChart.stories.tsx rename to packages/ui/src/stories/LineChart.stories.tsx diff --git a/src/lib/ui/stories/ModuleCard.stories.tsx b/packages/ui/src/stories/ModuleCard.stories.tsx similarity index 100% rename from src/lib/ui/stories/ModuleCard.stories.tsx rename to packages/ui/src/stories/ModuleCard.stories.tsx diff --git a/src/lib/ui/stories/Panel.stories.tsx b/packages/ui/src/stories/Panel.stories.tsx similarity index 100% rename from src/lib/ui/stories/Panel.stories.tsx rename to packages/ui/src/stories/Panel.stories.tsx diff --git a/src/lib/ui/stories/ProgressMeter.stories.tsx b/packages/ui/src/stories/ProgressMeter.stories.tsx similarity index 100% rename from src/lib/ui/stories/ProgressMeter.stories.tsx rename to packages/ui/src/stories/ProgressMeter.stories.tsx diff --git a/src/lib/ui/stories/ScanlineField.stories.tsx b/packages/ui/src/stories/ScanlineField.stories.tsx similarity index 100% rename from src/lib/ui/stories/ScanlineField.stories.tsx rename to packages/ui/src/stories/ScanlineField.stories.tsx diff --git a/src/lib/ui/stories/Separator.stories.tsx b/packages/ui/src/stories/Separator.stories.tsx similarity index 100% rename from src/lib/ui/stories/Separator.stories.tsx rename to packages/ui/src/stories/Separator.stories.tsx diff --git a/src/lib/ui/stories/ServiceGroupPanel.stories.tsx b/packages/ui/src/stories/ServiceGroupPanel.stories.tsx similarity index 100% rename from src/lib/ui/stories/ServiceGroupPanel.stories.tsx rename to packages/ui/src/stories/ServiceGroupPanel.stories.tsx diff --git a/src/lib/ui/stories/ServicePanel.stories.tsx b/packages/ui/src/stories/ServicePanel.stories.tsx similarity index 100% rename from src/lib/ui/stories/ServicePanel.stories.tsx rename to packages/ui/src/stories/ServicePanel.stories.tsx diff --git a/src/lib/ui/stories/ServiceRow.stories.tsx b/packages/ui/src/stories/ServiceRow.stories.tsx similarity index 100% rename from src/lib/ui/stories/ServiceRow.stories.tsx rename to packages/ui/src/stories/ServiceRow.stories.tsx diff --git a/src/lib/ui/stories/SignalTrace.stories.tsx b/packages/ui/src/stories/SignalTrace.stories.tsx similarity index 100% rename from src/lib/ui/stories/SignalTrace.stories.tsx rename to packages/ui/src/stories/SignalTrace.stories.tsx diff --git a/src/lib/ui/stories/Sparkline.stories.tsx b/packages/ui/src/stories/Sparkline.stories.tsx similarity index 100% rename from src/lib/ui/stories/Sparkline.stories.tsx rename to packages/ui/src/stories/Sparkline.stories.tsx diff --git a/src/lib/ui/stories/StatusBadge.stories.tsx b/packages/ui/src/stories/StatusBadge.stories.tsx similarity index 100% rename from src/lib/ui/stories/StatusBadge.stories.tsx rename to packages/ui/src/stories/StatusBadge.stories.tsx diff --git a/src/lib/ui/stories/StatusStrip.stories.tsx b/packages/ui/src/stories/StatusStrip.stories.tsx similarity index 100% rename from src/lib/ui/stories/StatusStrip.stories.tsx rename to packages/ui/src/stories/StatusStrip.stories.tsx diff --git a/src/lib/ui/stories/SystemState.stories.tsx b/packages/ui/src/stories/SystemState.stories.tsx similarity index 100% rename from src/lib/ui/stories/SystemState.stories.tsx rename to packages/ui/src/stories/SystemState.stories.tsx diff --git a/src/lib/ui/stories/TelemetryCard.stories.tsx b/packages/ui/src/stories/TelemetryCard.stories.tsx similarity index 100% rename from src/lib/ui/stories/TelemetryCard.stories.tsx rename to packages/ui/src/stories/TelemetryCard.stories.tsx diff --git a/src/lib/ui/stories/TelemetryGrid.stories.tsx b/packages/ui/src/stories/TelemetryGrid.stories.tsx similarity index 100% rename from src/lib/ui/stories/TelemetryGrid.stories.tsx rename to packages/ui/src/stories/TelemetryGrid.stories.tsx diff --git a/src/lib/ui/stories/TelemetryStrip.stories.tsx b/packages/ui/src/stories/TelemetryStrip.stories.tsx similarity index 100% rename from src/lib/ui/stories/TelemetryStrip.stories.tsx rename to packages/ui/src/stories/TelemetryStrip.stories.tsx diff --git a/src/lib/ui/stories/ThemeToggle.stories.tsx b/packages/ui/src/stories/ThemeToggle.stories.tsx similarity index 100% rename from src/lib/ui/stories/ThemeToggle.stories.tsx rename to packages/ui/src/stories/ThemeToggle.stories.tsx diff --git a/src/lib/ui/stories/WeatherModule.stories.tsx b/packages/ui/src/stories/WeatherModule.stories.tsx similarity index 100% rename from src/lib/ui/stories/WeatherModule.stories.tsx rename to packages/ui/src/stories/WeatherModule.stories.tsx diff --git a/src/lib/ui/stories/story-data.ts b/packages/ui/src/stories/story-data.ts similarity index 100% rename from src/lib/ui/stories/story-data.ts rename to packages/ui/src/stories/story-data.ts diff --git a/src/lib/ui/storybook.test.ts b/packages/ui/src/storybook.test.ts similarity index 86% rename from src/lib/ui/storybook.test.ts rename to packages/ui/src/storybook.test.ts index aa76cad..4ef14ba 100644 --- a/src/lib/ui/storybook.test.ts +++ b/packages/ui/src/storybook.test.ts @@ -3,8 +3,11 @@ import { join } from "node:path"; import { describe, expect, test } from "vitest"; const root = process.cwd(); -const componentsDir = join(root, "src/lib/ui/components"); -const storiesDir = join(root, "src/lib/ui/stories"); +const packageRoot = existsSync(join(root, "packages/ui/package.json")) + ? join(root, "packages/ui") + : root; +const componentsDir = join(packageRoot, "src/components"); +const storiesDir = join(packageRoot, "src/stories"); const requiredStoryFiles = [ "Badge.stories.tsx", @@ -49,7 +52,7 @@ const forbiddenStoryContent = [ describe("Storybook inventory", () => { test("exposes scripts for local and static Storybook review", () => { - const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { + const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as { scripts?: Record; }; @@ -65,21 +68,22 @@ describe("Storybook inventory", () => { }); test("loads dashboard component styles through the global app stylesheet", () => { - const appStyles = readFileSync(join(root, "src/app.css"), "utf8"); + const packageStyles = readFileSync(join(packageRoot, "src/styles.css"), "utf8"); const dashboardFrame = readFileSync( join(componentsDir, "DashboardFrame.tsx"), "utf8", ); - expect(appStyles).toContain('./lib/ui/components/styles.css'); + expect(packageStyles).toContain('./components/styles.css'); expect(dashboardFrame).not.toContain('./styles.css'); }); test("configures Storybook theme switching for reusable components", () => { - const previewSource = readFileSync(join(root, ".storybook/preview.ts"), "utf8"); + const previewSource = readFileSync(join(packageRoot, ".storybook/preview.ts"), "utf8"); expect(previewSource).toContain("globalTypes"); expect(previewSource).toContain("data-ui-theme"); + expect(previewSource).toContain("../src/styles.css"); }); test("keeps component and story files paired as the UI inventory changes", () => { @@ -135,7 +139,7 @@ describe("Storybook inventory", () => { test("does not add deferred form/navigation primitives", () => { for (const component of ["Input", "ToggleGroup", "ScrollArea"]) { - expect(existsSync(join(root, `src/lib/ui/components/${component}.tsx`))).toBe(false); + expect(existsSync(join(componentsDir, `${component}.tsx`))).toBe(false); expect(existsSync(join(storiesDir, `${component}.stories.tsx`))).toBe(false); } }); diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css new file mode 100644 index 0000000..fd6f4a2 --- /dev/null +++ b/packages/ui/src/styles.css @@ -0,0 +1,4 @@ +@import "@fontsource-variable/geist"; +@import "uplot/dist/uPlot.min.css"; +@import "./tokens.css"; +@import "./components/styles.css"; diff --git a/src/lib/ui/theme.test.ts b/packages/ui/src/theme.test.ts similarity index 100% rename from src/lib/ui/theme.test.ts rename to packages/ui/src/theme.test.ts diff --git a/src/lib/ui/theme.ts b/packages/ui/src/theme.ts similarity index 100% rename from src/lib/ui/theme.ts rename to packages/ui/src/theme.ts diff --git a/src/lib/ui/tokens.css b/packages/ui/src/tokens.css similarity index 100% rename from src/lib/ui/tokens.css rename to packages/ui/src/tokens.css diff --git a/src/lib/ui/types.ts b/packages/ui/src/types.ts similarity index 100% rename from src/lib/ui/types.ts rename to packages/ui/src/types.ts diff --git a/src/lib/utils.ts b/packages/ui/src/utils.ts similarity index 100% rename from src/lib/utils.ts rename to packages/ui/src/utils.ts diff --git a/packages/ui/tsconfig.build.json b/packages/ui/tsconfig.build.json index d942d59..bd56366 100644 --- a/packages/ui/tsconfig.build.json +++ b/packages/ui/tsconfig.build.json @@ -7,6 +7,7 @@ "outDir": "dist", "rootDir": "src" }, + "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": [ "dist", "node_modules", diff --git a/src/lib/ui/content-boundary.test.ts b/src/lib/ui/content-boundary.test.ts deleted file mode 100644 index 6613644..0000000 --- a/src/lib/ui/content-boundary.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { readdirSync, readFileSync, statSync } from "node:fs"; -import { join } from "node:path"; -import { describe, expect, test } from "vitest"; - -const forbiddenTerms = [ - "dimensionlab", - "dimension lab", - "vaultwarden", - "forgejo", - "grafana", - "uptime kuma", - "prometheus", - "backrest", - "open webui", - "comfyui", - "adminer", - "cockpit", - "ollama", -]; - -describe("UI content boundary", () => { - test("keeps environment-specific content out of reusable UI source", () => { - const source = readUiSource(join(process.cwd(), "src", "lib", "ui")); - const normalized = source.toLowerCase(); - - expect( - forbiddenTerms.filter((term) => normalized.includes(term)), - ).toEqual([]); - }); - - test("keeps icon rendering driven by icon identifiers", () => { - const source = readUiSource(join(process.cwd(), "src", "lib", "ui")); - - expect(source).not.toContain("@iconify-json/"); - expect(source).not.toContain("/icons/"); - }); -}); - -function readUiSource(path: string): string { - const stats = statSync(path); - if (stats.isFile()) { - if (path.endsWith(".test.ts")) return ""; - return readFileSync(path, "utf8"); - } - - return readdirSync(path) - .map((entry) => readUiSource(join(path, entry))) - .join("\n"); -} From b4e626a8687888c42ec5f48af69c51c721e3486d Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 05:41:36 +0200 Subject: [PATCH 05/50] refactor(web): move website into turbo app workspace --- .gitignore | 5 + Containerfile | 31 -- README.md | 96 +++--- apps/web/Containerfile | 32 ++ .../web/drizzle.config.ts | 0 .../drizzle}/0000_dashboard_persistence.sql | 0 .../web/drizzle}/meta/0000_snapshot.json | 0 .../web/drizzle}/meta/_journal.json | 0 index.html => apps/web/index.html | 0 apps/web/package.json | 6 +- .../web/playwright.config.ts | 2 +- {src => apps/web/src}/App.test.tsx | 0 {src => apps/web/src}/App.tsx | 4 +- {src => apps/web/src}/app.css | 4 +- .../lib/model/fixtures/dimensionlab.test.ts | 0 .../src}/lib/model/fixtures/dimensionlab.ts | 0 .../web/src}/lib/model/fixtures/generic.ts | 0 .../web/src}/lib/model/fixtures/index.ts | 0 {src => apps/web/src}/lib/model/index.ts | 0 .../web/src}/lib/model/schema.test.ts | 0 {src => apps/web/src}/lib/model/schema.ts | 0 {src => apps/web/src}/lib/model/validation.ts | 0 .../src}/lib/presentation-boundary.test.ts | 26 +- .../server/agent-config/agent-config.test.ts | 0 .../web/src}/lib/server/agent-config/index.ts | 0 .../web/src}/lib/server/dashboard.test.ts | 0 {src => apps/web/src}/lib/server/dashboard.ts | 0 .../datasources/dashboard-datasources.test.ts | 0 .../web/src}/lib/server/datasources/index.ts | 0 .../web/src}/lib/server/db/connection.ts | 0 .../lib/server/db/dashboard-store.test.ts | 0 .../web/src}/lib/server/db/dashboard-store.ts | 0 .../web/src}/lib/server/db/migrations.ts | 0 .../lib/server/db/model-migrations.test.ts | 0 .../src}/lib/server/db/model-migrations.ts | 0 {src => apps/web/src}/lib/server/db/schema.ts | 0 .../lib/testing/external-api-mocks.test.ts | 0 .../src}/lib/testing/external-api-mocks.ts | 0 .../lib/ui-adapter}/model-renderer.test.ts | 15 +- .../web/src/lib/ui-adapter}/model-renderer.ts | 2 +- apps/web/src/lib/workspace-boundary.test.ts | 4 +- {src => apps/web/src}/main.tsx | 0 {src => apps/web/src}/page.test.tsx | 0 {src => apps/web/src}/server/dev.test.ts | 0 {src => apps/web/src}/server/dev.ts | 0 {src => apps/web/src}/server/index.ts | 0 .../server/routes/agent-dashboard.test.ts | 0 .../web/src}/server/routes/agent-dashboard.ts | 0 .../web/src}/server/routes/dashboard.test.ts | 0 .../web/src}/server/routes/dashboard.ts | 0 {src => apps/web/src}/vite-env.d.ts | 0 .../web/static}/mockServiceWorker.js | 0 .../web/tests}/e2e/dashboard.spec.ts | 0 ...shboard-desktop-chromium-desktop-linux.png | Bin ...d-light-desktop-chromium-desktop-linux.png | Bin ...dashboard-mobile-chromium-mobile-linux.png | Bin .../web/tests}/e2e/storybook-server.ts | 6 +- .../web/tests}/e2e/storybook.spec.ts | 0 apps/web/tsconfig.json | 2 + vite.config.ts => apps/web/vite.config.ts | 6 + bun.lock | 322 +++++++++--------- .../ui/components.json | 12 +- packages/ui/package.json | 2 + packages/ui/src/css.d.ts | 1 + tsconfig.base.json | 2 +- tsconfig.json | 36 +- 66 files changed, 318 insertions(+), 298 deletions(-) delete mode 100644 Containerfile create mode 100644 apps/web/Containerfile rename drizzle.config.ts => apps/web/drizzle.config.ts (100%) rename {drizzle => apps/web/drizzle}/0000_dashboard_persistence.sql (100%) rename {drizzle => apps/web/drizzle}/meta/0000_snapshot.json (100%) rename {drizzle => apps/web/drizzle}/meta/_journal.json (100%) rename index.html => apps/web/index.html (100%) rename playwright.config.ts => apps/web/playwright.config.ts (90%) rename {src => apps/web/src}/App.test.tsx (100%) rename {src => apps/web/src}/App.tsx (97%) rename {src => apps/web/src}/app.css (97%) rename {src => apps/web/src}/lib/model/fixtures/dimensionlab.test.ts (100%) rename {src => apps/web/src}/lib/model/fixtures/dimensionlab.ts (100%) rename {src => apps/web/src}/lib/model/fixtures/generic.ts (100%) rename {src => apps/web/src}/lib/model/fixtures/index.ts (100%) rename {src => apps/web/src}/lib/model/index.ts (100%) rename {src => apps/web/src}/lib/model/schema.test.ts (100%) rename {src => apps/web/src}/lib/model/schema.ts (100%) rename {src => apps/web/src}/lib/model/validation.ts (100%) rename {src => apps/web/src}/lib/presentation-boundary.test.ts (64%) rename {src => apps/web/src}/lib/server/agent-config/agent-config.test.ts (100%) rename {src => apps/web/src}/lib/server/agent-config/index.ts (100%) rename {src => apps/web/src}/lib/server/dashboard.test.ts (100%) rename {src => apps/web/src}/lib/server/dashboard.ts (100%) rename {src => apps/web/src}/lib/server/datasources/dashboard-datasources.test.ts (100%) rename {src => apps/web/src}/lib/server/datasources/index.ts (100%) rename {src => apps/web/src}/lib/server/db/connection.ts (100%) rename {src => apps/web/src}/lib/server/db/dashboard-store.test.ts (100%) rename {src => apps/web/src}/lib/server/db/dashboard-store.ts (100%) rename {src => apps/web/src}/lib/server/db/migrations.ts (100%) rename {src => apps/web/src}/lib/server/db/model-migrations.test.ts (100%) rename {src => apps/web/src}/lib/server/db/model-migrations.ts (100%) rename {src => apps/web/src}/lib/server/db/schema.ts (100%) rename {src => apps/web/src}/lib/testing/external-api-mocks.test.ts (100%) rename {src => apps/web/src}/lib/testing/external-api-mocks.ts (100%) rename {src/lib/ui => apps/web/src/lib/ui-adapter}/model-renderer.test.ts (89%) rename {src/lib/ui => apps/web/src/lib/ui-adapter}/model-renderer.ts (98%) rename {src => apps/web/src}/main.tsx (100%) rename {src => apps/web/src}/page.test.tsx (100%) rename {src => apps/web/src}/server/dev.test.ts (100%) rename {src => apps/web/src}/server/dev.ts (100%) rename {src => apps/web/src}/server/index.ts (100%) rename {src => apps/web/src}/server/routes/agent-dashboard.test.ts (100%) rename {src => apps/web/src}/server/routes/agent-dashboard.ts (100%) rename {src => apps/web/src}/server/routes/dashboard.test.ts (100%) rename {src => apps/web/src}/server/routes/dashboard.ts (100%) rename {src => apps/web/src}/vite-env.d.ts (100%) rename {static => apps/web/static}/mockServiceWorker.js (100%) rename {tests => apps/web/tests}/e2e/dashboard.spec.ts (100%) rename {tests => apps/web/tests}/e2e/dashboard.spec.ts-snapshots/dashboard-desktop-chromium-desktop-linux.png (100%) rename {tests => apps/web/tests}/e2e/dashboard.spec.ts-snapshots/dashboard-light-desktop-chromium-desktop-linux.png (100%) rename {tests => apps/web/tests}/e2e/dashboard.spec.ts-snapshots/dashboard-mobile-chromium-mobile-linux.png (100%) rename {tests => apps/web/tests}/e2e/storybook-server.ts (82%) rename {tests => apps/web/tests}/e2e/storybook.spec.ts (100%) rename vite.config.ts => apps/web/vite.config.ts (76%) rename components.json => packages/ui/components.json (67%) create mode 100644 packages/ui/src/css.d.ts diff --git a/.gitignore b/.gitignore index 7d8d6c4..b6326bb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ node_modules/ build/ dist/ .vite/ +.turbo/ +apps/*/.turbo/ +packages/*/.turbo/ .env .env.* @@ -10,6 +13,8 @@ dist/ data/*.sqlite data/*.sqlite-* +apps/*/data/*.sqlite +apps/*/data/*.sqlite-* coverage/ playwright-report/ test-results/ diff --git a/Containerfile b/Containerfile deleted file mode 100644 index d19c2d0..0000000 --- a/Containerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM docker.io/oven/bun:1.3.14 AS deps - -WORKDIR /app -COPY package.json bun.lock ./ -RUN bun install --frozen-lockfile - -FROM deps AS build - -COPY . . -RUN bun run build - -FROM docker.io/oven/bun:1.3.14 AS runtime - -WORKDIR /app -ENV NODE_ENV=production -ENV HOST=0.0.0.0 -ENV PORT=3000 -ENV DATABASE_URL=file:/data/dimensionlab.sqlite -ENV DASHBOARD_MIGRATIONS_DIR=/app/drizzle - -COPY package.json bun.lock ./ -RUN bun install --frozen-lockfile --production -COPY --from=build /app/build ./build -COPY --from=build /app/dist ./dist -COPY --from=build /app/drizzle ./drizzle - -RUN mkdir -p /data -VOLUME ["/data"] -EXPOSE 3000 - -CMD ["bun", "build/index.js"] diff --git a/README.md b/README.md index f39dcb5..59dedf6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,20 @@ # Dimension Lab Website -Standalone React runtime for the Dimension Lab system overview dashboard. +Turbo/Bun workspace for the Dimension Lab system overview dashboard and its +reusable React component library. This project is not a Homepage customization and does not depend on Homepage runtime, frontend code, or configuration. The dashboard will be model-driven: -the reusable renderer stays content-free, while environment-specific data lives -in validated dashboard model state. +the reusable UI package stays content-free, while environment-specific data +lives in validated dashboard model state inside the web app. + +## Workspace Layout + +- `apps/web`: Vite React website, Bun API server, model fixtures, Drizzle + persistence, Playwright e2e checks, and container build. +- `packages/ui`: reusable dashboard React components, design tokens, + shadcn/radix primitives, generic fixtures, and Storybook. +- `docs/superpowers`: migration specs and execution plans used for this repo. ## Development @@ -18,18 +27,18 @@ This MVP uses Drizzle with Bun SQLite for local file-backed persistence. ## Scripts -- `bun run dev`: start the local Vite development server with a Bun API proxy. -- `bun run check`: run TypeScript checks. -- `bun run test`: run Vitest. +- `bun run dev`: start the web app dev runtime through Turbo. +- `bun run check`: run TypeScript checks in all workspaces. +- `bun run test`: run the unit test stage in all workspaces. - `bun run test:unit`: run Vitest explicitly as the unit test stage. - `bun run test:e2e`: build and run Playwright browser smoke and QA checks. -- `bun run test:qa`: run the MVP release gate. -- `bun run build`: build the production app. -- `bun run preview`: preview the production build. -- `bun run storybook`: start the component explorer on port 6006. -- `bun run build-storybook`: build the static Storybook review artifact. -- `bun run db:generate`: generate Drizzle migrations from the server schema. -- `bun run db:check`: validate migration consistency. +- `bun run test:qa`: run the release gate through Turbo. +- `bun run build`: build the UI package, production website, and Bun server. +- `bun run preview`: preview the production web build. +- `bun run storybook`: start the UI package component explorer on port 6006. +- `bun run build-storybook`: build the UI package static Storybook artifact. +- `bun run db:generate`: generate web app Drizzle migrations. +- `bun run db:check`: validate web app migration consistency. ## Persistence @@ -40,14 +49,14 @@ URL is: DATABASE_URL=file:./data/dimensionlab.sqlite ``` -SQLite files under `data/` are ignored. Drizzle schema lives in -`src/lib/server/db/schema.ts`; tracked migrations live in `drizzle/`. Runtime -startup applies the checked-in dashboard migrations before reads or writes. If -the app is launched from outside the repo tree, set `DASHBOARD_MIGRATIONS_DIR` -to the tracked migrations directory. The current driver is `bun:sqlite`, which -keeps this repo installable in the Bun workflow. The store boundary is isolated -so a later Postgres driver can replace the SQLite connection without changing -the dashboard model or renderer. +SQLite files under `data/` and `apps/*/data/` are ignored. Drizzle schema lives +in `apps/web/src/lib/server/db/schema.ts`; tracked migrations live in +`apps/web/drizzle/`. Runtime startup applies the checked-in dashboard migrations +before reads or writes. If the app is launched from outside the web app tree, +set `DASHBOARD_MIGRATIONS_DIR` to the tracked migrations directory. The current +driver is `bun:sqlite`, which keeps this repo installable in the Bun workflow. +The store boundary is isolated so a later Postgres driver can replace the +SQLite connection without changing the dashboard model or renderer. Stored dashboard documents pass through a version migration boundary before reads or writes; the MVP supports `dashboard.v1` and fails unsupported versions with an explicit migration error. @@ -55,25 +64,26 @@ with an explicit migration error. ## Seed Data The initial Dimension Lab dashboard lives in -`src/lib/model/fixtures/dimensionlab.ts` as validated model data. It includes -the first-screen telemetry, service groups, status strip, weather module, -Iconify icon identifiers, links, and datasource references. Values that are not -live yet are labeled as fallback values in the data so later datasource adapters -can replace them without changing presentation components. +`apps/web/src/lib/model/fixtures/dimensionlab.ts` as validated model data. It +includes the first-screen telemetry, service groups, status strip, weather +module, Iconify icon identifiers, links, and datasource references. Values that +are not live yet are labeled as fallback values in the data so later datasource +adapters can replace them without changing presentation components. ## Runtime Shape -The browser app is built with Vite and React. Local development starts Vite for -HMR and a loopback Bun API server for `/api/*` routes. Production uses a small -Bun HTTP server at `build/index.js` to serve the Vite `dist/` assets and JSON -API routes. The current persistence runtime is Bun because the MVP SQLite -driver is `bun:sqlite`. +The browser app in `apps/web` is built with Vite and React. Local development +starts Vite for HMR and a loopback Bun API server for `/api/*` routes. +Production uses a small Bun HTTP server at `apps/web/build/index.js` to serve +the Vite `apps/web/dist/` assets and JSON API routes. The current persistence +runtime is Bun because the MVP SQLite driver is `bun:sqlite`. ## Storybook -Storybook covers the reusable UI components with generic fixtures only. Stories -must not import environment-specific dashboard content; the presentation layer -accepts labels, values, icons, status, and links through typed props. +Storybook lives with `packages/ui` and covers the reusable UI components with +generic fixtures only. Stories must not import environment-specific dashboard +content; the presentation layer accepts labels, values, icons, status, and links +through typed props. ## MVP QA Gate @@ -106,27 +116,29 @@ belong in validated model data, not reusable components. ## Deployment Notes -The production build emits Vite client assets under `dist/` and a Bun server -entry at `build/index.js`. A minimal deployment flow is: +The production build emits Vite client assets under `apps/web/dist/` and a Bun +server entry at `apps/web/build/index.js`. A minimal deployment flow is: ```sh bun install --frozen-lockfile bun run build +cd apps/web DATABASE_URL=file:/data/dimensionlab.sqlite HOST=0.0.0.0 PORT=3000 bun build/index.js ``` Mount `/data` or set `DATABASE_URL` to another persistent SQLite path. If the process starts outside the repository root, set `DASHBOARD_MIGRATIONS_DIR` to -the checked-in `drizzle/` directory so startup migrations can run. +the checked-in `apps/web/drizzle/` directory so startup migrations can run. ### Internal Container -The checked-in `Containerfile` builds the React client and Bun server into a -runtime image. For the Dimension Lab internal host, run it behind Caddy on a -loopback port and mount persistent state at `/data`: +The checked-in `apps/web/Containerfile` builds the React client and Bun server +from the workspace root into a runtime image. For the Dimension Lab internal +host, run it behind Caddy on a loopback port and mount persistent state at +`/data`: ```sh -podman build -t localhost/dimensionlab-website:latest . +podman build -f apps/web/Containerfile -t localhost/dimensionlab-website:latest . podman run --rm \ --publish 127.0.0.1:25341:3000 \ --volume "$HOME/containers/dimensionlab-website/data:/data:Z" \ @@ -137,4 +149,4 @@ podman run --rm \ The env file must provide `AGENT_CONFIG_TOKEN`. Runtime defaults inside the image set `HOST=0.0.0.0`, `PORT=3000`, `DATABASE_URL=file:/data/dimensionlab.sqlite`, and -`DASHBOARD_MIGRATIONS_DIR=/app/drizzle`. +`DASHBOARD_MIGRATIONS_DIR=/repo/apps/web/drizzle`. diff --git a/apps/web/Containerfile b/apps/web/Containerfile new file mode 100644 index 0000000..912f780 --- /dev/null +++ b/apps/web/Containerfile @@ -0,0 +1,32 @@ +FROM docker.io/oven/bun:1.3.14 AS deps + +WORKDIR /repo +ENV PATH=/repo/apps/web/node_modules/.bin:/repo/packages/ui/node_modules/.bin:/repo/node_modules/.bin:$PATH +COPY package.json bun.lock turbo.json tsconfig.base.json ./ +COPY apps/web/package.json apps/web/package.json +COPY packages/ui/package.json packages/ui/package.json +RUN bun install --frozen-lockfile + +FROM deps AS build + +COPY . . +RUN bun install --frozen-lockfile && bun run build + +FROM deps AS runtime + +WORKDIR /repo/apps/web +ENV NODE_ENV=production +ENV HOST=0.0.0.0 +ENV PORT=3000 +ENV DATABASE_URL=file:/data/dimensionlab.sqlite +ENV DASHBOARD_MIGRATIONS_DIR=/repo/apps/web/drizzle + +COPY --from=build /repo/apps/web/build ./build +COPY --from=build /repo/apps/web/dist ./dist +COPY --from=build /repo/apps/web/drizzle ./drizzle + +RUN mkdir -p /data +VOLUME ["/data"] +EXPOSE 3000 + +CMD ["bun", "build/index.js"] diff --git a/drizzle.config.ts b/apps/web/drizzle.config.ts similarity index 100% rename from drizzle.config.ts rename to apps/web/drizzle.config.ts diff --git a/drizzle/0000_dashboard_persistence.sql b/apps/web/drizzle/0000_dashboard_persistence.sql similarity index 100% rename from drizzle/0000_dashboard_persistence.sql rename to apps/web/drizzle/0000_dashboard_persistence.sql diff --git a/drizzle/meta/0000_snapshot.json b/apps/web/drizzle/meta/0000_snapshot.json similarity index 100% rename from drizzle/meta/0000_snapshot.json rename to apps/web/drizzle/meta/0000_snapshot.json diff --git a/drizzle/meta/_journal.json b/apps/web/drizzle/meta/_journal.json similarity index 100% rename from drizzle/meta/_journal.json rename to apps/web/drizzle/meta/_journal.json diff --git a/index.html b/apps/web/index.html similarity index 100% rename from index.html rename to apps/web/index.html diff --git a/apps/web/package.json b/apps/web/package.json index 285a94b..7875aa8 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --host 0.0.0.0", + "dev": "bun src/server/dev.ts", "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", "check": "tsc --noEmit", @@ -22,7 +22,8 @@ "ajv-formats": "^3.0.1", "drizzle-orm": "^0.45.2", "react": "^19.2.7", - "react-dom": "^19.2.7" + "react-dom": "^19.2.7", + "tw-animate-css": "^1.4.0" }, "devDependencies": { "@axe-core/playwright": "^4.11.3", @@ -33,6 +34,7 @@ "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.2", + "bun-types": "^1.3.14", "drizzle-kit": "^0.31.10", "msw": "^2.14.6", "shadcn": "^4.11.0", diff --git a/playwright.config.ts b/apps/web/playwright.config.ts similarity index 90% rename from playwright.config.ts rename to apps/web/playwright.config.ts index 3593452..afba557 100644 --- a/playwright.config.ts +++ b/apps/web/playwright.config.ts @@ -27,7 +27,7 @@ export default defineConfig({ timeout: 120_000, }, { - command: `bun run build-storybook && STORYBOOK_STATIC_PORT=${storybookPort} bun tests/e2e/storybook-server.ts`, + command: `cd ../.. && bun run build-storybook && STORYBOOK_STATIC_PORT=${storybookPort} bun apps/web/tests/e2e/storybook-server.ts`, url: storybookURL, reuseExistingServer: false, timeout: 120_000, diff --git a/src/App.test.tsx b/apps/web/src/App.test.tsx similarity index 100% rename from src/App.test.tsx rename to apps/web/src/App.test.tsx diff --git a/src/App.tsx b/apps/web/src/App.tsx similarity index 97% rename from src/App.tsx rename to apps/web/src/App.tsx index 99cdc5e..0099173 100644 --- a/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,15 +1,15 @@ import { useEffect, useState } from "react"; import type { DashboardRuntimeState } from "$lib/server/dashboard"; +import { dashboardDocumentToUiDashboard } from "$lib/ui-adapter/model-renderer"; import { DashboardFrame, SystemState, ThemeToggle, - dashboardDocumentToUiDashboard, persistUiTheme, resolveInitialUiTheme, type UiTheme, type UiSeverity, -} from "$lib/ui"; +} from "@dimensionlab/ui"; const loadingDashboardState: DashboardRuntimeState = { state: "loading", diff --git a/src/app.css b/apps/web/src/app.css similarity index 97% rename from src/app.css rename to apps/web/src/app.css index 0345394..eeaabcb 100644 --- a/src/app.css +++ b/apps/web/src/app.css @@ -1,9 +1,7 @@ @import "tailwindcss"; -@import "./lib/ui/tokens.css"; -@import "./lib/ui/components/styles.css"; +@import "@dimensionlab/ui/styles.css"; @import "tw-animate-css"; @import "shadcn/tailwind.css"; -@import "@fontsource-variable/geist"; @custom-variant dark (&:is(.dark *)); diff --git a/src/lib/model/fixtures/dimensionlab.test.ts b/apps/web/src/lib/model/fixtures/dimensionlab.test.ts similarity index 100% rename from src/lib/model/fixtures/dimensionlab.test.ts rename to apps/web/src/lib/model/fixtures/dimensionlab.test.ts diff --git a/src/lib/model/fixtures/dimensionlab.ts b/apps/web/src/lib/model/fixtures/dimensionlab.ts similarity index 100% rename from src/lib/model/fixtures/dimensionlab.ts rename to apps/web/src/lib/model/fixtures/dimensionlab.ts diff --git a/src/lib/model/fixtures/generic.ts b/apps/web/src/lib/model/fixtures/generic.ts similarity index 100% rename from src/lib/model/fixtures/generic.ts rename to apps/web/src/lib/model/fixtures/generic.ts diff --git a/src/lib/model/fixtures/index.ts b/apps/web/src/lib/model/fixtures/index.ts similarity index 100% rename from src/lib/model/fixtures/index.ts rename to apps/web/src/lib/model/fixtures/index.ts diff --git a/src/lib/model/index.ts b/apps/web/src/lib/model/index.ts similarity index 100% rename from src/lib/model/index.ts rename to apps/web/src/lib/model/index.ts diff --git a/src/lib/model/schema.test.ts b/apps/web/src/lib/model/schema.test.ts similarity index 100% rename from src/lib/model/schema.test.ts rename to apps/web/src/lib/model/schema.test.ts diff --git a/src/lib/model/schema.ts b/apps/web/src/lib/model/schema.ts similarity index 100% rename from src/lib/model/schema.ts rename to apps/web/src/lib/model/schema.ts diff --git a/src/lib/model/validation.ts b/apps/web/src/lib/model/validation.ts similarity index 100% rename from src/lib/model/validation.ts rename to apps/web/src/lib/model/validation.ts diff --git a/src/lib/presentation-boundary.test.ts b/apps/web/src/lib/presentation-boundary.test.ts similarity index 64% rename from src/lib/presentation-boundary.test.ts rename to apps/web/src/lib/presentation-boundary.test.ts index 8e29ece..7daf204 100644 --- a/src/lib/presentation-boundary.test.ts +++ b/apps/web/src/lib/presentation-boundary.test.ts @@ -2,9 +2,17 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, test } from "vitest"; +const appRoot = process.cwd().endsWith(`${join("apps", "web")}`) + ? process.cwd() + : join(process.cwd(), "apps", "web"); +const repoRoot = existsSync(join(process.cwd(), "turbo.json")) + ? process.cwd() + : join(appRoot, "..", ".."); const presentationRoots = [ - join(process.cwd(), "src", "lib", "ui"), - join(process.cwd(), "src", "routes"), + join(repoRoot, "packages", "ui", "src"), + join(appRoot, "src", "App.tsx"), + join(appRoot, "src", "app.css"), + join(appRoot, "src", "lib", "ui-adapter"), ]; const forbiddenTerms = [ @@ -26,7 +34,9 @@ const forbiddenTerms = [ describe("presentation content boundary", () => { test("keeps environment-specific content out of route and UI implementation", () => { - const source = presentationRoots.map(readPresentationSource).join("\n").toLowerCase(); + const source = withoutInternalPackageScope( + presentationRoots.map(readPresentationSource).join("\n").toLowerCase(), + ); expect(forbiddenTerms.filter((term) => source.includes(term))).toEqual([]); }); @@ -34,7 +44,7 @@ describe("presentation content boundary", () => { test("does not keep legacy presentation component files in the React runtime", () => { const legacyExtension = [".sve", "lte"].join(""); - expect(findFiles(join(process.cwd(), "src"), legacyExtension)).toEqual([]); + expect(findFiles(join(appRoot, "src"), legacyExtension)).toEqual([]); }); }); @@ -44,7 +54,9 @@ function readPresentationSource(path: string): string { const stats = statSync(path); if (stats.isFile()) { if (path.endsWith(".test.ts")) return ""; - if (path.includes(`${join("src", "lib", "ui", "stories")}${"/"}`)) return ""; + if (path.includes(`${join("packages", "ui", "src", "stories")}${"/"}`)) { + return ""; + } if (!/\.(tsx|ts|js|mjs|css|json)$/.test(path)) return ""; return readFileSync(path, "utf8"); } @@ -60,3 +72,7 @@ function findFiles(path: string, extension: string): string[] { return readdirSync(path).flatMap((entry) => findFiles(join(path, entry), extension)); } + +function withoutInternalPackageScope(source: string): string { + return source.replaceAll("@dimensionlab/ui", "@internal/ui"); +} diff --git a/src/lib/server/agent-config/agent-config.test.ts b/apps/web/src/lib/server/agent-config/agent-config.test.ts similarity index 100% rename from src/lib/server/agent-config/agent-config.test.ts rename to apps/web/src/lib/server/agent-config/agent-config.test.ts diff --git a/src/lib/server/agent-config/index.ts b/apps/web/src/lib/server/agent-config/index.ts similarity index 100% rename from src/lib/server/agent-config/index.ts rename to apps/web/src/lib/server/agent-config/index.ts diff --git a/src/lib/server/dashboard.test.ts b/apps/web/src/lib/server/dashboard.test.ts similarity index 100% rename from src/lib/server/dashboard.test.ts rename to apps/web/src/lib/server/dashboard.test.ts diff --git a/src/lib/server/dashboard.ts b/apps/web/src/lib/server/dashboard.ts similarity index 100% rename from src/lib/server/dashboard.ts rename to apps/web/src/lib/server/dashboard.ts diff --git a/src/lib/server/datasources/dashboard-datasources.test.ts b/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts similarity index 100% rename from src/lib/server/datasources/dashboard-datasources.test.ts rename to apps/web/src/lib/server/datasources/dashboard-datasources.test.ts diff --git a/src/lib/server/datasources/index.ts b/apps/web/src/lib/server/datasources/index.ts similarity index 100% rename from src/lib/server/datasources/index.ts rename to apps/web/src/lib/server/datasources/index.ts diff --git a/src/lib/server/db/connection.ts b/apps/web/src/lib/server/db/connection.ts similarity index 100% rename from src/lib/server/db/connection.ts rename to apps/web/src/lib/server/db/connection.ts diff --git a/src/lib/server/db/dashboard-store.test.ts b/apps/web/src/lib/server/db/dashboard-store.test.ts similarity index 100% rename from src/lib/server/db/dashboard-store.test.ts rename to apps/web/src/lib/server/db/dashboard-store.test.ts diff --git a/src/lib/server/db/dashboard-store.ts b/apps/web/src/lib/server/db/dashboard-store.ts similarity index 100% rename from src/lib/server/db/dashboard-store.ts rename to apps/web/src/lib/server/db/dashboard-store.ts diff --git a/src/lib/server/db/migrations.ts b/apps/web/src/lib/server/db/migrations.ts similarity index 100% rename from src/lib/server/db/migrations.ts rename to apps/web/src/lib/server/db/migrations.ts diff --git a/src/lib/server/db/model-migrations.test.ts b/apps/web/src/lib/server/db/model-migrations.test.ts similarity index 100% rename from src/lib/server/db/model-migrations.test.ts rename to apps/web/src/lib/server/db/model-migrations.test.ts diff --git a/src/lib/server/db/model-migrations.ts b/apps/web/src/lib/server/db/model-migrations.ts similarity index 100% rename from src/lib/server/db/model-migrations.ts rename to apps/web/src/lib/server/db/model-migrations.ts diff --git a/src/lib/server/db/schema.ts b/apps/web/src/lib/server/db/schema.ts similarity index 100% rename from src/lib/server/db/schema.ts rename to apps/web/src/lib/server/db/schema.ts diff --git a/src/lib/testing/external-api-mocks.test.ts b/apps/web/src/lib/testing/external-api-mocks.test.ts similarity index 100% rename from src/lib/testing/external-api-mocks.test.ts rename to apps/web/src/lib/testing/external-api-mocks.test.ts diff --git a/src/lib/testing/external-api-mocks.ts b/apps/web/src/lib/testing/external-api-mocks.ts similarity index 100% rename from src/lib/testing/external-api-mocks.ts rename to apps/web/src/lib/testing/external-api-mocks.ts diff --git a/src/lib/ui/model-renderer.test.ts b/apps/web/src/lib/ui-adapter/model-renderer.test.ts similarity index 89% rename from src/lib/ui/model-renderer.test.ts rename to apps/web/src/lib/ui-adapter/model-renderer.test.ts index 62942ee..fb04ec4 100644 --- a/src/lib/ui/model-renderer.test.ts +++ b/apps/web/src/lib/ui-adapter/model-renderer.test.ts @@ -1,13 +1,15 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, test } from "vitest"; -import { - type DashboardDocument, -} from "$lib/model"; +import { type DashboardDocument } from "$lib/model"; import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; import { genericDashboardFixture } from "$lib/model/fixtures/generic"; import { dashboardDocumentToUiDashboard } from "./model-renderer"; +const appRoot = process.cwd().endsWith(`${join("apps", "web")}`) + ? process.cwd() + : join(process.cwd(), "apps", "web"); + describe("dashboard model renderer", () => { test("projects the Dimension Lab model into UI component props", () => { const dashboard = dashboardDocumentToUiDashboard(dimensionLabDashboardFixture); @@ -86,7 +88,12 @@ describe("dashboard model renderer", () => { }); test("does not hardcode environment-specific content in mapper source", () => { - const source = readFileSync(join(process.cwd(), "src/lib/ui/model-renderer.ts"), "utf8").toLowerCase(); + const source = readFileSync( + join(appRoot, "src/lib/ui-adapter/model-renderer.ts"), + "utf8", + ) + .toLowerCase() + .replaceAll("@dimensionlab/ui", "@internal/ui"); expect(source).not.toContain("dimension"); expect(source).not.toContain("vaultwarden"); diff --git a/src/lib/ui/model-renderer.ts b/apps/web/src/lib/ui-adapter/model-renderer.ts similarity index 98% rename from src/lib/ui/model-renderer.ts rename to apps/web/src/lib/ui-adapter/model-renderer.ts index d5f3647..1db2173 100644 --- a/src/lib/ui/model-renderer.ts +++ b/apps/web/src/lib/ui-adapter/model-renderer.ts @@ -14,7 +14,7 @@ import type { UiServiceRow, UiStatusItem, UiTelemetryCard, -} from "./types"; +} from "@dimensionlab/ui"; export function dashboardDocumentToUiDashboard( document: DashboardDocument, diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 21c4020..66a11c5 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -2,7 +2,9 @@ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, test } from "vitest"; -const root = process.cwd(); +const root = existsSync(join(process.cwd(), "turbo.json")) + ? process.cwd() + : join(process.cwd(), "..", ".."); describe("workspace boundaries", () => { test("declares the root as a turbo-managed bun workspace", () => { diff --git a/src/main.tsx b/apps/web/src/main.tsx similarity index 100% rename from src/main.tsx rename to apps/web/src/main.tsx diff --git a/src/page.test.tsx b/apps/web/src/page.test.tsx similarity index 100% rename from src/page.test.tsx rename to apps/web/src/page.test.tsx diff --git a/src/server/dev.test.ts b/apps/web/src/server/dev.test.ts similarity index 100% rename from src/server/dev.test.ts rename to apps/web/src/server/dev.test.ts diff --git a/src/server/dev.ts b/apps/web/src/server/dev.ts similarity index 100% rename from src/server/dev.ts rename to apps/web/src/server/dev.ts diff --git a/src/server/index.ts b/apps/web/src/server/index.ts similarity index 100% rename from src/server/index.ts rename to apps/web/src/server/index.ts diff --git a/src/server/routes/agent-dashboard.test.ts b/apps/web/src/server/routes/agent-dashboard.test.ts similarity index 100% rename from src/server/routes/agent-dashboard.test.ts rename to apps/web/src/server/routes/agent-dashboard.test.ts diff --git a/src/server/routes/agent-dashboard.ts b/apps/web/src/server/routes/agent-dashboard.ts similarity index 100% rename from src/server/routes/agent-dashboard.ts rename to apps/web/src/server/routes/agent-dashboard.ts diff --git a/src/server/routes/dashboard.test.ts b/apps/web/src/server/routes/dashboard.test.ts similarity index 100% rename from src/server/routes/dashboard.test.ts rename to apps/web/src/server/routes/dashboard.test.ts diff --git a/src/server/routes/dashboard.ts b/apps/web/src/server/routes/dashboard.ts similarity index 100% rename from src/server/routes/dashboard.ts rename to apps/web/src/server/routes/dashboard.ts diff --git a/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts similarity index 100% rename from src/vite-env.d.ts rename to apps/web/src/vite-env.d.ts diff --git a/static/mockServiceWorker.js b/apps/web/static/mockServiceWorker.js similarity index 100% rename from static/mockServiceWorker.js rename to apps/web/static/mockServiceWorker.js diff --git a/tests/e2e/dashboard.spec.ts b/apps/web/tests/e2e/dashboard.spec.ts similarity index 100% rename from tests/e2e/dashboard.spec.ts rename to apps/web/tests/e2e/dashboard.spec.ts diff --git a/tests/e2e/dashboard.spec.ts-snapshots/dashboard-desktop-chromium-desktop-linux.png b/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-desktop-chromium-desktop-linux.png similarity index 100% rename from tests/e2e/dashboard.spec.ts-snapshots/dashboard-desktop-chromium-desktop-linux.png rename to apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-desktop-chromium-desktop-linux.png diff --git a/tests/e2e/dashboard.spec.ts-snapshots/dashboard-light-desktop-chromium-desktop-linux.png b/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-light-desktop-chromium-desktop-linux.png similarity index 100% rename from tests/e2e/dashboard.spec.ts-snapshots/dashboard-light-desktop-chromium-desktop-linux.png rename to apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-light-desktop-chromium-desktop-linux.png diff --git a/tests/e2e/dashboard.spec.ts-snapshots/dashboard-mobile-chromium-mobile-linux.png b/apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-mobile-chromium-mobile-linux.png similarity index 100% rename from tests/e2e/dashboard.spec.ts-snapshots/dashboard-mobile-chromium-mobile-linux.png rename to apps/web/tests/e2e/dashboard.spec.ts-snapshots/dashboard-mobile-chromium-mobile-linux.png diff --git a/tests/e2e/storybook-server.ts b/apps/web/tests/e2e/storybook-server.ts similarity index 82% rename from tests/e2e/storybook-server.ts rename to apps/web/tests/e2e/storybook-server.ts index e6e3f63..7544654 100644 --- a/tests/e2e/storybook-server.ts +++ b/apps/web/tests/e2e/storybook-server.ts @@ -1,11 +1,13 @@ import { existsSync } from "node:fs"; import { resolve, sep } from "node:path"; -const root = resolve(process.cwd(), "storybook-static"); +const root = resolve(process.cwd(), "packages/ui/storybook-static"); const port = Number(process.env.STORYBOOK_STATIC_PORT || 6007); if (!existsSync(resolve(root, "iframe.html"))) { - throw new Error("storybook-static is missing. Run bun run build-storybook first."); + throw new Error( + "packages/ui/storybook-static is missing. Run bun run build-storybook first.", + ); } Bun.serve({ diff --git a/tests/e2e/storybook.spec.ts b/apps/web/tests/e2e/storybook.spec.ts similarity index 100% rename from tests/e2e/storybook.spec.ts rename to apps/web/tests/e2e/storybook.spec.ts diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 120931b..edfa07d 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -3,6 +3,8 @@ "compilerOptions": { "baseUrl": ".", "paths": { + "@dimensionlab/ui": ["../../packages/ui/src/index.ts"], + "@dimensionlab/ui/styles.css": ["../../packages/ui/src/styles.css"], "$lib/*": ["src/lib/*"] }, "types": ["node", "bun-types", "react", "react-dom", "vite/client"] diff --git a/vite.config.ts b/apps/web/vite.config.ts similarity index 76% rename from vite.config.ts rename to apps/web/vite.config.ts index ceff746..d88ee57 100644 --- a/vite.config.ts +++ b/apps/web/vite.config.ts @@ -24,6 +24,12 @@ export default defineConfig({ plugins: [react(), tailwindcss()], resolve: { alias: { + "@dimensionlab/ui/styles.css": fileURLToPath( + new URL("../../packages/ui/src/styles.css", import.meta.url), + ), + "@dimensionlab/ui": fileURLToPath( + new URL("../../packages/ui/src/index.ts", import.meta.url), + ), $lib: fileURLToPath(new URL("./src/lib", import.meta.url)), }, }, diff --git a/bun.lock b/bun.lock index 36ab7d6..543520d 100644 --- a/bun.lock +++ b/bun.lock @@ -4,45 +4,75 @@ "workspaces": { "": { "name": "dimensionlab-website", + "devDependencies": { + "turbo": "^2.5.0", + }, + }, + "apps/web": { + "name": "@dimensionlab/web", + "version": "0.0.1", "dependencies": { - "@fontsource-variable/geist": "^5.2.9", - "@iconify/react": "^6.0.2", + "@dimensionlab/ui": "workspace:*", "@sinclair/typebox": "^0.34.49", "ajv": "^8.20.0", "ajv-formats": "^3.0.1", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", "drizzle-orm": "^0.45.2", - "lucide-react": "^1.21.0", - "radix-ui": "^1.6.0", "react": "^19.2.7", "react-dom": "^19.2.7", - "tailwind-merge": "^3.6.0", "tw-animate-css": "^1.4.0", - "uplot": "^1.6.32", }, "devDependencies": { "@axe-core/playwright": "^4.11.3", "@playwright/test": "^1.61.0", - "@storybook/addon-a11y": "^10.4.6", - "@storybook/addon-vitest": "^10.4.6", - "@storybook/react-vite": "^10.4.6", "@tailwindcss/vite": "^4.3.1", "@types/bun": "^1.3.14", "@types/node": "^25.9.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.2", + "bun-types": "^1.3.14", "drizzle-kit": "^0.31.10", "msw": "^2.14.6", "shadcn": "^4.11.0", - "storybook": "^10.4.6", "tailwindcss": "^4.3.1", "typescript": "^6.0.3", "vite": "^8.0.16", "vitest": "^4.1.9", }, }, + "packages/ui": { + "name": "@dimensionlab/ui", + "version": "0.0.1", + "dependencies": { + "@fontsource-variable/geist": "^5.2.9", + "@iconify/react": "^6.0.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.21.0", + "radix-ui": "^1.6.0", + "tailwind-merge": "^3.6.0", + "tw-animate-css": "^1.4.0", + "uplot": "^1.6.32", + }, + "devDependencies": { + "@storybook/addon-a11y": "^10.4.6", + "@storybook/addon-vitest": "^10.4.6", + "@storybook/react-vite": "^10.4.6", + "@types/bun": "^1.3.14", + "@types/node": "^25.9.3", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "bun-types": "^1.3.14", + "storybook": "^10.4.6", + "typescript": "^6.0.3", + "vite": "^8.0.16", + "vitest": "^4.1.9", + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + }, + }, }, "packages": { "@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="], @@ -107,6 +137,10 @@ "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + "@dimensionlab/ui": ["@dimensionlab/ui@workspace:packages/ui"], + + "@dimensionlab/web": ["@dimensionlab/web@workspace:apps/web"], + "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.74.3", "", { "dependencies": { "commander": "^11.1.0", "conf": "^10.2.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "enquirer": "^2.4.1", "env-paths": "^2.2.1", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "open": "^8.4.2", "picomatch": "^4.0.4", "systeminformation": "^5.22.11", "undici": "^7.11.0", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-+VjTIiOCApjB0K4a41+SkN18gTetmhU9UN2JD8LHeNUqo/38FmtgvdtWsW/WWN1dyYRGS7XKeAAb/ruy7x1tRw=="], "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], @@ -123,57 +157,57 @@ "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], @@ -587,6 +621,18 @@ "@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="], + "@turbo/darwin-64": ["@turbo/darwin-64@2.9.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-9f27peFu16ur8c0v9nUFUEyBnbKuuFsUTjHFWfmwGfzySBXbHwzU44QhZon6Mznz0cHsIr3984NQj/bVrnGSRw=="], + + "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.9.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9A6TMRq/Ib+QnbhLlgkhOm+624wO4pzSQ/yQviQfWHOlFvaYxdnIAYmu2H6TS6y7kSVL0DvzNe04NbESTOzFVQ=="], + + "@turbo/linux-64": ["@turbo/linux-64@2.9.18", "", { "os": "linux", "cpu": "x64" }, "sha512-zCdIDtz69AnbYh913elJRRoF3QY5aa2HNnf+4rAkc7bQ+tWujiDkCNV7stazOUPggaDvhKIf2Z87qHftTeXSkw=="], + + "@turbo/linux-arm64": ["@turbo/linux-arm64@2.9.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-Va1kXI04naMgYwqv/5Dfa36dTDx8015U7oaQAjrXa45ua9OoFjSV4OmvkML4EmXvUclQHCiBRbY8bvd0jV7eAg=="], + + "@turbo/windows-64": ["@turbo/windows-64@2.9.18", "", { "os": "win32", "cpu": "x64" }, "sha512-m0kDhZANxSNz9ck1ybogFscHabriAsp4eDFNrN/1H5WrgTF7b3VlcPZnhuO3v2+E2KnCbeAc+UUT10BZZHdDKw=="], + + "@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-nUdR8WqoomUys9iIQmG45TMiizJ+5BV8egSeLLZba/AWblyp3fVBcIH1kSE58OtK4g2YzbMJEth6Ttv9w5rqMA=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], @@ -655,7 +701,7 @@ "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -669,7 +715,7 @@ "atomically": ["atomically@1.7.0", "", {}, "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w=="], - "axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], + "axe-core": ["axe-core@4.12.1", "", {}, "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA=="], "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], @@ -819,7 +865,7 @@ "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], - "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -945,7 +991,7 @@ "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], - "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], @@ -975,7 +1021,7 @@ "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], - "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], @@ -1101,7 +1147,7 @@ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], - "open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], @@ -1331,6 +1377,8 @@ "tsx": ["tsx@4.22.4", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="], + "turbo": ["turbo@2.9.18", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.18", "@turbo/darwin-arm64": "2.9.18", "@turbo/linux-64": "2.9.18", "@turbo/linux-arm64": "2.9.18", "@turbo/windows-64": "2.9.18", "@turbo/windows-arm64": "2.9.18" }, "bin": { "turbo": "bin/turbo" } }, "sha512-bwabv6PupzeavybzEoArBAkwq5fnzwf8OFnRtpHwnviFWuwJPFxtyH+aVp36TmIqK3aYYgtTJ3J0m2ysxxSzQg=="], + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], "type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="], @@ -1387,7 +1435,7 @@ "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], - "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], @@ -1405,6 +1453,8 @@ "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + "@axe-core/playwright/axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -1429,8 +1479,6 @@ "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - "@storybook/addon-a11y/axe-core": ["axe-core@4.12.1", "", {}, "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], @@ -1467,12 +1515,12 @@ "dot-prop/is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], + "drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "enquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "is-inside-container/is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], - "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], @@ -1489,8 +1537,6 @@ "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], - "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], "redent/strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], @@ -1501,23 +1547,19 @@ "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], - "storybook/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], - - "storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "shadcn/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], - "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], "vitest/@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="], "vitest/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="], - "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "wsl-utils/is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], @@ -1533,6 +1575,10 @@ "@dotenvx/dotenvx/open/define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], + "@dotenvx/dotenvx/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "@dotenvx/dotenvx/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -1585,122 +1631,68 @@ "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "drizzle-kit/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "drizzle-kit/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "drizzle-kit/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "drizzle-kit/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "drizzle-kit/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "drizzle-kit/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "drizzle-kit/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "drizzle-kit/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "drizzle-kit/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "drizzle-kit/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "drizzle-kit/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "drizzle-kit/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "drizzle-kit/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "drizzle-kit/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "drizzle-kit/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "drizzle-kit/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "drizzle-kit/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "drizzle-kit/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "drizzle-kit/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "drizzle-kit/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "drizzle-kit/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "drizzle-kit/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "enquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], - "storybook/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], - - "storybook/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], - - "storybook/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], - - "storybook/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], - - "storybook/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], - - "storybook/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], - - "storybook/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], - - "storybook/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], - - "storybook/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], - - "storybook/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], - - "storybook/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], - - "storybook/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], - - "storybook/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], - - "storybook/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], - - "storybook/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], - - "storybook/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], - - "storybook/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], - - "storybook/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], - - "storybook/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], - - "storybook/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], - - "storybook/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], - - "storybook/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], - - "storybook/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], - - "storybook/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], - - "storybook/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], - - "storybook/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], - - "storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + "shadcn/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], - - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], - - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], - - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], - - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], - - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], - - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], - - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], - - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], - - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], - - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], - - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], - - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], - - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], - - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], - - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], - - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], - - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], - - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], - - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], - - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], - - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], - - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], - - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], - - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], - - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], - "vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "storybook/open/wsl-utils/is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], } } diff --git a/components.json b/packages/ui/components.json similarity index 67% rename from components.json rename to packages/ui/components.json index f8d8d59..d951369 100644 --- a/components.json +++ b/packages/ui/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "src/app.css", + "css": "src/styles.css", "baseColor": "neutral", "cssVariables": true, "prefix": "" @@ -13,11 +13,11 @@ "iconLibrary": "lucide", "rtl": false, "aliases": { - "components": "$lib/components", - "utils": "$lib/utils", - "ui": "$lib/components/ui", - "lib": "$lib/.", - "hooks": "$lib/hooks" + "components": "src/components", + "utils": "src/utils", + "ui": "src/primitives", + "lib": "src", + "hooks": "src/hooks" }, "menuColor": "default", "menuAccent": "subtle", diff --git a/packages/ui/package.json b/packages/ui/package.json index 3afa92e..cb7febf 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -43,6 +43,8 @@ "@types/node": "^25.9.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", + "bun-types": "^1.3.14", + "storybook": "^10.4.6", "typescript": "^6.0.3", "vite": "^8.0.16", "vitest": "^4.1.9" diff --git a/packages/ui/src/css.d.ts b/packages/ui/src/css.d.ts new file mode 100644 index 0000000..cbe652d --- /dev/null +++ b/packages/ui/src/css.d.ts @@ -0,0 +1 @@ +declare module "*.css"; diff --git a/tsconfig.base.json b/tsconfig.base.json index 6e74a60..dcde3db 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -13,6 +13,6 @@ "sourceMap": true, "strict": true, "target": "ES2022", - "types": ["node", "bun-types", "react", "react-dom", "vite/client"] + "types": ["node", "bun-types", "react", "react-dom"] } } diff --git a/tsconfig.json b/tsconfig.json index ab92320..57ef1e6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,35 +1,7 @@ { - "compilerOptions": { - "allowJs": true, - "baseUrl": ".", - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "ignoreDeprecations": "6.0", - "jsx": "react-jsx", - "module": "ESNext", - "moduleResolution": "bundler", - "paths": { - "$lib/*": ["src/lib/*"] - }, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "target": "ES2022", - "types": ["node", "bun-types", "react", "react-dom"] - }, - "include": [ - "src/**/*.ts", - "src/**/*.tsx", - "tests/**/*.ts", - "vite.config.ts", - "playwright.config.ts", - "drizzle.config.ts" - ], - "exclude": [ - "build", - "node_modules", - ".storybook" + "files": [], + "references": [ + { "path": "./apps/web" }, + { "path": "./packages/ui" } ] } From e93f4aabb7ed83f61fa7ec8cd6ce2b8b5b030a09 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 05:49:19 +0200 Subject: [PATCH 06/50] fix(scripts): run web app wrappers with cwd --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ba20908..5b94c8f 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "scripts": { "dev": "turbo dev --filter=@dimensionlab/web", "build": "turbo build", - "preview": "bun --cwd apps/web run preview", + "preview": "bun run --cwd apps/web preview", "storybook": "turbo storybook --filter=@dimensionlab/ui", "build-storybook": "turbo build-storybook --filter=@dimensionlab/ui", "check": "turbo check", @@ -19,8 +19,8 @@ "test:unit": "turbo test:unit", "test:e2e": "turbo test:e2e --filter=@dimensionlab/web", "test:qa": "turbo test:qa", - "db:generate": "bun --cwd apps/web run db:generate", - "db:check": "bun --cwd apps/web run db:check" + "db:generate": "bun run --cwd apps/web db:generate", + "db:check": "bun run --cwd apps/web db:check" }, "devDependencies": { "turbo": "^2.5.0" From 72309c0c09b22496e82d2b14afe4800469f43348 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 05:51:18 +0200 Subject: [PATCH 07/50] docs: fix bun workspace command examples --- .../plans/2026-06-20-turbo-component-library.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/superpowers/plans/2026-06-20-turbo-component-library.md b/docs/superpowers/plans/2026-06-20-turbo-component-library.md index e53586c..22d2ad8 100644 --- a/docs/superpowers/plans/2026-06-20-turbo-component-library.md +++ b/docs/superpowers/plans/2026-06-20-turbo-component-library.md @@ -114,7 +114,7 @@ Create root `package.json` as the workspace orchestrator: "scripts": { "dev": "turbo dev --filter=@dimensionlab/web", "build": "turbo build", - "preview": "bun --cwd apps/web run preview", + "preview": "bun run --cwd apps/web preview", "storybook": "turbo storybook --filter=@dimensionlab/ui", "build-storybook": "turbo build-storybook --filter=@dimensionlab/ui", "check": "turbo check", @@ -122,8 +122,8 @@ Create root `package.json` as the workspace orchestrator: "test:unit": "turbo test:unit", "test:e2e": "turbo test:e2e --filter=@dimensionlab/web", "test:qa": "turbo test:qa", - "db:generate": "bun --cwd apps/web run db:generate", - "db:check": "bun --cwd apps/web run db:check" + "db:generate": "bun run --cwd apps/web db:generate", + "db:check": "bun run --cwd apps/web db:check" }, "devDependencies": { "turbo": "^2.5.0" @@ -251,9 +251,9 @@ Remove `dashboardDocumentToUiDashboard` from `packages/ui/src/index.ts`. Run: ```sh -bun --cwd packages/ui run build -bun --cwd packages/ui run check -bun --cwd packages/ui run test:unit +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 @@ -308,8 +308,8 @@ In `apps/web/src/app.css`, replace local UI imports with: Run: ```sh -bun --cwd apps/web run check -bun --cwd apps/web run test:unit +bun run --cwd apps/web check +bun run --cwd apps/web test:unit ``` Expected: PASS. From ae6eabed6f0746a0bad5fa493d07de09dcac6c5e Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 06:10:16 +0200 Subject: [PATCH 08/50] build(turbo): normalize workspace task graph --- README.md | 7 +- apps/web/package.json | 1 - apps/web/playwright.config.ts | 2 +- apps/web/src/lib/workspace-boundary.test.ts | 53 +++++++++++++- package.json | 24 +++---- turbo.json | 80 +++++++++++++++++---- 6 files changed, 136 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 59dedf6..31c1333 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,8 @@ This MVP uses Drizzle with Bun SQLite for local file-backed persistence. - `bun run test`: run the unit test stage in all workspaces. - `bun run test:unit`: run Vitest explicitly as the unit test stage. - `bun run test:e2e`: build and run Playwright browser smoke and QA checks. -- `bun run test:qa`: run the release gate through Turbo. +- `bun run test:qa`: run the release gate through Turbo across check, unit, + build, Storybook, and e2e tasks. - `bun run build`: build the UI package, production website, and Bun server. - `bun run preview`: preview the production web build. - `bun run storybook`: start the UI package component explorer on port 6006. @@ -102,7 +103,9 @@ bun run test:qa The gate runs TypeScript checks, Vitest coverage for model, persistence, renderer, datasource mocks, and presentation boundaries, the production build, the static Storybook build, and Playwright desktop/mobile -smoke checks against the built adapter output. Playwright also performs +smoke checks against the built adapter output. Turbo owns the release task +graph; app package scripts stay as leaf commands and do not re-run the QA +pipeline internally. Playwright also performs baseline screenshot checks, keyboard navigation checks, reduced-motion checks, landmark checks, and axe accessibility checks against the real model-driven route. diff --git a/apps/web/package.json b/apps/web/package.json index 7875aa8..c2c183f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,7 +11,6 @@ "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 test:e2e", "db:generate": "drizzle-kit generate", "db:check": "drizzle-kit check" }, diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts index afba557..0dbd949 100644 --- a/apps/web/playwright.config.ts +++ b/apps/web/playwright.config.ts @@ -27,7 +27,7 @@ export default defineConfig({ timeout: 120_000, }, { - command: `cd ../.. && bun run build-storybook && STORYBOOK_STATIC_PORT=${storybookPort} bun apps/web/tests/e2e/storybook-server.ts`, + command: `cd ../.. && STORYBOOK_STATIC_PORT=${storybookPort} bun apps/web/tests/e2e/storybook-server.ts`, url: storybookURL, reuseExistingServer: false, timeout: 120_000, diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 66a11c5..5707a8c 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -18,10 +18,61 @@ describe("workspace boundaries", () => { expect(packageJson.private).toBe(true); expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]); - expect(packageJson.scripts?.build).toBe("turbo build"); + expect(packageJson.scripts?.build).toBe("turbo run build"); expect(existsSync(join(root, "turbo.json"))).toBe(true); }); + test("keeps release orchestration in the root turbo task graph", () => { + const rootPackage = JSON.parse( + readFileSync(join(root, "package.json"), "utf8"), + ) as { + scripts?: Record; + }; + const webPackage = JSON.parse( + readFileSync(join(root, "apps/web/package.json"), "utf8"), + ) as { scripts?: Record }; + const turboConfig = JSON.parse( + readFileSync(join(root, "turbo.json"), "utf8"), + ) as { + globalDependencies?: string[]; + tasks?: Record; + }; + + expect(rootPackage.scripts?.["test:qa"]).toBe( + "turbo run check test:unit build @dimensionlab/ui#build-storybook @dimensionlab/web#test:e2e", + ); + expect(webPackage.scripts).not.toHaveProperty("test:qa"); + expect(turboConfig.tasks).not.toHaveProperty("test:qa"); + expect(turboConfig.globalDependencies).toEqual( + expect.arrayContaining(["bun.lock", "tsconfig.base.json"]), + ); + expect(turboConfig.tasks?.["test:e2e"]?.env).toEqual( + expect.arrayContaining([ + "CI", + "PLAYWRIGHT_DATABASE_URL", + "PLAYWRIGHT_PORT", + "PLAYWRIGHT_STORYBOOK_PORT", + ]), + ); + }); + + test("lets turbo build Storybook before e2e serves the static artifact", () => { + const playwrightConfig = readFileSync( + join(root, "apps/web/playwright.config.ts"), + "utf8", + ); + const turboConfig = JSON.parse( + readFileSync(join(root, "turbo.json"), "utf8"), + ) as { + tasks?: Record; + }; + + expect(playwrightConfig).not.toContain("bun run build-storybook"); + expect(turboConfig.tasks?.["test:e2e"]?.dependsOn).toEqual( + expect.arrayContaining(["@dimensionlab/ui#build-storybook"]), + ); + }); + test("keeps the website app and reusable UI library as separate packages", () => { const webPackage = JSON.parse( readFileSync(join(root, "apps/web/package.json"), "utf8"), diff --git a/package.json b/package.json index 5b94c8f..e33d3aa 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,18 @@ "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" + "dev": "turbo run dev --filter=@dimensionlab/web", + "build": "turbo run build", + "preview": "turbo run preview --filter=@dimensionlab/web", + "storybook": "turbo run storybook --filter=@dimensionlab/ui", + "build-storybook": "turbo run build-storybook --filter=@dimensionlab/ui", + "check": "turbo run check", + "test": "turbo run test:unit", + "test:unit": "turbo run test:unit", + "test:e2e": "turbo run test:e2e --filter=@dimensionlab/web", + "test:qa": "turbo run check test:unit build @dimensionlab/ui#build-storybook @dimensionlab/web#test:e2e", + "db:generate": "turbo run db:generate --filter=@dimensionlab/web", + "db:check": "turbo run db:check --filter=@dimensionlab/web" }, "devDependencies": { "turbo": "^2.5.0" diff --git a/turbo.json b/turbo.json index ee027a8..446d6fe 100644 --- a/turbo.json +++ b/turbo.json @@ -1,9 +1,16 @@ { "$schema": "https://turbo.build/schema.json", + "globalDependencies": [ + "bun.lock", + "package.json", + "tsconfig.base.json", + "tsconfig.json" + ], "tasks": { "build": { "dependsOn": ["^build"], - "outputs": ["dist/**", "build/**"] + "outputs": ["dist/**", "build/**"], + "env": ["NODE_ENV", "VITE_*"] }, "check": { "dependsOn": ["^build"], @@ -11,33 +18,78 @@ }, "test:unit": { "dependsOn": ["^build"], - "outputs": [] + "outputs": [], + "env": [ + "AGENT_CONFIG_TOKEN", + "DASHBOARD_MIGRATIONS_DIR", + "DATABASE_URL", + "DISABLE_LIVE_DATASOURCES", + "PROMETHEUS_BASE_URL" + ] }, "build-storybook": { "dependsOn": ["^build"], - "outputs": ["storybook-static/**"] + "outputs": ["storybook-static/**"], + "env": ["NODE_ENV", "STORYBOOK_*", "VITE_*"] }, "test:e2e": { - "dependsOn": ["build", "^build"], - "outputs": ["test-results/**", "playwright-report/**"] - }, - "test:qa": { "dependsOn": [ - "check", - "test:unit", "build", - "build-storybook", - "test:e2e" + "^build", + "@dimensionlab/ui#build-storybook" ], - "outputs": [] + "outputs": ["test-results/**", "playwright-report/**"], + "env": [ + "AGENT_CONFIG_TOKEN", + "CI", + "DASHBOARD_MIGRATIONS_DIR", + "DATABASE_URL", + "DISABLE_LIVE_DATASOURCES", + "PLAYWRIGHT_DATABASE_URL", + "PLAYWRIGHT_PORT", + "PLAYWRIGHT_STORYBOOK_PORT", + "PROMETHEUS_BASE_URL", + "STORYBOOK_STATIC_PORT" + ] + }, + "db:generate": { + "cache": false + }, + "db:check": { + "outputs": [], + "env": ["DATABASE_URL"] }, "dev": { "cache": false, - "persistent": true + "persistent": true, + "env": [ + "AGENT_CONFIG_TOKEN", + "DASHBOARD_DEV_API_HOST", + "DASHBOARD_DEV_API_PORT", + "DASHBOARD_DEV_API_TARGET", + "DASHBOARD_MIGRATIONS_DIR", + "DATABASE_URL", + "HOST", + "PORT", + "PROMETHEUS_BASE_URL" + ] + }, + "preview": { + "cache": false, + "persistent": true, + "env": [ + "AGENT_CONFIG_TOKEN", + "DASHBOARD_MIGRATIONS_DIR", + "DATABASE_URL", + "HOST", + "PORT", + "PROMETHEUS_BASE_URL" + ] }, "storybook": { "cache": false, - "persistent": true + "persistent": true, + "env": ["HOST", "PORT", "STORYBOOK_*", "VITE_*"] } } } From c8183bcc474fad99a59930fc3becd3876a04b4f3 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 06:16:41 +0200 Subject: [PATCH 09/50] fix(turbo): rely on graph build before e2e --- apps/web/playwright.config.ts | 2 +- apps/web/src/lib/workspace-boundary.test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts index 0dbd949..ac0fe5e 100644 --- a/apps/web/playwright.config.ts +++ b/apps/web/playwright.config.ts @@ -21,7 +21,7 @@ export default defineConfig({ }, webServer: [ { - 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`, + command: `DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`, url: baseURL, reuseExistingServer: false, timeout: 120_000, diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 5707a8c..51652dc 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -56,7 +56,7 @@ describe("workspace boundaries", () => { ); }); - test("lets turbo build Storybook before e2e serves the static artifact", () => { + test("lets turbo build app and Storybook artifacts before e2e serves them", () => { const playwrightConfig = readFileSync( join(root, "apps/web/playwright.config.ts"), "utf8", @@ -67,6 +67,7 @@ describe("workspace boundaries", () => { tasks?: Record; }; + expect(playwrightConfig).not.toContain("bun run build &&"); expect(playwrightConfig).not.toContain("bun run build-storybook"); expect(turboConfig.tasks?.["test:e2e"]?.dependsOn).toEqual( expect.arrayContaining(["@dimensionlab/ui#build-storybook"]), From 7a6ad8ab0ec9fe806b4aba37261af9bfaed1abdd Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 06:26:19 +0200 Subject: [PATCH 10/50] refactor(model): extract dashboard model package --- README.md | 9 ++- apps/web/package.json | 1 + .../dimensionlab.test.ts | 2 +- .../dimensionlab.ts | 2 +- apps/web/src/lib/model/fixtures/index.ts | 2 - .../web/src/lib/presentation-boundary.test.ts | 4 +- .../server/agent-config/agent-config.test.ts | 4 +- apps/web/src/lib/server/agent-config/index.ts | 2 +- apps/web/src/lib/server/dashboard.test.ts | 4 +- apps/web/src/lib/server/dashboard.ts | 4 +- .../datasources/dashboard-datasources.test.ts | 2 +- apps/web/src/lib/server/datasources/index.ts | 2 +- .../src/lib/server/db/dashboard-store.test.ts | 4 +- apps/web/src/lib/server/db/dashboard-store.ts | 2 +- .../lib/server/db/model-migrations.test.ts | 4 +- .../web/src/lib/server/db/model-migrations.ts | 2 +- apps/web/src/lib/server/db/schema.ts | 2 +- .../src/lib/ui-adapter/model-renderer.test.ts | 7 ++- apps/web/src/lib/ui-adapter/model-renderer.ts | 2 +- apps/web/src/lib/workspace-boundary.test.ts | 25 ++++++++ apps/web/src/page.test.tsx | 2 +- apps/web/src/server/routes/dashboard.test.ts | 2 +- apps/web/tsconfig.json | 4 ++ apps/web/vite.config.ts | 6 ++ bun.lock | 57 ++++++++++++------- packages/dashboard-model/package.json | 34 +++++++++++ .../dashboard-model/src}/fixtures/generic.ts | 0 .../dashboard-model/src/fixtures/index.ts | 1 + .../dashboard-model/src}/index.ts | 0 .../dashboard-model/src}/schema.test.ts | 7 --- .../dashboard-model/src}/schema.ts | 0 .../dashboard-model/src}/validation.ts | 0 packages/dashboard-model/tsconfig.build.json | 12 ++++ packages/dashboard-model/tsconfig.json | 9 +++ tsconfig.json | 1 + 35 files changed, 164 insertions(+), 57 deletions(-) rename apps/web/src/lib/{model/fixtures => dashboard-seed}/dimensionlab.test.ts (99%) rename apps/web/src/lib/{model/fixtures => dashboard-seed}/dimensionlab.ts (99%) delete mode 100644 apps/web/src/lib/model/fixtures/index.ts create mode 100644 packages/dashboard-model/package.json rename {apps/web/src/lib/model => packages/dashboard-model/src}/fixtures/generic.ts (100%) create mode 100644 packages/dashboard-model/src/fixtures/index.ts rename {apps/web/src/lib/model => packages/dashboard-model/src}/index.ts (100%) rename {apps/web/src/lib/model => packages/dashboard-model/src}/schema.test.ts (96%) rename {apps/web/src/lib/model => packages/dashboard-model/src}/schema.ts (100%) rename {apps/web/src/lib/model => packages/dashboard-model/src}/validation.ts (100%) create mode 100644 packages/dashboard-model/tsconfig.build.json create mode 100644 packages/dashboard-model/tsconfig.json diff --git a/README.md b/README.md index 31c1333..98dc8ec 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,16 @@ reusable React component library. This project is not a Homepage customization and does not depend on Homepage runtime, frontend code, or configuration. The dashboard will be model-driven: -the reusable UI package stays content-free, while environment-specific data -lives in validated dashboard model state inside the web app. +the reusable UI package stays content-free, the reusable dashboard model +package owns schema and validation, and environment-specific data lives in +validated dashboard model state inside the web app. ## Workspace Layout - `apps/web`: Vite React website, Bun API server, model fixtures, Drizzle persistence, Playwright e2e checks, and container build. +- `packages/dashboard-model`: reusable dashboard schema, validation, and + generic model fixtures shared by apps and tooling. - `packages/ui`: reusable dashboard React components, design tokens, shadcn/radix primitives, generic fixtures, and Storybook. - `docs/superpowers`: migration specs and execution plans used for this repo. @@ -65,7 +68,7 @@ with an explicit migration error. ## Seed Data The initial Dimension Lab dashboard lives in -`apps/web/src/lib/model/fixtures/dimensionlab.ts` as validated model data. It +`apps/web/src/lib/dashboard-seed/dimensionlab.ts` as validated model data. It includes the first-screen telemetry, service groups, status strip, weather module, Iconify icon identifiers, links, and datasource references. Values that are not live yet are labeled as fallback values in the data so later datasource diff --git a/apps/web/package.json b/apps/web/package.json index c2c183f..2e9fef6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,6 +15,7 @@ "db:check": "drizzle-kit check" }, "dependencies": { + "@dimensionlab/dashboard-model": "workspace:*", "@dimensionlab/ui": "workspace:*", "@sinclair/typebox": "^0.34.49", "ajv": "^8.20.0", diff --git a/apps/web/src/lib/model/fixtures/dimensionlab.test.ts b/apps/web/src/lib/dashboard-seed/dimensionlab.test.ts similarity index 99% rename from apps/web/src/lib/model/fixtures/dimensionlab.test.ts rename to apps/web/src/lib/dashboard-seed/dimensionlab.test.ts index 38b2ac6..31c01d9 100644 --- a/apps/web/src/lib/model/fixtures/dimensionlab.test.ts +++ b/apps/web/src/lib/dashboard-seed/dimensionlab.test.ts @@ -7,7 +7,7 @@ import type { DatasourceReference, ServiceEntry, TelemetryCard, -} from "../schema"; +} from "@dimensionlab/dashboard-model"; describe("Dimension Lab dashboard seed", () => { test("defines the primary first-screen sections as model data", () => { diff --git a/apps/web/src/lib/model/fixtures/dimensionlab.ts b/apps/web/src/lib/dashboard-seed/dimensionlab.ts similarity index 99% rename from apps/web/src/lib/model/fixtures/dimensionlab.ts rename to apps/web/src/lib/dashboard-seed/dimensionlab.ts index c2b4808..6cc544b 100644 --- a/apps/web/src/lib/model/fixtures/dimensionlab.ts +++ b/apps/web/src/lib/dashboard-seed/dimensionlab.ts @@ -7,7 +7,7 @@ import { type ServiceGroup, type Severity, type TelemetryCard, -} from "../schema"; +} from "@dimensionlab/dashboard-model"; type NumericValueKind = "percent" | "bytes" | "temperature" | "latency" | "number"; diff --git a/apps/web/src/lib/model/fixtures/index.ts b/apps/web/src/lib/model/fixtures/index.ts deleted file mode 100644 index a04278d..0000000 --- a/apps/web/src/lib/model/fixtures/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { dimensionLabDashboardFixture } from "./dimensionlab"; -export { genericDashboardFixture } from "./generic"; diff --git a/apps/web/src/lib/presentation-boundary.test.ts b/apps/web/src/lib/presentation-boundary.test.ts index 7daf204..8a62205 100644 --- a/apps/web/src/lib/presentation-boundary.test.ts +++ b/apps/web/src/lib/presentation-boundary.test.ts @@ -74,5 +74,7 @@ function findFiles(path: string, extension: string): string[] { } function withoutInternalPackageScope(source: string): string { - return source.replaceAll("@dimensionlab/ui", "@internal/ui"); + return source + .replaceAll("@dimensionlab/dashboard-model", "@internal/dashboard-model") + .replaceAll("@dimensionlab/ui", "@internal/ui"); } diff --git a/apps/web/src/lib/server/agent-config/agent-config.test.ts b/apps/web/src/lib/server/agent-config/agent-config.test.ts index 87449e8..cdc60cf 100644 --- a/apps/web/src/lib/server/agent-config/agent-config.test.ts +++ b/apps/web/src/lib/server/agent-config/agent-config.test.ts @@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, test } from "vitest"; -import type { DashboardDocument } from "$lib/model"; -import { genericDashboardFixture } from "$lib/model/fixtures/generic"; +import type { DashboardDocument } from "@dimensionlab/dashboard-model"; +import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures"; import { createDashboardStore, type DashboardStore } from "$lib/server/db/dashboard-store"; import { AgentConfigAuthorizationError, diff --git a/apps/web/src/lib/server/agent-config/index.ts b/apps/web/src/lib/server/agent-config/index.ts index ffa7dd8..ab3d0ae 100644 --- a/apps/web/src/lib/server/agent-config/index.ts +++ b/apps/web/src/lib/server/agent-config/index.ts @@ -16,7 +16,7 @@ import { type ServiceGroup, type StatusItem, type TelemetryCard, -} from "$lib/model"; +} from "@dimensionlab/dashboard-model"; import { createDashboardStore, DashboardRevisionNotFoundError, diff --git a/apps/web/src/lib/server/dashboard.test.ts b/apps/web/src/lib/server/dashboard.test.ts index b15fa6c..7851ebb 100644 --- a/apps/web/src/lib/server/dashboard.test.ts +++ b/apps/web/src/lib/server/dashboard.test.ts @@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, test } from "vitest"; -import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; -import { genericDashboardFixture } from "$lib/model/fixtures/generic"; +import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab"; +import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures"; import { createDashboardStore, type DashboardStore } from "./db/dashboard-store"; import { loadDashboardRuntime } from "./dashboard"; diff --git a/apps/web/src/lib/server/dashboard.ts b/apps/web/src/lib/server/dashboard.ts index 7821ee0..47ebfa2 100644 --- a/apps/web/src/lib/server/dashboard.ts +++ b/apps/web/src/lib/server/dashboard.ts @@ -1,5 +1,5 @@ -import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; -import type { DashboardDocument } from "$lib/model"; +import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab"; +import type { DashboardDocument } from "@dimensionlab/dashboard-model"; import { createDashboardStore, DashboardPersistenceValidationError, diff --git a/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts b/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts index 19082a2..61acdd3 100644 --- a/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts +++ b/apps/web/src/lib/server/datasources/dashboard-datasources.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test, vi } from "vitest"; import { DASHBOARD_SCHEMA_VERSION, type DashboardDocument, -} from "$lib/model"; +} from "@dimensionlab/dashboard-model"; import { resolveDashboardDatasources } from "."; describe("dashboard datasource resolution", () => { diff --git a/apps/web/src/lib/server/datasources/index.ts b/apps/web/src/lib/server/datasources/index.ts index 804fbb5..d69db86 100644 --- a/apps/web/src/lib/server/datasources/index.ts +++ b/apps/web/src/lib/server/datasources/index.ts @@ -8,7 +8,7 @@ import type { StatusItem, StatusStrip, TelemetryCard, -} from "$lib/model"; +} from "@dimensionlab/dashboard-model"; export interface DatasourceResolutionOptions { fetch?: DatasourceFetch; diff --git a/apps/web/src/lib/server/db/dashboard-store.test.ts b/apps/web/src/lib/server/db/dashboard-store.test.ts index ecb80f3..c2717f5 100644 --- a/apps/web/src/lib/server/db/dashboard-store.test.ts +++ b/apps/web/src/lib/server/db/dashboard-store.test.ts @@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, test, vi } from "vitest"; -import { genericDashboardFixture } from "$lib/model/fixtures/generic"; -import type { DashboardDocument } from "$lib/model"; +import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures"; +import type { DashboardDocument } from "@dimensionlab/dashboard-model"; import { DashboardPersistenceValidationError, createDashboardStore, diff --git a/apps/web/src/lib/server/db/dashboard-store.ts b/apps/web/src/lib/server/db/dashboard-store.ts index 92c7a73..3928206 100644 --- a/apps/web/src/lib/server/db/dashboard-store.ts +++ b/apps/web/src/lib/server/db/dashboard-store.ts @@ -3,7 +3,7 @@ import { desc, eq } from "drizzle-orm"; import { type DashboardDocument, type DashboardValidationFailure, -} from "$lib/model"; +} from "@dimensionlab/dashboard-model"; import { type DashboardDatabaseConnection, openDashboardDatabase, diff --git a/apps/web/src/lib/server/db/model-migrations.test.ts b/apps/web/src/lib/server/db/model-migrations.test.ts index eb16b8f..5c863a0 100644 --- a/apps/web/src/lib/server/db/model-migrations.test.ts +++ b/apps/web/src/lib/server/db/model-migrations.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { DASHBOARD_SCHEMA_VERSION } from "$lib/model"; -import { genericDashboardFixture } from "$lib/model/fixtures/generic"; +import { DASHBOARD_SCHEMA_VERSION } from "@dimensionlab/dashboard-model"; +import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures"; import { UnsupportedDashboardModelVersionError, migrateDashboardDocumentForPersistence, diff --git a/apps/web/src/lib/server/db/model-migrations.ts b/apps/web/src/lib/server/db/model-migrations.ts index 92550e6..72e9c47 100644 --- a/apps/web/src/lib/server/db/model-migrations.ts +++ b/apps/web/src/lib/server/db/model-migrations.ts @@ -3,7 +3,7 @@ import { validateDashboardDocument, type DashboardDocument, type DashboardValidationFailure, -} from "$lib/model"; +} from "@dimensionlab/dashboard-model"; export interface DashboardModelMigrationSuccess { valid: true; diff --git a/apps/web/src/lib/server/db/schema.ts b/apps/web/src/lib/server/db/schema.ts index c148035..a3a7206 100644 --- a/apps/web/src/lib/server/db/schema.ts +++ b/apps/web/src/lib/server/db/schema.ts @@ -1,4 +1,4 @@ -import type { DashboardDocument } from "$lib/model"; +import type { DashboardDocument } from "@dimensionlab/dashboard-model"; import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; export const dashboardDocuments = sqliteTable("dashboard_documents", { diff --git a/apps/web/src/lib/ui-adapter/model-renderer.test.ts b/apps/web/src/lib/ui-adapter/model-renderer.test.ts index fb04ec4..2f8178d 100644 --- a/apps/web/src/lib/ui-adapter/model-renderer.test.ts +++ b/apps/web/src/lib/ui-adapter/model-renderer.test.ts @@ -1,9 +1,9 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, test } from "vitest"; -import { type DashboardDocument } from "$lib/model"; -import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; -import { genericDashboardFixture } from "$lib/model/fixtures/generic"; +import { type DashboardDocument } from "@dimensionlab/dashboard-model"; +import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab"; +import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures"; import { dashboardDocumentToUiDashboard } from "./model-renderer"; const appRoot = process.cwd().endsWith(`${join("apps", "web")}`) @@ -93,6 +93,7 @@ describe("dashboard model renderer", () => { "utf8", ) .toLowerCase() + .replaceAll("@dimensionlab/dashboard-model", "@internal/dashboard-model") .replaceAll("@dimensionlab/ui", "@internal/ui"); expect(source).not.toContain("dimension"); diff --git a/apps/web/src/lib/ui-adapter/model-renderer.ts b/apps/web/src/lib/ui-adapter/model-renderer.ts index 1db2173..7ac1912 100644 --- a/apps/web/src/lib/ui-adapter/model-renderer.ts +++ b/apps/web/src/lib/ui-adapter/model-renderer.ts @@ -6,7 +6,7 @@ import type { StatusItem, StatusStrip, TelemetryCard, -} from "$lib/model"; +} from "@dimensionlab/dashboard-model"; import type { UiDashboardPreview, UiModuleBlock, diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 51652dc..4edcfc6 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -88,4 +88,29 @@ describe("workspace boundaries", () => { expect(uiPackage.exports).toHaveProperty("."); expect(uiPackage.exports).toHaveProperty("./styles.css"); }); + + test("keeps the dashboard model in a reusable internal package", () => { + const webPackage = JSON.parse( + readFileSync(join(root, "apps/web/package.json"), "utf8"), + ) as { dependencies?: Record }; + const modelPackage = JSON.parse( + readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"), + ) as { exports?: Record; name?: string }; + const tsconfig = JSON.parse( + readFileSync(join(root, "tsconfig.json"), "utf8"), + ) as { references?: Array<{ path: string }> }; + + expect(webPackage.dependencies?.["@dimensionlab/dashboard-model"]).toBe( + "workspace:*", + ); + expect(modelPackage.name).toBe("@dimensionlab/dashboard-model"); + expect(modelPackage.exports).toHaveProperty("."); + expect(modelPackage.exports).toHaveProperty("./fixtures"); + expect(tsconfig.references).toEqual( + expect.arrayContaining([{ path: "./packages/dashboard-model" }]), + ); + expect(existsSync(join(root, "apps/web/src/lib/model/index.ts"))).toBe( + false, + ); + }); }); diff --git a/apps/web/src/page.test.tsx b/apps/web/src/page.test.tsx index fc68147..2ec5869 100644 --- a/apps/web/src/page.test.tsx +++ b/apps/web/src/page.test.tsx @@ -1,6 +1,6 @@ import { renderToString } from "react-dom/server"; import { describe, expect, test } from "vitest"; -import { genericDashboardFixture } from "$lib/model/fixtures/generic"; +import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures"; import { AppStateView, resolveDocumentMetadata } from "./App"; describe("home page model renderer", () => { diff --git a/apps/web/src/server/routes/dashboard.test.ts b/apps/web/src/server/routes/dashboard.test.ts index ebe575b..21ad58d 100644 --- a/apps/web/src/server/routes/dashboard.test.ts +++ b/apps/web/src/server/routes/dashboard.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab"; +import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab"; import { loadDashboardResponse } from "./dashboard"; describe("dashboard API route", () => { diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index edfa07d..f9f784d 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -3,6 +3,10 @@ "compilerOptions": { "baseUrl": ".", "paths": { + "@dimensionlab/dashboard-model": ["../../packages/dashboard-model/src/index.ts"], + "@dimensionlab/dashboard-model/fixtures": [ + "../../packages/dashboard-model/src/fixtures/index.ts" + ], "@dimensionlab/ui": ["../../packages/ui/src/index.ts"], "@dimensionlab/ui/styles.css": ["../../packages/ui/src/styles.css"], "$lib/*": ["src/lib/*"] diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index d88ee57..687c180 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -30,6 +30,12 @@ export default defineConfig({ "@dimensionlab/ui": fileURLToPath( new URL("../../packages/ui/src/index.ts", import.meta.url), ), + "@dimensionlab/dashboard-model/fixtures": fileURLToPath( + new URL("../../packages/dashboard-model/src/fixtures/index.ts", import.meta.url), + ), + "@dimensionlab/dashboard-model": fileURLToPath( + new URL("../../packages/dashboard-model/src/index.ts", import.meta.url), + ), $lib: fileURLToPath(new URL("./src/lib", import.meta.url)), }, }, diff --git a/bun.lock b/bun.lock index 543520d..d6915d0 100644 --- a/bun.lock +++ b/bun.lock @@ -12,6 +12,7 @@ "name": "@dimensionlab/web", "version": "0.0.1", "dependencies": { + "@dimensionlab/dashboard-model": "workspace:*", "@dimensionlab/ui": "workspace:*", "@sinclair/typebox": "^0.34.49", "ajv": "^8.20.0", @@ -40,6 +41,22 @@ "vitest": "^4.1.9", }, }, + "packages/dashboard-model": { + "name": "@dimensionlab/dashboard-model", + "version": "0.0.1", + "dependencies": { + "@sinclair/typebox": "^0.34.49", + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1", + }, + "devDependencies": { + "@types/bun": "^1.3.14", + "@types/node": "^25.9.3", + "bun-types": "^1.3.14", + "typescript": "^6.0.3", + "vitest": "^4.1.9", + }, + }, "packages/ui": { "name": "@dimensionlab/ui", "version": "0.0.1", @@ -137,6 +154,8 @@ "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + "@dimensionlab/dashboard-model": ["@dimensionlab/dashboard-model@workspace:packages/dashboard-model"], + "@dimensionlab/ui": ["@dimensionlab/ui@workspace:packages/ui"], "@dimensionlab/web": ["@dimensionlab/web@workspace:apps/web"], @@ -671,7 +690,7 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.2", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.0" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg=="], - "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + "@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="], "@vitest/mocker": ["@vitest/mocker@4.1.9", "", { "dependencies": { "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw=="], @@ -681,7 +700,7 @@ "@vitest/snapshot": ["@vitest/snapshot@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA=="], - "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + "@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="], "@vitest/utils": ["@vitest/utils@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA=="], @@ -745,7 +764,7 @@ "caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="], - "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -873,7 +892,7 @@ "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], - "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], @@ -1479,6 +1498,8 @@ "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="], @@ -1495,14 +1516,6 @@ "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], - "@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], - - "@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], - - "@vitest/mocker/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="], - - "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - "body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -1549,14 +1562,14 @@ "shadcn/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], + "storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], - "vitest/@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="], - - "vitest/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="], - "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -1625,8 +1638,6 @@ "@oxc-resolver/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], - "@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], - "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], @@ -1689,10 +1700,16 @@ "shadcn/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], + "storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], - "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], } } diff --git a/packages/dashboard-model/package.json b/packages/dashboard-model/package.json new file mode 100644 index 0000000..978522e --- /dev/null +++ b/packages/dashboard-model/package.json @@ -0,0 +1,34 @@ +{ + "name": "@dimensionlab/dashboard-model", + "version": "0.0.1", + "private": true, + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./fixtures": { + "types": "./dist/fixtures/index.d.ts", + "default": "./dist/fixtures/index.js" + } + }, + "scripts": { + "build": "rm -rf dist && tsc -p tsconfig.build.json", + "check": "tsc --noEmit", + "test": "vitest run", + "test:unit": "vitest run" + }, + "dependencies": { + "@sinclair/typebox": "^0.34.49", + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1" + }, + "devDependencies": { + "@types/bun": "^1.3.14", + "@types/node": "^25.9.3", + "bun-types": "^1.3.14", + "typescript": "^6.0.3", + "vitest": "^4.1.9" + } +} diff --git a/apps/web/src/lib/model/fixtures/generic.ts b/packages/dashboard-model/src/fixtures/generic.ts similarity index 100% rename from apps/web/src/lib/model/fixtures/generic.ts rename to packages/dashboard-model/src/fixtures/generic.ts diff --git a/packages/dashboard-model/src/fixtures/index.ts b/packages/dashboard-model/src/fixtures/index.ts new file mode 100644 index 0000000..f62d646 --- /dev/null +++ b/packages/dashboard-model/src/fixtures/index.ts @@ -0,0 +1 @@ +export { genericDashboardFixture } from "./generic"; diff --git a/apps/web/src/lib/model/index.ts b/packages/dashboard-model/src/index.ts similarity index 100% rename from apps/web/src/lib/model/index.ts rename to packages/dashboard-model/src/index.ts diff --git a/apps/web/src/lib/model/schema.test.ts b/packages/dashboard-model/src/schema.test.ts similarity index 96% rename from apps/web/src/lib/model/schema.test.ts rename to packages/dashboard-model/src/schema.test.ts index 7eeed74..9975f87 100644 --- a/apps/web/src/lib/model/schema.test.ts +++ b/packages/dashboard-model/src/schema.test.ts @@ -5,7 +5,6 @@ import { validateDashboardDocument, } from "."; import { - dimensionLabDashboardFixture, genericDashboardFixture, } from "./fixtures"; @@ -19,12 +18,6 @@ describe("dashboard model validation", () => { } }); - it("accepts the Dimension Lab dashboard fixture", () => { - const result = validateDashboardDocument(dimensionLabDashboardFixture); - - expect(result.valid).toBe(true); - }); - it("rejects documents with an unsupported schema version", () => { const invalid = { ...genericDashboardFixture, diff --git a/apps/web/src/lib/model/schema.ts b/packages/dashboard-model/src/schema.ts similarity index 100% rename from apps/web/src/lib/model/schema.ts rename to packages/dashboard-model/src/schema.ts diff --git a/apps/web/src/lib/model/validation.ts b/packages/dashboard-model/src/validation.ts similarity index 100% rename from apps/web/src/lib/model/validation.ts rename to packages/dashboard-model/src/validation.ts diff --git a/packages/dashboard-model/tsconfig.build.json b/packages/dashboard-model/tsconfig.build.json new file mode 100644 index 0000000..5d3e7ce --- /dev/null +++ b/packages/dashboard-model/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": false, + "noEmit": false, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules", "src/**/*.test.ts"] +} diff --git a/packages/dashboard-model/tsconfig.json b/packages/dashboard-model/tsconfig.json new file mode 100644 index 0000000..26cb1c4 --- /dev/null +++ b/packages/dashboard-model/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "types": ["node", "bun-types"] + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/tsconfig.json b/tsconfig.json index 57ef1e6..8fe9b00 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "files": [], "references": [ + { "path": "./packages/dashboard-model" }, { "path": "./apps/web" }, { "path": "./packages/ui" } ] From 9311b81102480516c59e2e9dab631ee611a1737b Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 06:34:31 +0200 Subject: [PATCH 11/50] fix(container): stage model package manifest --- apps/web/Containerfile | 1 + apps/web/src/lib/workspace-boundary.test.ts | 32 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/apps/web/Containerfile b/apps/web/Containerfile index 912f780..7f12f94 100644 --- a/apps/web/Containerfile +++ b/apps/web/Containerfile @@ -4,6 +4,7 @@ WORKDIR /repo ENV PATH=/repo/apps/web/node_modules/.bin:/repo/packages/ui/node_modules/.bin:/repo/node_modules/.bin:$PATH COPY package.json bun.lock turbo.json tsconfig.base.json ./ COPY apps/web/package.json apps/web/package.json +COPY packages/dashboard-model/package.json packages/dashboard-model/package.json COPY packages/ui/package.json packages/ui/package.json RUN bun install --frozen-lockfile diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 4edcfc6..4f4200c 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -113,4 +113,36 @@ describe("workspace boundaries", () => { false, ); }); + + test("stages web workspace dependency manifests before container install", () => { + const webPackage = JSON.parse( + readFileSync(join(root, "apps/web/package.json"), "utf8"), + ) as { dependencies?: Record }; + const workspacePackages = [ + "packages/ui/package.json", + "packages/dashboard-model/package.json", + ].map((manifestPath) => { + const packageJson = JSON.parse( + readFileSync(join(root, manifestPath), "utf8"), + ) as { name?: string }; + + return [packageJson.name, manifestPath] as const; + }); + const manifestsByPackageName = new Map(workspacePackages); + const containerfile = readFileSync( + join(root, "apps/web/Containerfile"), + "utf8", + ); + + const workspaceDependencyManifests = Object.entries( + webPackage.dependencies ?? {}, + ) + .filter(([, version]) => version.startsWith("workspace:")) + .map(([packageName]) => manifestsByPackageName.get(packageName)); + + expect(workspaceDependencyManifests).not.toContain(undefined); + for (const manifestPath of workspaceDependencyManifests) { + expect(containerfile).toContain(`COPY ${manifestPath} ${manifestPath}`); + } + }); }); From 32ad2cebe474535a44841f128398b87a4a4b2e44 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 06:43:09 +0200 Subject: [PATCH 12/50] refactor(ui): organize component source tree --- README.md | 3 +- .../src/components/{ => foundation}/Badge.tsx | 2 +- .../components/{ => foundation}/Button.tsx | 0 .../{ => foundation}/IconButton.tsx | 0 .../components/{ => foundation}/IconGlyph.tsx | 0 .../{ => foundation}/ProgressMeter.tsx | 4 +- .../components/{ => foundation}/Separator.tsx | 0 .../{ => foundation}/StatusBadge.tsx | 2 +- .../{ => foundation}/ThemeToggle.tsx | 4 +- .../{ => frames}/CornerBracketFrame.tsx | 0 .../{ => frames}/DashboardFrame.tsx | 8 +- .../{ => frames}/DashboardHeader.tsx | 2 +- .../{ => frames}/DiagonalStripeField.tsx | 0 .../components/{ => frames}/FooterCell.tsx | 2 +- .../{ => frames}/FooterStatusCell.tsx | 2 +- .../src/components/{ => frames}/GridFrame.tsx | 0 .../components/{ => frames}/ModuleCard.tsx | 4 +- .../ui/src/components/{ => frames}/Panel.tsx | 0 .../components/{ => frames}/ScanlineField.tsx | 0 .../{ => operations}/ServiceGroupPanel.tsx | 2 +- .../{ => operations}/ServicePanel.tsx | 4 +- .../{ => operations}/ServiceRow.tsx | 6 +- .../{ => operations}/StatusStrip.tsx | 4 +- .../{ => operations}/SystemState.tsx | 4 +- .../{ => operations}/WeatherModule.tsx | 4 +- packages/ui/src/components/render.test.tsx | 20 ++--- .../components/{ => telemetry}/LineChart.tsx | 2 +- .../{ => telemetry}/SignalTrace.tsx | 0 .../components/{ => telemetry}/Sparkline.tsx | 2 +- .../{ => telemetry}/TelemetryCard.tsx | 6 +- .../{ => telemetry}/TelemetryGrid.tsx | 2 +- .../{ => telemetry}/TelemetryStrip.tsx | 2 +- packages/ui/src/content-boundary.test.ts | 25 ++++-- packages/ui/src/index.ts | 60 +++++++------- packages/ui/src/stories/Badge.stories.tsx | 2 +- packages/ui/src/stories/Button.stories.tsx | 2 +- .../stories/CornerBracketFrame.stories.tsx | 2 +- .../ui/src/stories/DashboardFrame.stories.tsx | 2 +- .../src/stories/DashboardHeader.stories.tsx | 2 +- .../src/stories/DashboardOnePager.stories.tsx | 2 +- .../stories/DiagonalStripeField.stories.tsx | 2 +- .../ui/src/stories/FooterCell.stories.tsx | 2 +- .../src/stories/FooterStatusCell.stories.tsx | 2 +- packages/ui/src/stories/GridFrame.stories.tsx | 3 +- .../ui/src/stories/IconButton.stories.tsx | 2 +- packages/ui/src/stories/IconGlyph.stories.tsx | 2 +- packages/ui/src/stories/LineChart.stories.tsx | 2 +- .../ui/src/stories/ModuleCard.stories.tsx | 2 +- packages/ui/src/stories/Panel.stories.tsx | 3 +- .../ui/src/stories/ProgressMeter.stories.tsx | 2 +- .../ui/src/stories/ScanlineField.stories.tsx | 2 +- packages/ui/src/stories/Separator.stories.tsx | 2 +- .../src/stories/ServiceGroupPanel.stories.tsx | 2 +- .../ui/src/stories/ServicePanel.stories.tsx | 2 +- .../ui/src/stories/ServiceRow.stories.tsx | 2 +- .../ui/src/stories/SignalTrace.stories.tsx | 2 +- packages/ui/src/stories/Sparkline.stories.tsx | 2 +- .../ui/src/stories/StatusBadge.stories.tsx | 2 +- .../ui/src/stories/StatusStrip.stories.tsx | 2 +- .../ui/src/stories/SystemState.stories.tsx | 2 +- .../ui/src/stories/TelemetryCard.stories.tsx | 2 +- .../ui/src/stories/TelemetryGrid.stories.tsx | 2 +- .../ui/src/stories/TelemetryStrip.stories.tsx | 2 +- .../ui/src/stories/ThemeToggle.stories.tsx | 2 +- .../ui/src/stories/WeatherModule.stories.tsx | 2 +- packages/ui/src/storybook.test.ts | 78 ++++++++++++++++--- 66 files changed, 194 insertions(+), 124 deletions(-) rename packages/ui/src/components/{ => foundation}/Badge.tsx (84%) rename packages/ui/src/components/{ => foundation}/Button.tsx (100%) rename packages/ui/src/components/{ => foundation}/IconButton.tsx (100%) rename packages/ui/src/components/{ => foundation}/IconGlyph.tsx (100%) rename packages/ui/src/components/{ => foundation}/ProgressMeter.tsx (88%) rename packages/ui/src/components/{ => foundation}/Separator.tsx (100%) rename packages/ui/src/components/{ => foundation}/StatusBadge.tsx (85%) rename packages/ui/src/components/{ => foundation}/ThemeToggle.tsx (92%) rename packages/ui/src/components/{ => frames}/CornerBracketFrame.tsx (100%) rename packages/ui/src/components/{ => frames}/DashboardFrame.tsx (88%) rename packages/ui/src/components/{ => frames}/DashboardHeader.tsx (92%) rename packages/ui/src/components/{ => frames}/DiagonalStripeField.tsx (100%) rename packages/ui/src/components/{ => frames}/FooterCell.tsx (94%) rename packages/ui/src/components/{ => frames}/FooterStatusCell.tsx (75%) rename packages/ui/src/components/{ => frames}/GridFrame.tsx (100%) rename packages/ui/src/components/{ => frames}/ModuleCard.tsx (89%) rename packages/ui/src/components/{ => frames}/Panel.tsx (100%) rename packages/ui/src/components/{ => frames}/ScanlineField.tsx (100%) rename packages/ui/src/components/{ => operations}/ServiceGroupPanel.tsx (76%) rename packages/ui/src/components/{ => operations}/ServicePanel.tsx (87%) rename packages/ui/src/components/{ => operations}/ServiceRow.tsx (89%) rename packages/ui/src/components/{ => operations}/StatusStrip.tsx (82%) rename packages/ui/src/components/{ => operations}/SystemState.tsx (82%) rename packages/ui/src/components/{ => operations}/WeatherModule.tsx (53%) rename packages/ui/src/components/{ => telemetry}/LineChart.tsx (98%) rename packages/ui/src/components/{ => telemetry}/SignalTrace.tsx (100%) rename packages/ui/src/components/{ => telemetry}/Sparkline.tsx (94%) rename packages/ui/src/components/{ => telemetry}/TelemetryCard.tsx (91%) rename packages/ui/src/components/{ => telemetry}/TelemetryGrid.tsx (94%) rename packages/ui/src/components/{ => telemetry}/TelemetryStrip.tsx (76%) diff --git a/README.md b/README.md index 98dc8ec..572f9bc 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ validated dashboard model state inside the web app. - `packages/dashboard-model`: reusable dashboard schema, validation, and generic model fixtures shared by apps and tooling. - `packages/ui`: reusable dashboard React components, design tokens, - shadcn/radix primitives, generic fixtures, and Storybook. + shadcn/radix primitives, generic fixtures, and Storybook. Component source is + grouped under `foundation`, `frames`, `operations`, and `telemetry` domains. - `docs/superpowers`: migration specs and execution plans used for this repo. ## Development diff --git a/packages/ui/src/components/Badge.tsx b/packages/ui/src/components/foundation/Badge.tsx similarity index 84% rename from packages/ui/src/components/Badge.tsx rename to packages/ui/src/components/foundation/Badge.tsx index a21ade8..5bdee48 100644 --- a/packages/ui/src/components/Badge.tsx +++ b/packages/ui/src/components/foundation/Badge.tsx @@ -1,4 +1,4 @@ -import type { UiSeverity } from "../types"; +import type { UiSeverity } from "../../types"; import { StatusBadge } from "./StatusBadge"; export interface BadgeProps { diff --git a/packages/ui/src/components/Button.tsx b/packages/ui/src/components/foundation/Button.tsx similarity index 100% rename from packages/ui/src/components/Button.tsx rename to packages/ui/src/components/foundation/Button.tsx diff --git a/packages/ui/src/components/IconButton.tsx b/packages/ui/src/components/foundation/IconButton.tsx similarity index 100% rename from packages/ui/src/components/IconButton.tsx rename to packages/ui/src/components/foundation/IconButton.tsx diff --git a/packages/ui/src/components/IconGlyph.tsx b/packages/ui/src/components/foundation/IconGlyph.tsx similarity index 100% rename from packages/ui/src/components/IconGlyph.tsx rename to packages/ui/src/components/foundation/IconGlyph.tsx diff --git a/packages/ui/src/components/ProgressMeter.tsx b/packages/ui/src/components/foundation/ProgressMeter.tsx similarity index 88% rename from packages/ui/src/components/ProgressMeter.tsx rename to packages/ui/src/components/foundation/ProgressMeter.tsx index c7278bf..c3f3aad 100644 --- a/packages/ui/src/components/ProgressMeter.tsx +++ b/packages/ui/src/components/foundation/ProgressMeter.tsx @@ -1,6 +1,6 @@ import type { CSSProperties } from "react"; -import { clampPercent } from "../format"; -import type { UiSeverity } from "../types"; +import { clampPercent } from "../../format"; +import type { UiSeverity } from "../../types"; export interface ProgressMeterProps { value?: number; diff --git a/packages/ui/src/components/Separator.tsx b/packages/ui/src/components/foundation/Separator.tsx similarity index 100% rename from packages/ui/src/components/Separator.tsx rename to packages/ui/src/components/foundation/Separator.tsx diff --git a/packages/ui/src/components/StatusBadge.tsx b/packages/ui/src/components/foundation/StatusBadge.tsx similarity index 85% rename from packages/ui/src/components/StatusBadge.tsx rename to packages/ui/src/components/foundation/StatusBadge.tsx index fb63a04..d2b01b3 100644 --- a/packages/ui/src/components/StatusBadge.tsx +++ b/packages/ui/src/components/foundation/StatusBadge.tsx @@ -1,4 +1,4 @@ -import type { UiSeverity } from "../types"; +import type { UiSeverity } from "../../types"; export interface StatusBadgeProps { label: string; diff --git a/packages/ui/src/components/ThemeToggle.tsx b/packages/ui/src/components/foundation/ThemeToggle.tsx similarity index 92% rename from packages/ui/src/components/ThemeToggle.tsx rename to packages/ui/src/components/foundation/ThemeToggle.tsx index 9e883af..5d83031 100644 --- a/packages/ui/src/components/ThemeToggle.tsx +++ b/packages/ui/src/components/foundation/ThemeToggle.tsx @@ -1,5 +1,5 @@ -import type { UiTheme } from "../theme"; -import { getNextUiTheme } from "../theme"; +import type { UiTheme } from "../../theme"; +import { getNextUiTheme } from "../../theme"; import { IconGlyph } from "./IconGlyph"; export interface ThemeToggleProps { diff --git a/packages/ui/src/components/CornerBracketFrame.tsx b/packages/ui/src/components/frames/CornerBracketFrame.tsx similarity index 100% rename from packages/ui/src/components/CornerBracketFrame.tsx rename to packages/ui/src/components/frames/CornerBracketFrame.tsx diff --git a/packages/ui/src/components/DashboardFrame.tsx b/packages/ui/src/components/frames/DashboardFrame.tsx similarity index 88% rename from packages/ui/src/components/DashboardFrame.tsx rename to packages/ui/src/components/frames/DashboardFrame.tsx index cdf312a..08029cf 100644 --- a/packages/ui/src/components/DashboardFrame.tsx +++ b/packages/ui/src/components/frames/DashboardFrame.tsx @@ -1,10 +1,10 @@ import { useId } from "react"; import type { ReactNode } from "react"; -import type { UiDashboardPreview } from "../types"; +import type { UiDashboardPreview } from "../../types"; import { ModuleCard } from "./ModuleCard"; -import { ServicePanel } from "./ServicePanel"; -import { StatusStrip } from "./StatusStrip"; -import { TelemetryGrid } from "./TelemetryGrid"; +import { ServicePanel } from "../operations/ServicePanel"; +import { StatusStrip } from "../operations/StatusStrip"; +import { TelemetryGrid } from "../telemetry/TelemetryGrid"; export interface DashboardFrameProps { actions?: ReactNode; diff --git a/packages/ui/src/components/DashboardHeader.tsx b/packages/ui/src/components/frames/DashboardHeader.tsx similarity index 92% rename from packages/ui/src/components/DashboardHeader.tsx rename to packages/ui/src/components/frames/DashboardHeader.tsx index 68b4e11..3f418fa 100644 --- a/packages/ui/src/components/DashboardHeader.tsx +++ b/packages/ui/src/components/frames/DashboardHeader.tsx @@ -1,5 +1,5 @@ import { useId } from "react"; -import type { UiModuleBlock } from "../types"; +import type { UiModuleBlock } from "../../types"; import { ModuleCard } from "./ModuleCard"; export interface DashboardHeaderProps { diff --git a/packages/ui/src/components/DiagonalStripeField.tsx b/packages/ui/src/components/frames/DiagonalStripeField.tsx similarity index 100% rename from packages/ui/src/components/DiagonalStripeField.tsx rename to packages/ui/src/components/frames/DiagonalStripeField.tsx diff --git a/packages/ui/src/components/FooterCell.tsx b/packages/ui/src/components/frames/FooterCell.tsx similarity index 94% rename from packages/ui/src/components/FooterCell.tsx rename to packages/ui/src/components/frames/FooterCell.tsx index 760e853..8b9a86d 100644 --- a/packages/ui/src/components/FooterCell.tsx +++ b/packages/ui/src/components/frames/FooterCell.tsx @@ -1,4 +1,4 @@ -import type { UiStatusItem } from "../types"; +import type { UiStatusItem } from "../../types"; export interface FooterCellProps { item: UiStatusItem; diff --git a/packages/ui/src/components/FooterStatusCell.tsx b/packages/ui/src/components/frames/FooterStatusCell.tsx similarity index 75% rename from packages/ui/src/components/FooterStatusCell.tsx rename to packages/ui/src/components/frames/FooterStatusCell.tsx index dcfb8b4..b5b339e 100644 --- a/packages/ui/src/components/FooterStatusCell.tsx +++ b/packages/ui/src/components/frames/FooterStatusCell.tsx @@ -1,4 +1,4 @@ -import type { UiStatusItem } from "../types"; +import type { UiStatusItem } from "../../types"; import { FooterCell } from "./FooterCell"; export function FooterStatusCell({ item }: { item: UiStatusItem }) { diff --git a/packages/ui/src/components/GridFrame.tsx b/packages/ui/src/components/frames/GridFrame.tsx similarity index 100% rename from packages/ui/src/components/GridFrame.tsx rename to packages/ui/src/components/frames/GridFrame.tsx diff --git a/packages/ui/src/components/ModuleCard.tsx b/packages/ui/src/components/frames/ModuleCard.tsx similarity index 89% rename from packages/ui/src/components/ModuleCard.tsx rename to packages/ui/src/components/frames/ModuleCard.tsx index d05bee0..9b96df9 100644 --- a/packages/ui/src/components/ModuleCard.tsx +++ b/packages/ui/src/components/frames/ModuleCard.tsx @@ -1,6 +1,6 @@ import { useId } from "react"; -import type { UiModuleBlock } from "../types"; -import { IconGlyph } from "./IconGlyph"; +import type { UiModuleBlock } from "../../types"; +import { IconGlyph } from "../foundation/IconGlyph"; export interface ModuleCardProps { module: UiModuleBlock; diff --git a/packages/ui/src/components/Panel.tsx b/packages/ui/src/components/frames/Panel.tsx similarity index 100% rename from packages/ui/src/components/Panel.tsx rename to packages/ui/src/components/frames/Panel.tsx diff --git a/packages/ui/src/components/ScanlineField.tsx b/packages/ui/src/components/frames/ScanlineField.tsx similarity index 100% rename from packages/ui/src/components/ScanlineField.tsx rename to packages/ui/src/components/frames/ScanlineField.tsx diff --git a/packages/ui/src/components/ServiceGroupPanel.tsx b/packages/ui/src/components/operations/ServiceGroupPanel.tsx similarity index 76% rename from packages/ui/src/components/ServiceGroupPanel.tsx rename to packages/ui/src/components/operations/ServiceGroupPanel.tsx index 4252cc9..9b53340 100644 --- a/packages/ui/src/components/ServiceGroupPanel.tsx +++ b/packages/ui/src/components/operations/ServiceGroupPanel.tsx @@ -1,4 +1,4 @@ -import type { UiServiceGroup } from "../types"; +import type { UiServiceGroup } from "../../types"; import { ServicePanel } from "./ServicePanel"; export function ServiceGroupPanel({ group }: { group: UiServiceGroup }) { diff --git a/packages/ui/src/components/ServicePanel.tsx b/packages/ui/src/components/operations/ServicePanel.tsx similarity index 87% rename from packages/ui/src/components/ServicePanel.tsx rename to packages/ui/src/components/operations/ServicePanel.tsx index 12a2e74..c8b85af 100644 --- a/packages/ui/src/components/ServicePanel.tsx +++ b/packages/ui/src/components/operations/ServicePanel.tsx @@ -1,5 +1,5 @@ -import type { UiServiceGroup } from "../types"; -import { Panel } from "./Panel"; +import type { UiServiceGroup } from "../../types"; +import { Panel } from "../frames/Panel"; import { ServiceRow } from "./ServiceRow"; import { StatusStrip } from "./StatusStrip"; diff --git a/packages/ui/src/components/ServiceRow.tsx b/packages/ui/src/components/operations/ServiceRow.tsx similarity index 89% rename from packages/ui/src/components/ServiceRow.tsx rename to packages/ui/src/components/operations/ServiceRow.tsx index cf065f4..e6e5aa9 100644 --- a/packages/ui/src/components/ServiceRow.tsx +++ b/packages/ui/src/components/operations/ServiceRow.tsx @@ -1,6 +1,6 @@ -import type { UiServiceRow } from "../types"; -import { IconGlyph } from "./IconGlyph"; -import { StatusBadge } from "./StatusBadge"; +import type { UiServiceRow } from "../../types"; +import { IconGlyph } from "../foundation/IconGlyph"; +import { StatusBadge } from "../foundation/StatusBadge"; export interface ServiceRowProps { service: UiServiceRow; diff --git a/packages/ui/src/components/StatusStrip.tsx b/packages/ui/src/components/operations/StatusStrip.tsx similarity index 82% rename from packages/ui/src/components/StatusStrip.tsx rename to packages/ui/src/components/operations/StatusStrip.tsx index 66c6451..3f2bca2 100644 --- a/packages/ui/src/components/StatusStrip.tsx +++ b/packages/ui/src/components/operations/StatusStrip.tsx @@ -1,5 +1,5 @@ -import type { UiStatusItem } from "../types"; -import { FooterCell } from "./FooterCell"; +import type { UiStatusItem } from "../../types"; +import { FooterCell } from "../frames/FooterCell"; export interface StatusStripProps { id?: string; diff --git a/packages/ui/src/components/SystemState.tsx b/packages/ui/src/components/operations/SystemState.tsx similarity index 82% rename from packages/ui/src/components/SystemState.tsx rename to packages/ui/src/components/operations/SystemState.tsx index e817e2b..438fa52 100644 --- a/packages/ui/src/components/SystemState.tsx +++ b/packages/ui/src/components/operations/SystemState.tsx @@ -1,5 +1,5 @@ -import type { UiSeverity } from "../types"; -import { IconGlyph } from "./IconGlyph"; +import type { UiSeverity } from "../../types"; +import { IconGlyph } from "../foundation/IconGlyph"; export interface SystemStateProps { title: string; diff --git a/packages/ui/src/components/WeatherModule.tsx b/packages/ui/src/components/operations/WeatherModule.tsx similarity index 53% rename from packages/ui/src/components/WeatherModule.tsx rename to packages/ui/src/components/operations/WeatherModule.tsx index 0967cfb..c7e55db 100644 --- a/packages/ui/src/components/WeatherModule.tsx +++ b/packages/ui/src/components/operations/WeatherModule.tsx @@ -1,5 +1,5 @@ -import type { UiModuleBlock } from "../types"; -import { ModuleCard } from "./ModuleCard"; +import type { UiModuleBlock } from "../../types"; +import { ModuleCard } from "../frames/ModuleCard"; export function WeatherModule({ module }: { module: UiModuleBlock }) { return ; diff --git a/packages/ui/src/components/render.test.tsx b/packages/ui/src/components/render.test.tsx index 1cf40fe..010f672 100644 --- a/packages/ui/src/components/render.test.tsx +++ b/packages/ui/src/components/render.test.tsx @@ -1,14 +1,16 @@ import { renderToString } from "react-dom/server"; import { describe, expect, test } from "vitest"; -import { Button } from "./Button"; -import { DashboardFrame } from "./DashboardFrame"; -import { FooterCell } from "./FooterCell"; -import { IconButton } from "./IconButton"; -import { ServiceRow } from "./ServiceRow"; -import { StatusStrip } from "./StatusStrip"; -import { TelemetryCard } from "./TelemetryCard"; -import { TelemetryGrid } from "./TelemetryGrid"; -import { ThemeToggle } from "./ThemeToggle"; +import { + Button, + DashboardFrame, + FooterCell, + IconButton, + ServiceRow, + StatusStrip, + TelemetryCard, + TelemetryGrid, + ThemeToggle, +} from "../index"; import { dashboardPreviewFixtures } from "../fixtures"; describe("dashboard UI components", () => { diff --git a/packages/ui/src/components/LineChart.tsx b/packages/ui/src/components/telemetry/LineChart.tsx similarity index 98% rename from packages/ui/src/components/LineChart.tsx rename to packages/ui/src/components/telemetry/LineChart.tsx index 386b515..0accdf7 100644 --- a/packages/ui/src/components/LineChart.tsx +++ b/packages/ui/src/components/telemetry/LineChart.tsx @@ -1,6 +1,6 @@ import "uplot/dist/uPlot.min.css"; import { useEffect, useRef } from "react"; -import type { UiSeverity } from "../types"; +import type { UiSeverity } from "../../types"; export interface LineChartProps { values?: number[]; diff --git a/packages/ui/src/components/SignalTrace.tsx b/packages/ui/src/components/telemetry/SignalTrace.tsx similarity index 100% rename from packages/ui/src/components/SignalTrace.tsx rename to packages/ui/src/components/telemetry/SignalTrace.tsx diff --git a/packages/ui/src/components/Sparkline.tsx b/packages/ui/src/components/telemetry/Sparkline.tsx similarity index 94% rename from packages/ui/src/components/Sparkline.tsx rename to packages/ui/src/components/telemetry/Sparkline.tsx index a79f532..d03fa13 100644 --- a/packages/ui/src/components/Sparkline.tsx +++ b/packages/ui/src/components/telemetry/Sparkline.tsx @@ -1,4 +1,4 @@ -import type { UiSeverity } from "../types"; +import type { UiSeverity } from "../../types"; export interface SparklineProps { values?: number[]; diff --git a/packages/ui/src/components/TelemetryCard.tsx b/packages/ui/src/components/telemetry/TelemetryCard.tsx similarity index 91% rename from packages/ui/src/components/TelemetryCard.tsx rename to packages/ui/src/components/telemetry/TelemetryCard.tsx index 97033c9..15481ee 100644 --- a/packages/ui/src/components/TelemetryCard.tsx +++ b/packages/ui/src/components/telemetry/TelemetryCard.tsx @@ -1,7 +1,7 @@ import type { CSSProperties } from "react"; -import { clampPercent, formatMetricValue } from "../format"; -import type { UiTelemetryCard } from "../types"; -import { IconGlyph } from "./IconGlyph"; +import { clampPercent, formatMetricValue } from "../../format"; +import type { UiTelemetryCard } from "../../types"; +import { IconGlyph } from "../foundation/IconGlyph"; import { LineChart } from "./LineChart"; export interface TelemetryCardProps { diff --git a/packages/ui/src/components/TelemetryGrid.tsx b/packages/ui/src/components/telemetry/TelemetryGrid.tsx similarity index 94% rename from packages/ui/src/components/TelemetryGrid.tsx rename to packages/ui/src/components/telemetry/TelemetryGrid.tsx index 8afb9a2..9f4d497 100644 --- a/packages/ui/src/components/TelemetryGrid.tsx +++ b/packages/ui/src/components/telemetry/TelemetryGrid.tsx @@ -1,5 +1,5 @@ import { useId } from "react"; -import type { UiTelemetryCard } from "../types"; +import type { UiTelemetryCard } from "../../types"; import { TelemetryCard } from "./TelemetryCard"; export interface TelemetryGridProps { diff --git a/packages/ui/src/components/TelemetryStrip.tsx b/packages/ui/src/components/telemetry/TelemetryStrip.tsx similarity index 76% rename from packages/ui/src/components/TelemetryStrip.tsx rename to packages/ui/src/components/telemetry/TelemetryStrip.tsx index 765c1e2..35073bb 100644 --- a/packages/ui/src/components/TelemetryStrip.tsx +++ b/packages/ui/src/components/telemetry/TelemetryStrip.tsx @@ -1,4 +1,4 @@ -import type { UiTelemetryCard } from "../types"; +import type { UiTelemetryCard } from "../../types"; import { TelemetryGrid } from "./TelemetryGrid"; export function TelemetryStrip({ cards }: { cards: UiTelemetryCard[] }) { diff --git a/packages/ui/src/content-boundary.test.ts b/packages/ui/src/content-boundary.test.ts index 80b85ce..f7188a1 100644 --- a/packages/ui/src/content-boundary.test.ts +++ b/packages/ui/src/content-boundary.test.ts @@ -26,15 +26,28 @@ const forbiddenTerms = [ describe("UI package content boundary", () => { test("contains the reusable dashboard component inventory", () => { expect(existsSync(join(uiSourceRoot, "index.ts"))).toBe(true); - expect(existsSync(join(uiSourceRoot, "components/DashboardFrame.tsx"))).toBe( - true, - ); - expect(existsSync(join(uiSourceRoot, "components/ThemeToggle.tsx"))).toBe( - true, - ); + expect( + existsSync(join(uiSourceRoot, "components/frames/DashboardFrame.tsx")), + ).toBe(true); + expect( + existsSync(join(uiSourceRoot, "components/foundation/ThemeToggle.tsx")), + ).toBe(true); expect(existsSync(join(uiSourceRoot, "styles.css"))).toBe(true); }); + test("keeps UI components grouped by domain instead of a flat bucket", () => { + const domainFolders = ["foundation", "frames", "operations", "telemetry"]; + + for (const folder of domainFolders) { + expect(existsSync(join(uiSourceRoot, "components", folder))).toBe(true); + } + + const flatComponentFiles = readdirSync(join(uiSourceRoot, "components")) + .filter((entry) => entry.endsWith(".tsx") && !entry.endsWith(".test.tsx")) + .sort(); + expect(flatComponentFiles).toEqual([]); + }); + test("keeps environment-specific content out of reusable UI source", () => { const source = readUiSource(uiSourceRoot).toLowerCase(); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index a9a83df..bc506c0 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,33 +1,33 @@ -export { Badge } from "./components/Badge"; -export { Button } from "./components/Button"; -export { CornerBracketFrame } from "./components/CornerBracketFrame"; -export { DashboardHeader } from "./components/DashboardHeader"; -export { DashboardFrame } from "./components/DashboardFrame"; -export { DiagonalStripeField } from "./components/DiagonalStripeField"; -export { FooterCell } from "./components/FooterCell"; -export { FooterStatusCell } from "./components/FooterStatusCell"; -export { GridFrame } from "./components/GridFrame"; -export { IconGlyph } from "./components/IconGlyph"; -export { IconButton } from "./components/IconButton"; -export { LineChart } from "./components/LineChart"; -export { ModuleCard } from "./components/ModuleCard"; -export { Panel } from "./components/Panel"; -export { ProgressMeter } from "./components/ProgressMeter"; -export { Separator } from "./components/Separator"; -export { ServiceGroupPanel } from "./components/ServiceGroupPanel"; -export { ServicePanel } from "./components/ServicePanel"; -export { ServiceRow } from "./components/ServiceRow"; -export { ScanlineField } from "./components/ScanlineField"; -export { SignalTrace } from "./components/SignalTrace"; -export { Sparkline } from "./components/Sparkline"; -export { StatusBadge } from "./components/StatusBadge"; -export { StatusStrip } from "./components/StatusStrip"; -export { SystemState } from "./components/SystemState"; -export { TelemetryCard } from "./components/TelemetryCard"; -export { TelemetryGrid } from "./components/TelemetryGrid"; -export { TelemetryStrip } from "./components/TelemetryStrip"; -export { ThemeToggle } from "./components/ThemeToggle"; -export { WeatherModule } from "./components/WeatherModule"; +export { Badge } from "./components/foundation/Badge"; +export { Button } from "./components/foundation/Button"; +export { IconGlyph } from "./components/foundation/IconGlyph"; +export { IconButton } from "./components/foundation/IconButton"; +export { ProgressMeter } from "./components/foundation/ProgressMeter"; +export { Separator } from "./components/foundation/Separator"; +export { StatusBadge } from "./components/foundation/StatusBadge"; +export { ThemeToggle } from "./components/foundation/ThemeToggle"; +export { CornerBracketFrame } from "./components/frames/CornerBracketFrame"; +export { DashboardHeader } from "./components/frames/DashboardHeader"; +export { DashboardFrame } from "./components/frames/DashboardFrame"; +export { DiagonalStripeField } from "./components/frames/DiagonalStripeField"; +export { FooterCell } from "./components/frames/FooterCell"; +export { FooterStatusCell } from "./components/frames/FooterStatusCell"; +export { GridFrame } from "./components/frames/GridFrame"; +export { ModuleCard } from "./components/frames/ModuleCard"; +export { Panel } from "./components/frames/Panel"; +export { ScanlineField } from "./components/frames/ScanlineField"; +export { ServiceGroupPanel } from "./components/operations/ServiceGroupPanel"; +export { ServicePanel } from "./components/operations/ServicePanel"; +export { ServiceRow } from "./components/operations/ServiceRow"; +export { StatusStrip } from "./components/operations/StatusStrip"; +export { SystemState } from "./components/operations/SystemState"; +export { WeatherModule } from "./components/operations/WeatherModule"; +export { LineChart } from "./components/telemetry/LineChart"; +export { SignalTrace } from "./components/telemetry/SignalTrace"; +export { Sparkline } from "./components/telemetry/Sparkline"; +export { TelemetryCard } from "./components/telemetry/TelemetryCard"; +export { TelemetryGrid } from "./components/telemetry/TelemetryGrid"; +export { TelemetryStrip } from "./components/telemetry/TelemetryStrip"; export { dashboardPreviewFixtures } from "./fixtures"; export { getNextUiTheme, diff --git a/packages/ui/src/stories/Badge.stories.tsx b/packages/ui/src/stories/Badge.stories.tsx index d33394d..26e3ea8 100644 --- a/packages/ui/src/stories/Badge.stories.tsx +++ b/packages/ui/src/stories/Badge.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { Badge } from "../components/Badge"; +import { Badge } from "../index"; const meta = { title: "UI/Badge", diff --git a/packages/ui/src/stories/Button.stories.tsx b/packages/ui/src/stories/Button.stories.tsx index c207233..084caa8 100644 --- a/packages/ui/src/stories/Button.stories.tsx +++ b/packages/ui/src/stories/Button.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { Button } from "../components/Button"; +import { Button } from "../index"; const meta = { title: "UI/Button", diff --git a/packages/ui/src/stories/CornerBracketFrame.stories.tsx b/packages/ui/src/stories/CornerBracketFrame.stories.tsx index dd7efbf..d5c0110 100644 --- a/packages/ui/src/stories/CornerBracketFrame.stories.tsx +++ b/packages/ui/src/stories/CornerBracketFrame.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { CornerBracketFrame } from "../components/CornerBracketFrame"; +import { CornerBracketFrame } from "../index"; const meta = { title: "UI/CornerBracketFrame", diff --git a/packages/ui/src/stories/DashboardFrame.stories.tsx b/packages/ui/src/stories/DashboardFrame.stories.tsx index e1963af..894219e 100644 --- a/packages/ui/src/stories/DashboardFrame.stories.tsx +++ b/packages/ui/src/stories/DashboardFrame.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { DashboardFrame } from "../components/DashboardFrame"; +import { DashboardFrame } from "../index"; import { fullCompositionDashboard, secondaryDashboard } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/DashboardHeader.stories.tsx b/packages/ui/src/stories/DashboardHeader.stories.tsx index 8263dd1..c337158 100644 --- a/packages/ui/src/stories/DashboardHeader.stories.tsx +++ b/packages/ui/src/stories/DashboardHeader.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { DashboardHeader } from "../components/DashboardHeader"; +import { DashboardHeader } from "../index"; import { moduleBlocks } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/DashboardOnePager.stories.tsx b/packages/ui/src/stories/DashboardOnePager.stories.tsx index c44364b..3bd800f 100644 --- a/packages/ui/src/stories/DashboardOnePager.stories.tsx +++ b/packages/ui/src/stories/DashboardOnePager.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { DashboardFrame } from "../components/DashboardFrame"; +import { DashboardFrame } from "../index"; import { fullCompositionDashboard } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/DiagonalStripeField.stories.tsx b/packages/ui/src/stories/DiagonalStripeField.stories.tsx index 4ca01f2..1f094ee 100644 --- a/packages/ui/src/stories/DiagonalStripeField.stories.tsx +++ b/packages/ui/src/stories/DiagonalStripeField.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { DiagonalStripeField } from "../components/DiagonalStripeField"; +import { DiagonalStripeField } from "../index"; const meta = { title: "UI/DiagonalStripeField", diff --git a/packages/ui/src/stories/FooterCell.stories.tsx b/packages/ui/src/stories/FooterCell.stories.tsx index 3ba26c7..7aa06d8 100644 --- a/packages/ui/src/stories/FooterCell.stories.tsx +++ b/packages/ui/src/stories/FooterCell.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { FooterCell } from "../components/FooterCell"; +import { FooterCell } from "../index"; import { statusItems } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/FooterStatusCell.stories.tsx b/packages/ui/src/stories/FooterStatusCell.stories.tsx index 8097ba3..bd985a1 100644 --- a/packages/ui/src/stories/FooterStatusCell.stories.tsx +++ b/packages/ui/src/stories/FooterStatusCell.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { FooterStatusCell } from "../components/FooterStatusCell"; +import { FooterStatusCell } from "../index"; import { statusItems } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/GridFrame.stories.tsx b/packages/ui/src/stories/GridFrame.stories.tsx index a43ad22..191bf6c 100644 --- a/packages/ui/src/stories/GridFrame.stories.tsx +++ b/packages/ui/src/stories/GridFrame.stories.tsx @@ -1,6 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { GridFrame } from "../components/GridFrame"; -import { ModuleCard } from "../components/ModuleCard"; +import { GridFrame, ModuleCard } from "../index"; import { moduleBlocks } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/IconButton.stories.tsx b/packages/ui/src/stories/IconButton.stories.tsx index 35b58b9..612bf2a 100644 --- a/packages/ui/src/stories/IconButton.stories.tsx +++ b/packages/ui/src/stories/IconButton.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { IconButton } from "../components/IconButton"; +import { IconButton } from "../index"; const meta = { title: "UI/IconButton", diff --git a/packages/ui/src/stories/IconGlyph.stories.tsx b/packages/ui/src/stories/IconGlyph.stories.tsx index 3f1715a..d832951 100644 --- a/packages/ui/src/stories/IconGlyph.stories.tsx +++ b/packages/ui/src/stories/IconGlyph.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { IconGlyph } from "../components/IconGlyph"; +import { IconGlyph } from "../index"; const meta = { title: "UI/IconGlyph", diff --git a/packages/ui/src/stories/LineChart.stories.tsx b/packages/ui/src/stories/LineChart.stories.tsx index bbf8690..ccbd502 100644 --- a/packages/ui/src/stories/LineChart.stories.tsx +++ b/packages/ui/src/stories/LineChart.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { LineChart } from "../components/LineChart"; +import { LineChart } from "../index"; const meta = { title: "UI/LineChart", diff --git a/packages/ui/src/stories/ModuleCard.stories.tsx b/packages/ui/src/stories/ModuleCard.stories.tsx index 3366a55..ea0305b 100644 --- a/packages/ui/src/stories/ModuleCard.stories.tsx +++ b/packages/ui/src/stories/ModuleCard.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { ModuleCard } from "../components/ModuleCard"; +import { ModuleCard } from "../index"; import { moduleBlocks } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/Panel.stories.tsx b/packages/ui/src/stories/Panel.stories.tsx index 730c2d9..91eb61e 100644 --- a/packages/ui/src/stories/Panel.stories.tsx +++ b/packages/ui/src/stories/Panel.stories.tsx @@ -1,6 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { Panel } from "../components/Panel"; -import { ServiceRow } from "../components/ServiceRow"; +import { Panel, ServiceRow } from "../index"; import { serviceRows } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/ProgressMeter.stories.tsx b/packages/ui/src/stories/ProgressMeter.stories.tsx index d3bfdac..eec35c2 100644 --- a/packages/ui/src/stories/ProgressMeter.stories.tsx +++ b/packages/ui/src/stories/ProgressMeter.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { ProgressMeter } from "../components/ProgressMeter"; +import { ProgressMeter } from "../index"; const meta = { title: "UI/ProgressMeter", diff --git a/packages/ui/src/stories/ScanlineField.stories.tsx b/packages/ui/src/stories/ScanlineField.stories.tsx index 89e554e..fbe53f4 100644 --- a/packages/ui/src/stories/ScanlineField.stories.tsx +++ b/packages/ui/src/stories/ScanlineField.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { ScanlineField } from "../components/ScanlineField"; +import { ScanlineField } from "../index"; const meta = { title: "UI/ScanlineField", diff --git a/packages/ui/src/stories/Separator.stories.tsx b/packages/ui/src/stories/Separator.stories.tsx index e261f52..986ed23 100644 --- a/packages/ui/src/stories/Separator.stories.tsx +++ b/packages/ui/src/stories/Separator.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { Separator } from "../components/Separator"; +import { Separator } from "../index"; const meta = { title: "UI/Separator", diff --git a/packages/ui/src/stories/ServiceGroupPanel.stories.tsx b/packages/ui/src/stories/ServiceGroupPanel.stories.tsx index 7183771..f5ebdce 100644 --- a/packages/ui/src/stories/ServiceGroupPanel.stories.tsx +++ b/packages/ui/src/stories/ServiceGroupPanel.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { ServiceGroupPanel } from "../components/ServiceGroupPanel"; +import { ServiceGroupPanel } from "../index"; import { serviceGroups } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/ServicePanel.stories.tsx b/packages/ui/src/stories/ServicePanel.stories.tsx index 750c428..d29b7ad 100644 --- a/packages/ui/src/stories/ServicePanel.stories.tsx +++ b/packages/ui/src/stories/ServicePanel.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { ServicePanel } from "../components/ServicePanel"; +import { ServicePanel } from "../index"; import { serviceGroups } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/ServiceRow.stories.tsx b/packages/ui/src/stories/ServiceRow.stories.tsx index 9513d2c..56ba076 100644 --- a/packages/ui/src/stories/ServiceRow.stories.tsx +++ b/packages/ui/src/stories/ServiceRow.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { ServiceRow } from "../components/ServiceRow"; +import { ServiceRow } from "../index"; import { serviceRows } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/SignalTrace.stories.tsx b/packages/ui/src/stories/SignalTrace.stories.tsx index ae8785b..4089df8 100644 --- a/packages/ui/src/stories/SignalTrace.stories.tsx +++ b/packages/ui/src/stories/SignalTrace.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { SignalTrace } from "../components/SignalTrace"; +import { SignalTrace } from "../index"; const meta = { title: "UI/SignalTrace", diff --git a/packages/ui/src/stories/Sparkline.stories.tsx b/packages/ui/src/stories/Sparkline.stories.tsx index efecfbd..af3e6e5 100644 --- a/packages/ui/src/stories/Sparkline.stories.tsx +++ b/packages/ui/src/stories/Sparkline.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { Sparkline } from "../components/Sparkline"; +import { Sparkline } from "../index"; const meta = { title: "UI/Sparkline", diff --git a/packages/ui/src/stories/StatusBadge.stories.tsx b/packages/ui/src/stories/StatusBadge.stories.tsx index 4513a08..92a444d 100644 --- a/packages/ui/src/stories/StatusBadge.stories.tsx +++ b/packages/ui/src/stories/StatusBadge.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { StatusBadge } from "../components/StatusBadge"; +import { StatusBadge } from "../index"; const meta = { title: "UI/StatusBadge", diff --git a/packages/ui/src/stories/StatusStrip.stories.tsx b/packages/ui/src/stories/StatusStrip.stories.tsx index 7b808fa..2fc236d 100644 --- a/packages/ui/src/stories/StatusStrip.stories.tsx +++ b/packages/ui/src/stories/StatusStrip.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { StatusStrip } from "../components/StatusStrip"; +import { StatusStrip } from "../index"; import { mixedStatusStrip } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/SystemState.stories.tsx b/packages/ui/src/stories/SystemState.stories.tsx index 585540e..9b3f6e5 100644 --- a/packages/ui/src/stories/SystemState.stories.tsx +++ b/packages/ui/src/stories/SystemState.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { SystemState } from "../components/SystemState"; +import { SystemState } from "../index"; const meta = { title: "UI/SystemState", diff --git a/packages/ui/src/stories/TelemetryCard.stories.tsx b/packages/ui/src/stories/TelemetryCard.stories.tsx index dbdf301..3ac0e1e 100644 --- a/packages/ui/src/stories/TelemetryCard.stories.tsx +++ b/packages/ui/src/stories/TelemetryCard.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { TelemetryCard } from "../components/TelemetryCard"; +import { TelemetryCard } from "../index"; import { telemetryCards } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/TelemetryGrid.stories.tsx b/packages/ui/src/stories/TelemetryGrid.stories.tsx index 6b0166e..34493c5 100644 --- a/packages/ui/src/stories/TelemetryGrid.stories.tsx +++ b/packages/ui/src/stories/TelemetryGrid.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { TelemetryGrid } from "../components/TelemetryGrid"; +import { TelemetryGrid } from "../index"; import { eightTelemetryCards, sixteenTelemetryCards } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/TelemetryStrip.stories.tsx b/packages/ui/src/stories/TelemetryStrip.stories.tsx index 76fc983..f7c9d13 100644 --- a/packages/ui/src/stories/TelemetryStrip.stories.tsx +++ b/packages/ui/src/stories/TelemetryStrip.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { TelemetryStrip } from "../components/TelemetryStrip"; +import { TelemetryStrip } from "../index"; import { eightTelemetryCards } from "./story-data"; const meta = { diff --git a/packages/ui/src/stories/ThemeToggle.stories.tsx b/packages/ui/src/stories/ThemeToggle.stories.tsx index 33e494a..f2edeea 100644 --- a/packages/ui/src/stories/ThemeToggle.stories.tsx +++ b/packages/ui/src/stories/ThemeToggle.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { ThemeToggle } from "../components/ThemeToggle"; +import { ThemeToggle } from "../index"; const meta = { title: "UI/ThemeToggle", diff --git a/packages/ui/src/stories/WeatherModule.stories.tsx b/packages/ui/src/stories/WeatherModule.stories.tsx index 6429de2..a376448 100644 --- a/packages/ui/src/stories/WeatherModule.stories.tsx +++ b/packages/ui/src/stories/WeatherModule.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { WeatherModule } from "../components/WeatherModule"; +import { WeatherModule } from "../index"; import { moduleBlocks } from "./story-data"; const meta = { diff --git a/packages/ui/src/storybook.test.ts b/packages/ui/src/storybook.test.ts index 4ef14ba..49a9e83 100644 --- a/packages/ui/src/storybook.test.ts +++ b/packages/ui/src/storybook.test.ts @@ -9,6 +9,56 @@ const packageRoot = existsSync(join(root, "packages/ui/package.json")) const componentsDir = join(packageRoot, "src/components"); const storiesDir = join(packageRoot, "src/stories"); +const componentDomains = { + foundation: [ + "Badge", + "Button", + "IconButton", + "IconGlyph", + "ProgressMeter", + "Separator", + "StatusBadge", + "ThemeToggle", + ], + frames: [ + "CornerBracketFrame", + "DashboardFrame", + "DashboardHeader", + "DiagonalStripeField", + "FooterCell", + "FooterStatusCell", + "GridFrame", + "ModuleCard", + "Panel", + "ScanlineField", + ], + operations: [ + "ServiceGroupPanel", + "ServicePanel", + "ServiceRow", + "StatusStrip", + "SystemState", + "WeatherModule", + ], + telemetry: [ + "LineChart", + "SignalTrace", + "Sparkline", + "TelemetryCard", + "TelemetryGrid", + "TelemetryStrip", + ], +} as const; + +const componentFiles: ReadonlyMap = new Map( + Object.entries(componentDomains).flatMap(([domain, components]) => + components.map((component) => [ + component, + join(componentsDir, domain, `${component}.tsx`), + ]), + ), +); + const requiredStoryFiles = [ "Badge.stories.tsx", "Button.stories.tsx", @@ -69,10 +119,7 @@ describe("Storybook inventory", () => { test("loads dashboard component styles through the global app stylesheet", () => { const packageStyles = readFileSync(join(packageRoot, "src/styles.css"), "utf8"); - const dashboardFrame = readFileSync( - join(componentsDir, "DashboardFrame.tsx"), - "utf8", - ); + const dashboardFrame = readFileSync(requiredComponentPath("DashboardFrame"), "utf8"); expect(packageStyles).toContain('./components/styles.css'); expect(dashboardFrame).not.toContain('./styles.css'); @@ -87,9 +134,9 @@ describe("Storybook inventory", () => { }); test("keeps component and story files paired as the UI inventory changes", () => { - const componentStoryFiles = readdirSync(componentsDir) - .filter((filename) => filename.endsWith(".tsx") && !filename.endsWith(".test.tsx")) - .map((filename) => filename.replace(".tsx", ".stories.tsx")); + const componentStoryFiles = [...componentFiles.keys()].map( + (component) => `${component}.stories.tsx`, + ); for (const filename of componentStoryFiles) { expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true); @@ -114,9 +161,7 @@ describe("Storybook inventory", () => { "ScanlineField", "SignalTrace", ]) { - expect(existsSync(join(componentsDir, `${component}.tsx`)), `${component} is missing`).toBe( - true, - ); + expect(existsSync(requiredComponentPath(component)), `${component} is missing`).toBe(true); expect( existsSync(join(storiesDir, `${component}.stories.tsx`)), `${component}.stories.tsx is missing`, @@ -131,7 +176,7 @@ describe("Storybook inventory", () => { "ScanlineField", "SignalTrace", ]) { - const source = readFileSync(join(componentsDir, `${component}.tsx`), "utf8"); + const source = readFileSync(requiredComponentPath(component), "utf8"); expect(source).toContain('aria-hidden="true"'); } @@ -139,6 +184,7 @@ describe("Storybook inventory", () => { test("does not add deferred form/navigation primitives", () => { for (const component of ["Input", "ToggleGroup", "ScrollArea"]) { + expect(componentFiles.has(component)).toBe(false); expect(existsSync(join(componentsDir, `${component}.tsx`))).toBe(false); expect(existsSync(join(storiesDir, `${component}.stories.tsx`))).toBe(false); } @@ -153,3 +199,13 @@ describe("Storybook inventory", () => { expect(legacyStories).toEqual([]); }); }); + +function requiredComponentPath(component: string): string { + const componentPath = componentFiles.get(component); + + expect(componentPath, `${component} is missing from the UI component map`).toBeTypeOf( + "string", + ); + + return componentPath as string; +} From fe8d1e56422cabff437425ecafbbee9f40c9005c Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 06:50:10 +0200 Subject: [PATCH 13/50] test(ui): derive story inventory from component tree --- packages/ui/src/storybook.test.ts | 201 ++++++++++++++++-------------- 1 file changed, 110 insertions(+), 91 deletions(-) diff --git a/packages/ui/src/storybook.test.ts b/packages/ui/src/storybook.test.ts index 49a9e83..83f51c7 100644 --- a/packages/ui/src/storybook.test.ts +++ b/packages/ui/src/storybook.test.ts @@ -1,5 +1,5 @@ import { existsSync, readdirSync, readFileSync } from "node:fs"; -import { join } from "node:path"; +import { basename, join, relative } from "node:path"; import { describe, expect, test } from "vitest"; const root = process.cwd(); @@ -9,89 +9,13 @@ const packageRoot = existsSync(join(root, "packages/ui/package.json")) const componentsDir = join(packageRoot, "src/components"); const storiesDir = join(packageRoot, "src/stories"); -const componentDomains = { - foundation: [ - "Badge", - "Button", - "IconButton", - "IconGlyph", - "ProgressMeter", - "Separator", - "StatusBadge", - "ThemeToggle", - ], - frames: [ - "CornerBracketFrame", - "DashboardFrame", - "DashboardHeader", - "DiagonalStripeField", - "FooterCell", - "FooterStatusCell", - "GridFrame", - "ModuleCard", - "Panel", - "ScanlineField", - ], - operations: [ - "ServiceGroupPanel", - "ServicePanel", - "ServiceRow", - "StatusStrip", - "SystemState", - "WeatherModule", - ], - telemetry: [ - "LineChart", - "SignalTrace", - "Sparkline", - "TelemetryCard", - "TelemetryGrid", - "TelemetryStrip", - ], -} as const; - -const componentFiles: ReadonlyMap = new Map( - Object.entries(componentDomains).flatMap(([domain, components]) => - components.map((component) => [ - component, - join(componentsDir, domain, `${component}.tsx`), - ]), - ), -); - -const requiredStoryFiles = [ - "Badge.stories.tsx", - "Button.stories.tsx", - "DashboardFrame.stories.tsx", - "DashboardHeader.stories.tsx", - "DashboardOnePager.stories.tsx", - "CornerBracketFrame.stories.tsx", - "DiagonalStripeField.stories.tsx", - "FooterCell.stories.tsx", - "FooterStatusCell.stories.tsx", - "GridFrame.stories.tsx", - "IconButton.stories.tsx", - "IconGlyph.stories.tsx", - "LineChart.stories.tsx", - "ModuleCard.stories.tsx", - "Panel.stories.tsx", - "ProgressMeter.stories.tsx", - "Separator.stories.tsx", - "ServiceGroupPanel.stories.tsx", - "ServicePanel.stories.tsx", - "ServiceRow.stories.tsx", - "SignalTrace.stories.tsx", - "Sparkline.stories.tsx", - "StatusBadge.stories.tsx", - "StatusStrip.stories.tsx", - "SystemState.stories.tsx", - "ScanlineField.stories.tsx", - "TelemetryCard.stories.tsx", - "TelemetryGrid.stories.tsx", - "TelemetryStrip.stories.tsx", - "ThemeToggle.stories.tsx", - "WeatherModule.stories.tsx", +const allowedComponentDomains = [ + "foundation", + "frames", + "operations", + "telemetry", ] as const; +const compositionStoryFiles = ["DashboardOnePager.stories.tsx"] as const; const forbiddenStoryContent = [ "dimension lab", @@ -112,11 +36,23 @@ describe("Storybook inventory", () => { }); test("has a story for every reusable dashboard UI component", () => { - for (const filename of requiredStoryFiles) { - expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true); + for (const component of componentInventory()) { + expect( + existsSync(join(storiesDir, component.storyFile)), + `${component.storyFile} is missing`, + ).toBe(true); } }); + test("uses only approved component domain folders", () => { + const actualDomains = readdirSync(componentsDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .sort(); + + expect(actualDomains).toEqual([...allowedComponentDomains].sort()); + }); + test("loads dashboard component styles through the global app stylesheet", () => { const packageStyles = readFileSync(join(packageRoot, "src/styles.css"), "utf8"); const dashboardFrame = readFileSync(requiredComponentPath("DashboardFrame"), "utf8"); @@ -134,12 +70,33 @@ describe("Storybook inventory", () => { }); test("keeps component and story files paired as the UI inventory changes", () => { - const componentStoryFiles = [...componentFiles.keys()].map( - (component) => `${component}.stories.tsx`, + const componentStoryFiles = new Set( + componentInventory().map((component) => component.storyFile), ); for (const filename of componentStoryFiles) { - expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true); + expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe( + true, + ); + } + + const unpairedStoryFiles = storyFiles().filter( + (filename) => + !componentStoryFiles.has(filename) && + !compositionStoryFiles.includes( + filename as (typeof compositionStoryFiles)[number], + ), + ); + expect(unpairedStoryFiles).toEqual([]); + }); + + test("exports every reusable component through the package barrel", () => { + const indexSource = readFileSync(join(packageRoot, "src/index.ts"), "utf8"); + + for (const component of componentInventory()) { + expect(indexSource).toContain( + `export { ${component.name} } from "${component.relativeExportPath}";`, + ); } }); @@ -183,8 +140,12 @@ describe("Storybook inventory", () => { }); test("does not add deferred form/navigation primitives", () => { + const componentNames = new Set( + componentInventory().map((component) => component.name), + ); + for (const component of ["Input", "ToggleGroup", "ScrollArea"]) { - expect(componentFiles.has(component)).toBe(false); + expect(componentNames.has(component)).toBe(false); expect(existsSync(join(componentsDir, `${component}.tsx`))).toBe(false); expect(existsSync(join(storiesDir, `${component}.stories.tsx`))).toBe(false); } @@ -201,11 +162,69 @@ describe("Storybook inventory", () => { }); function requiredComponentPath(component: string): string { - const componentPath = componentFiles.get(component); + const componentPath = componentInventory().find( + (entry) => entry.name === component, + )?.path; - expect(componentPath, `${component} is missing from the UI component map`).toBeTypeOf( + expect(componentPath, `${component} is missing from the UI component tree`).toBeTypeOf( "string", ); return componentPath as string; } + +interface ComponentInventoryItem { + name: string; + path: string; + relativeExportPath: string; + storyFile: string; +} + +function componentInventory(): ComponentInventoryItem[] { + const components = allowedComponentDomains + .flatMap((domain) => collectComponentFiles(join(componentsDir, domain))) + .sort((a, b) => a.name.localeCompare(b.name)); + const names = components.map((component) => component.name); + + expect(names).toEqual([...new Set(names)]); + + return components; +} + +function collectComponentFiles(directory: string): ComponentInventoryItem[] { + return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { + const entryPath = join(directory, entry.name); + + if (entry.isDirectory()) { + return collectComponentFiles(entryPath); + } + + if ( + !entry.isFile() || + !entry.name.endsWith(".tsx") || + entry.name.endsWith(".test.tsx") + ) { + return []; + } + + const name = basename(entry.name, ".tsx"); + const relativeExportPath = `./${relative(join(packageRoot, "src"), entryPath) + .replace(/\\/g, "/") + .replace(/\.tsx$/, "")}`; + + return [ + { + name, + path: entryPath, + relativeExportPath, + storyFile: `${name}.stories.tsx`, + }, + ]; + }); +} + +function storyFiles(): string[] { + return readdirSync(storiesDir) + .filter((filename) => filename.endsWith(".stories.tsx")) + .sort(); +} From 3e46a2c0cc853bb4023f2fbd0c9c8c47b3e72f5d Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 07:01:15 +0200 Subject: [PATCH 14/50] build(container): use turbo pruned web workspace --- .containerignore | 1 + .gitignore | 1 + README.md | 9 ++-- apps/web/Containerfile | 24 +++++---- apps/web/src/lib/workspace-boundary.test.ts | 60 ++++++++++++--------- 5 files changed, 58 insertions(+), 37 deletions(-) diff --git a/.containerignore b/.containerignore index 70b617e..b4b14d1 100644 --- a/.containerignore +++ b/.containerignore @@ -5,6 +5,7 @@ coverage data dist node_modules +out playwright-report storybook-static test-results diff --git a/.gitignore b/.gitignore index b6326bb..eed0de1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ +out/ .svelte-kit/ build/ dist/ diff --git a/README.md b/README.md index 572f9bc..fee9443 100644 --- a/README.md +++ b/README.md @@ -139,10 +139,11 @@ the checked-in `apps/web/drizzle/` directory so startup migrations can run. ### Internal Container -The checked-in `apps/web/Containerfile` builds the React client and Bun server -from the workspace root into a runtime image. For the Dimension Lab internal -host, run it behind Caddy on a loopback port and mount persistent state at -`/data`: +The checked-in `apps/web/Containerfile` runs +`turbo prune @dimensionlab/web --docker`, installs the pruned manifest set, and +builds the React client plus Bun server from the pruned workspace source. For the +Dimension Lab internal host, run it behind Caddy on a loopback port and mount +persistent state at `/data`: ```sh podman build -f apps/web/Containerfile -t localhost/dimensionlab-website:latest . diff --git a/apps/web/Containerfile b/apps/web/Containerfile index 7f12f94..9ec2baa 100644 --- a/apps/web/Containerfile +++ b/apps/web/Containerfile @@ -1,19 +1,25 @@ -FROM docker.io/oven/bun:1.3.14 AS deps +FROM docker.io/oven/bun:1.3.14 AS base WORKDIR /repo -ENV PATH=/repo/apps/web/node_modules/.bin:/repo/packages/ui/node_modules/.bin:/repo/node_modules/.bin:$PATH -COPY package.json bun.lock turbo.json tsconfig.base.json ./ -COPY apps/web/package.json apps/web/package.json -COPY packages/dashboard-model/package.json packages/dashboard-model/package.json -COPY packages/ui/package.json packages/ui/package.json +ENV PATH=/repo/apps/web/node_modules/.bin:/repo/packages/dashboard-model/node_modules/.bin:/repo/packages/ui/node_modules/.bin:/repo/node_modules/.bin:$PATH + +FROM base AS pruner + +COPY . . +RUN bunx turbo prune @dimensionlab/web --docker + +FROM base AS deps + +COPY --from=pruner /repo/out/json/ ./ RUN bun install --frozen-lockfile FROM deps AS build -COPY . . -RUN bun install --frozen-lockfile && bun run build +COPY --from=pruner /repo/out/full/ ./ +COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./ +RUN bun run build -FROM deps AS runtime +FROM base AS runtime WORKDIR /repo/apps/web ENV NODE_ENV=production diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 4f4200c..9c056bf 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -114,35 +114,47 @@ describe("workspace boundaries", () => { ); }); - test("stages web workspace dependency manifests before container install", () => { - const webPackage = JSON.parse( - readFileSync(join(root, "apps/web/package.json"), "utf8"), - ) as { dependencies?: Record }; - const workspacePackages = [ - "packages/ui/package.json", - "packages/dashboard-model/package.json", - ].map((manifestPath) => { - const packageJson = JSON.parse( - readFileSync(join(root, manifestPath), "utf8"), - ) as { name?: string }; - - return [packageJson.name, manifestPath] as const; - }); - const manifestsByPackageName = new Map(workspacePackages); + test("builds the internal container from a turbo-pruned web workspace", () => { const containerfile = readFileSync( join(root, "apps/web/Containerfile"), "utf8", ); + const pruneCommand = "turbo prune @dimensionlab/web --docker"; + const jsonCopy = "COPY --from=pruner /repo/out/json/ ./"; + const sourceCopy = "COPY --from=pruner /repo/out/full/ ./"; + const tsconfigCopy = + "COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./"; + const installCommand = "RUN bun install --frozen-lockfile"; - const workspaceDependencyManifests = Object.entries( - webPackage.dependencies ?? {}, - ) - .filter(([, version]) => version.startsWith("workspace:")) - .map(([packageName]) => manifestsByPackageName.get(packageName)); + expect(containerfile).toContain("AS pruner"); + expect(containerfile).toContain(pruneCommand); + expect(containerfile).toContain(jsonCopy); + expect(containerfile).toContain(sourceCopy); + expect(containerfile).toContain(tsconfigCopy); + expect(containerfile).not.toContain( + "COPY packages/dashboard-model/package.json", + ); + expect(containerfile).not.toContain("COPY packages/ui/package.json"); - expect(workspaceDependencyManifests).not.toContain(undefined); - for (const manifestPath of workspaceDependencyManifests) { - expect(containerfile).toContain(`COPY ${manifestPath} ${manifestPath}`); - } + const jsonCopyIndex = containerfile.indexOf(jsonCopy); + const installIndex = containerfile.indexOf(installCommand); + const sourceCopyIndex = containerfile.indexOf(sourceCopy); + const tsconfigCopyIndex = containerfile.indexOf(tsconfigCopy); + + expect(jsonCopyIndex).toBeGreaterThan(-1); + expect(installIndex).toBeGreaterThan(jsonCopyIndex); + expect(sourceCopyIndex).toBeGreaterThan(installIndex); + expect(tsconfigCopyIndex).toBeGreaterThan(sourceCopyIndex); + expect(containerfile.indexOf("RUN bun run build")).toBeGreaterThan( + tsconfigCopyIndex, + ); + }); + + test("keeps local turbo prune output out of git and container contexts", () => { + const gitignore = readFileSync(join(root, ".gitignore"), "utf8"); + const containerignore = readFileSync(join(root, ".containerignore"), "utf8"); + + expect(gitignore).toContain("out/"); + expect(containerignore).toContain("out"); }); }); From 74e70cf71426289e2e6614a68a56afbb3ce66159 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 07:12:46 +0200 Subject: [PATCH 15/50] fix(container): exclude generated files from pruned build --- .containerignore | 9 ++++++++ apps/web/package.json | 2 +- apps/web/src/lib/workspace-boundary.test.ts | 25 +++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/.containerignore b/.containerignore index b4b14d1..1812091 100644 --- a/.containerignore +++ b/.containerignore @@ -11,3 +11,12 @@ storybook-static test-results .env .env.* +apps/*/.turbo +apps/*/build +apps/*/data +apps/*/dist +apps/*/playwright-report +apps/*/test-results +packages/*/.turbo +packages/*/dist +packages/*/storybook-static diff --git a/apps/web/package.json b/apps/web/package.json index 2e9fef6..790663c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "bun src/server/dev.ts", - "build": "vite build && bun build src/server/index.ts --target bun --outdir build", + "build": "rm -rf 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", "check": "tsc --noEmit", "test": "vitest run", diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 9c056bf..9cde8e8 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -151,10 +151,35 @@ describe("workspace boundaries", () => { }); test("keeps local turbo prune output out of git and container contexts", () => { + const webPackage = JSON.parse( + readFileSync(join(root, "apps/web/package.json"), "utf8"), + ) as { scripts?: Record }; const gitignore = readFileSync(join(root, ".gitignore"), "utf8"); const containerignore = readFileSync(join(root, ".containerignore"), "utf8"); + const containerIgnoreRules = new Set( + containerignore + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean), + ); expect(gitignore).toContain("out/"); expect(containerignore).toContain("out"); + expect([...containerIgnoreRules]).toEqual( + expect.arrayContaining([ + "apps/*/.turbo", + "apps/*/build", + "apps/*/data", + "apps/*/dist", + "apps/*/playwright-report", + "apps/*/test-results", + "packages/*/.turbo", + "packages/*/dist", + "packages/*/storybook-static", + ]), + ); + expect(webPackage.scripts?.build).toBe( + "rm -rf build && vite build && bun build src/server/index.ts --target bun --outdir build", + ); }); }); From a77826eab9354717c9bc39377adba47c8a8ff2e4 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 07:20:18 +0200 Subject: [PATCH 16/50] refactor(turbo): consume workspace package exports --- apps/web/src/lib/workspace-boundary.test.ts | 27 +++++++++++++++++++++ apps/web/tsconfig.json | 6 ----- apps/web/vite.config.ts | 12 --------- turbo.json | 1 + 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 9cde8e8..3bb7f85 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -114,6 +114,33 @@ describe("workspace boundaries", () => { ); }); + test("consumes workspace packages through package exports instead of source aliases", () => { + const webTsconfig = JSON.parse( + readFileSync(join(root, "apps/web/tsconfig.json"), "utf8"), + ) as { compilerOptions?: { paths?: Record } }; + const viteConfig = readFileSync(join(root, "apps/web/vite.config.ts"), "utf8"); + const turboConfig = JSON.parse( + readFileSync(join(root, "turbo.json"), "utf8"), + ) as { tasks?: Record }; + + expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty( + "@dimensionlab/dashboard-model", + ); + expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty( + "@dimensionlab/dashboard-model/fixtures", + ); + expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty("@dimensionlab/ui"); + expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty( + "@dimensionlab/ui/styles.css", + ); + expect(webTsconfig.compilerOptions?.paths).toEqual({ + "$lib/*": ["src/lib/*"], + }); + expect(viteConfig).not.toContain("../../packages/dashboard-model/src"); + expect(viteConfig).not.toContain("../../packages/ui/src"); + expect(turboConfig.tasks?.dev?.dependsOn).toEqual(["^build"]); + }); + test("builds the internal container from a turbo-pruned web workspace", () => { const containerfile = readFileSync( join(root, "apps/web/Containerfile"), diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index f9f784d..120931b 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -3,12 +3,6 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@dimensionlab/dashboard-model": ["../../packages/dashboard-model/src/index.ts"], - "@dimensionlab/dashboard-model/fixtures": [ - "../../packages/dashboard-model/src/fixtures/index.ts" - ], - "@dimensionlab/ui": ["../../packages/ui/src/index.ts"], - "@dimensionlab/ui/styles.css": ["../../packages/ui/src/styles.css"], "$lib/*": ["src/lib/*"] }, "types": ["node", "bun-types", "react", "react-dom", "vite/client"] diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 687c180..ceff746 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -24,18 +24,6 @@ export default defineConfig({ plugins: [react(), tailwindcss()], resolve: { alias: { - "@dimensionlab/ui/styles.css": fileURLToPath( - new URL("../../packages/ui/src/styles.css", import.meta.url), - ), - "@dimensionlab/ui": fileURLToPath( - new URL("../../packages/ui/src/index.ts", import.meta.url), - ), - "@dimensionlab/dashboard-model/fixtures": fileURLToPath( - new URL("../../packages/dashboard-model/src/fixtures/index.ts", import.meta.url), - ), - "@dimensionlab/dashboard-model": fileURLToPath( - new URL("../../packages/dashboard-model/src/index.ts", import.meta.url), - ), $lib: fileURLToPath(new URL("./src/lib", import.meta.url)), }, }, diff --git a/turbo.json b/turbo.json index 446d6fe..f2d3137 100644 --- a/turbo.json +++ b/turbo.json @@ -60,6 +60,7 @@ "env": ["DATABASE_URL"] }, "dev": { + "dependsOn": ["^build"], "cache": false, "persistent": true, "env": [ From 2d779165cdbd4bf999e053ef1e60a37dc9b78706 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 07:26:29 +0200 Subject: [PATCH 17/50] fix(turbo): keep package exports fresh in dev --- apps/web/src/lib/workspace-boundary.test.ts | 44 +++++++++++++++++++++ apps/web/src/server/dev.test.ts | 8 ++++ apps/web/src/server/dev.ts | 6 ++- packages/dashboard-model/package.json | 2 + packages/ui/package.json | 11 +++++- 5 files changed, 68 insertions(+), 3 deletions(-) diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 3bb7f85..2f1bfb1 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -118,6 +118,12 @@ describe("workspace boundaries", () => { const webTsconfig = JSON.parse( readFileSync(join(root, "apps/web/tsconfig.json"), "utf8"), ) as { compilerOptions?: { paths?: Record } }; + const modelPackage = JSON.parse( + readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"), + ) as { exports?: Record }; + const uiPackage = JSON.parse( + readFileSync(join(root, "packages/ui/package.json"), "utf8"), + ) as { exports?: Record }; const viteConfig = readFileSync(join(root, "apps/web/vite.config.ts"), "utf8"); const turboConfig = JSON.parse( readFileSync(join(root, "turbo.json"), "utf8"), @@ -139,6 +145,29 @@ describe("workspace boundaries", () => { expect(viteConfig).not.toContain("../../packages/dashboard-model/src"); expect(viteConfig).not.toContain("../../packages/ui/src"); expect(turboConfig.tasks?.dev?.dependsOn).toEqual(["^build"]); + expectPackageExport(modelPackage.exports?.["."], { + types: "./dist/index.d.ts", + development: "./src/index.ts", + default: "./dist/index.js", + }); + expectPackageExport(modelPackage.exports?.["./fixtures"], { + types: "./dist/fixtures/index.d.ts", + development: "./src/fixtures/index.ts", + default: "./dist/fixtures/index.js", + }); + expectPackageExport(uiPackage.exports?.["."], { + types: "./dist/index.d.ts", + development: "./src/index.ts", + default: "./dist/index.js", + }); + expectPackageExport(uiPackage.exports?.["./styles.css"], { + development: "./src/styles.css", + default: "./dist/styles.css", + }); + expectPackageExport(uiPackage.exports?.["./tokens.css"], { + development: "./src/tokens.css", + default: "./dist/tokens.css", + }); }); test("builds the internal container from a turbo-pruned web workspace", () => { @@ -210,3 +239,18 @@ describe("workspace boundaries", () => { ); }); }); + +type WorkspacePackageExport = + | string + | { + types?: string; + development?: string; + default?: string; + }; + +function expectPackageExport( + actual: WorkspacePackageExport | undefined, + expected: Exclude, +): void { + expect(actual).toMatchObject(expected); +} diff --git a/apps/web/src/server/dev.test.ts b/apps/web/src/server/dev.test.ts index f43f953..60031db 100644 --- a/apps/web/src/server/dev.test.ts +++ b/apps/web/src/server/dev.test.ts @@ -2,6 +2,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, test } from "vitest"; import { createDevServerConfig } from "../../vite.config"; +import { apiServerArgs } from "./dev"; describe("local development runtime", () => { test("starts the Bun API server together with the Vite dev server", () => { @@ -22,4 +23,11 @@ describe("local development runtime", () => { changeOrigin: true, }); }); + + test("uses development package export conditions for the Bun API server", () => { + expect(apiServerArgs).toEqual([ + "--conditions=development", + "src/server/index.ts", + ]); + }); }); diff --git a/apps/web/src/server/dev.ts b/apps/web/src/server/dev.ts index c515240..fa4dc2f 100644 --- a/apps/web/src/server/dev.ts +++ b/apps/web/src/server/dev.ts @@ -3,6 +3,10 @@ const webPort = process.env.PORT || "5173"; const apiHost = process.env.DASHBOARD_DEV_API_HOST || "127.0.0.1"; const apiPort = process.env.DASHBOARD_DEV_API_PORT || "5174"; const apiTarget = `http://${apiHost}:${apiPort}`; +export const apiServerArgs = [ + "--conditions=development", + "src/server/index.ts", +] as const; if (import.meta.main) { runDevServers(); @@ -51,7 +55,7 @@ export function runDevServers(): void { process.on("SIGINT", () => shutdown(0)); process.on("SIGTERM", () => shutdown(0)); - spawn("api server", [process.execPath, "src/server/index.ts"], { + spawn("api server", [process.execPath, ...apiServerArgs], { HOST: apiHost, PORT: apiPort, }); diff --git a/packages/dashboard-model/package.json b/packages/dashboard-model/package.json index 978522e..ae9a49b 100644 --- a/packages/dashboard-model/package.json +++ b/packages/dashboard-model/package.json @@ -6,10 +6,12 @@ "exports": { ".": { "types": "./dist/index.d.ts", + "development": "./src/index.ts", "default": "./dist/index.js" }, "./fixtures": { "types": "./dist/fixtures/index.d.ts", + "development": "./src/fixtures/index.ts", "default": "./dist/fixtures/index.js" } }, diff --git a/packages/ui/package.json b/packages/ui/package.json index cb7febf..897043c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -7,10 +7,17 @@ "exports": { ".": { "types": "./dist/index.d.ts", + "development": "./src/index.ts", "default": "./dist/index.js" }, - "./styles.css": "./dist/styles.css", - "./tokens.css": "./dist/tokens.css" + "./styles.css": { + "development": "./src/styles.css", + "default": "./dist/styles.css" + }, + "./tokens.css": { + "development": "./src/tokens.css", + "default": "./dist/tokens.css" + } }, "scripts": { "build": "rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/components && cp src/styles.css dist/styles.css && cp src/tokens.css dist/tokens.css && cp src/components/styles.css dist/components/styles.css", From ef43aac6dfab3367833e8665cf045bd879c622c0 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 07:31:35 +0200 Subject: [PATCH 18/50] feat(ui): expose grouped component entrypoints --- packages/ui/package.json | 20 ++++++++ .../ui/src/components/foundation/index.ts | 8 +++ packages/ui/src/components/frames/index.ts | 10 ++++ .../ui/src/components/operations/index.ts | 6 +++ packages/ui/src/components/telemetry/index.ts | 6 +++ packages/ui/src/index.ts | 4 ++ packages/ui/src/storybook.test.ts | 51 +++++++++++++++++++ 7 files changed, 105 insertions(+) create mode 100644 packages/ui/src/components/foundation/index.ts create mode 100644 packages/ui/src/components/frames/index.ts create mode 100644 packages/ui/src/components/operations/index.ts create mode 100644 packages/ui/src/components/telemetry/index.ts diff --git a/packages/ui/package.json b/packages/ui/package.json index 897043c..a30db52 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -10,6 +10,26 @@ "development": "./src/index.ts", "default": "./dist/index.js" }, + "./foundation": { + "types": "./dist/components/foundation/index.d.ts", + "development": "./src/components/foundation/index.ts", + "default": "./dist/components/foundation/index.js" + }, + "./frames": { + "types": "./dist/components/frames/index.d.ts", + "development": "./src/components/frames/index.ts", + "default": "./dist/components/frames/index.js" + }, + "./operations": { + "types": "./dist/components/operations/index.d.ts", + "development": "./src/components/operations/index.ts", + "default": "./dist/components/operations/index.js" + }, + "./telemetry": { + "types": "./dist/components/telemetry/index.d.ts", + "development": "./src/components/telemetry/index.ts", + "default": "./dist/components/telemetry/index.js" + }, "./styles.css": { "development": "./src/styles.css", "default": "./dist/styles.css" diff --git a/packages/ui/src/components/foundation/index.ts b/packages/ui/src/components/foundation/index.ts new file mode 100644 index 0000000..36ed868 --- /dev/null +++ b/packages/ui/src/components/foundation/index.ts @@ -0,0 +1,8 @@ +export { Badge } from "./Badge"; +export { Button } from "./Button"; +export { IconButton } from "./IconButton"; +export { IconGlyph } from "./IconGlyph"; +export { ProgressMeter } from "./ProgressMeter"; +export { Separator } from "./Separator"; +export { StatusBadge } from "./StatusBadge"; +export { ThemeToggle } from "./ThemeToggle"; diff --git a/packages/ui/src/components/frames/index.ts b/packages/ui/src/components/frames/index.ts new file mode 100644 index 0000000..66a48c9 --- /dev/null +++ b/packages/ui/src/components/frames/index.ts @@ -0,0 +1,10 @@ +export { CornerBracketFrame } from "./CornerBracketFrame"; +export { DashboardFrame } from "./DashboardFrame"; +export { DashboardHeader } from "./DashboardHeader"; +export { DiagonalStripeField } from "./DiagonalStripeField"; +export { FooterCell } from "./FooterCell"; +export { FooterStatusCell } from "./FooterStatusCell"; +export { GridFrame } from "./GridFrame"; +export { ModuleCard } from "./ModuleCard"; +export { Panel } from "./Panel"; +export { ScanlineField } from "./ScanlineField"; diff --git a/packages/ui/src/components/operations/index.ts b/packages/ui/src/components/operations/index.ts new file mode 100644 index 0000000..27ab623 --- /dev/null +++ b/packages/ui/src/components/operations/index.ts @@ -0,0 +1,6 @@ +export { ServiceGroupPanel } from "./ServiceGroupPanel"; +export { ServicePanel } from "./ServicePanel"; +export { ServiceRow } from "./ServiceRow"; +export { StatusStrip } from "./StatusStrip"; +export { SystemState } from "./SystemState"; +export { WeatherModule } from "./WeatherModule"; diff --git a/packages/ui/src/components/telemetry/index.ts b/packages/ui/src/components/telemetry/index.ts new file mode 100644 index 0000000..4ba32fa --- /dev/null +++ b/packages/ui/src/components/telemetry/index.ts @@ -0,0 +1,6 @@ +export { LineChart } from "./LineChart"; +export { SignalTrace } from "./SignalTrace"; +export { Sparkline } from "./Sparkline"; +export { TelemetryCard } from "./TelemetryCard"; +export { TelemetryGrid } from "./TelemetryGrid"; +export { TelemetryStrip } from "./TelemetryStrip"; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index bc506c0..e1fe97e 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,3 +1,7 @@ +export * from "./components/foundation"; +export * from "./components/frames"; +export * from "./components/operations"; +export * from "./components/telemetry"; export { Badge } from "./components/foundation/Badge"; export { Button } from "./components/foundation/Button"; export { IconGlyph } from "./components/foundation/IconGlyph"; diff --git a/packages/ui/src/storybook.test.ts b/packages/ui/src/storybook.test.ts index 83f51c7..050170b 100644 --- a/packages/ui/src/storybook.test.ts +++ b/packages/ui/src/storybook.test.ts @@ -100,6 +100,43 @@ describe("Storybook inventory", () => { } }); + test("exposes grouped package entrypoints for each component domain", () => { + const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as { + exports?: Record; + }; + const rootIndexSource = readFileSync(join(packageRoot, "src/index.ts"), "utf8"); + const componentsByDomain = new Map<(typeof allowedComponentDomains)[number], string[]>(); + + for (const domain of allowedComponentDomains) { + componentsByDomain.set(domain, []); + } + + for (const component of componentInventory()) { + componentsByDomain.get(component.domain)?.push(component.name); + } + + for (const domain of allowedComponentDomains) { + const domainIndexPath = join(componentsDir, domain, "index.ts"); + const domainIndexSource = existsSync(domainIndexPath) + ? readFileSync(domainIndexPath, "utf8") + : ""; + + expect(existsSync(domainIndexPath), `${domain} index is missing`).toBe(true); + expect(rootIndexSource).toContain(`export * from "./components/${domain}";`); + expect(packageJson.exports?.[`./${domain}`]).toMatchObject({ + types: `./dist/components/${domain}/index.d.ts`, + development: `./src/components/${domain}/index.ts`, + default: `./dist/components/${domain}/index.js`, + }); + + for (const componentName of componentsByDomain.get(domain) ?? []) { + expect(domainIndexSource).toContain( + `export { ${componentName} } from "./${componentName}";`, + ); + } + } + }); + test("keeps Storybook fixtures generic and content-free", () => { const storyText = readdirSync(storiesDir) .filter((filename) => filename.endsWith(".tsx") || filename.endsWith(".ts")) @@ -174,12 +211,21 @@ function requiredComponentPath(component: string): string { } interface ComponentInventoryItem { + domain: (typeof allowedComponentDomains)[number]; name: string; path: string; relativeExportPath: string; storyFile: string; } +type WorkspacePackageExport = + | string + | { + types?: string; + development?: string; + default?: string; + }; + function componentInventory(): ComponentInventoryItem[] { const components = allowedComponentDomains .flatMap((domain) => collectComponentFiles(join(componentsDir, domain))) @@ -208,12 +254,17 @@ function collectComponentFiles(directory: string): ComponentInventoryItem[] { } const name = basename(entry.name, ".tsx"); + const relativeComponentPath = relative(componentsDir, entryPath).replace(/\\/g, "/"); + const domain = relativeComponentPath.split( + "/", + )[0] as (typeof allowedComponentDomains)[number]; const relativeExportPath = `./${relative(join(packageRoot, "src"), entryPath) .replace(/\\/g, "/") .replace(/\.tsx$/, "")}`; return [ { + domain, name, path: entryPath, relativeExportPath, From 2535dccff2650d4c31ecb33579b4d0f5f95f2861 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 07:37:15 +0200 Subject: [PATCH 19/50] feat(ui): export component prop types --- .../ui/src/components/foundation/index.ts | 16 +++++++------- packages/ui/src/components/frames/index.ts | 20 ++++++++--------- .../ui/src/components/operations/index.ts | 12 +++++----- packages/ui/src/components/telemetry/index.ts | 12 +++++----- packages/ui/src/storybook.test.ts | 22 ++++++++++++++++++- 5 files changed, 51 insertions(+), 31 deletions(-) diff --git a/packages/ui/src/components/foundation/index.ts b/packages/ui/src/components/foundation/index.ts index 36ed868..b82cd47 100644 --- a/packages/ui/src/components/foundation/index.ts +++ b/packages/ui/src/components/foundation/index.ts @@ -1,8 +1,8 @@ -export { Badge } from "./Badge"; -export { Button } from "./Button"; -export { IconButton } from "./IconButton"; -export { IconGlyph } from "./IconGlyph"; -export { ProgressMeter } from "./ProgressMeter"; -export { Separator } from "./Separator"; -export { StatusBadge } from "./StatusBadge"; -export { ThemeToggle } from "./ThemeToggle"; +export * from "./Badge"; +export * from "./Button"; +export * from "./IconButton"; +export * from "./IconGlyph"; +export * from "./ProgressMeter"; +export * from "./Separator"; +export * from "./StatusBadge"; +export * from "./ThemeToggle"; diff --git a/packages/ui/src/components/frames/index.ts b/packages/ui/src/components/frames/index.ts index 66a48c9..80b093d 100644 --- a/packages/ui/src/components/frames/index.ts +++ b/packages/ui/src/components/frames/index.ts @@ -1,10 +1,10 @@ -export { CornerBracketFrame } from "./CornerBracketFrame"; -export { DashboardFrame } from "./DashboardFrame"; -export { DashboardHeader } from "./DashboardHeader"; -export { DiagonalStripeField } from "./DiagonalStripeField"; -export { FooterCell } from "./FooterCell"; -export { FooterStatusCell } from "./FooterStatusCell"; -export { GridFrame } from "./GridFrame"; -export { ModuleCard } from "./ModuleCard"; -export { Panel } from "./Panel"; -export { ScanlineField } from "./ScanlineField"; +export * from "./CornerBracketFrame"; +export * from "./DashboardFrame"; +export * from "./DashboardHeader"; +export * from "./DiagonalStripeField"; +export * from "./FooterCell"; +export * from "./FooterStatusCell"; +export * from "./GridFrame"; +export * from "./ModuleCard"; +export * from "./Panel"; +export * from "./ScanlineField"; diff --git a/packages/ui/src/components/operations/index.ts b/packages/ui/src/components/operations/index.ts index 27ab623..127a1e0 100644 --- a/packages/ui/src/components/operations/index.ts +++ b/packages/ui/src/components/operations/index.ts @@ -1,6 +1,6 @@ -export { ServiceGroupPanel } from "./ServiceGroupPanel"; -export { ServicePanel } from "./ServicePanel"; -export { ServiceRow } from "./ServiceRow"; -export { StatusStrip } from "./StatusStrip"; -export { SystemState } from "./SystemState"; -export { WeatherModule } from "./WeatherModule"; +export * from "./ServiceGroupPanel"; +export * from "./ServicePanel"; +export * from "./ServiceRow"; +export * from "./StatusStrip"; +export * from "./SystemState"; +export * from "./WeatherModule"; diff --git a/packages/ui/src/components/telemetry/index.ts b/packages/ui/src/components/telemetry/index.ts index 4ba32fa..a77693e 100644 --- a/packages/ui/src/components/telemetry/index.ts +++ b/packages/ui/src/components/telemetry/index.ts @@ -1,6 +1,6 @@ -export { LineChart } from "./LineChart"; -export { SignalTrace } from "./SignalTrace"; -export { Sparkline } from "./Sparkline"; -export { TelemetryCard } from "./TelemetryCard"; -export { TelemetryGrid } from "./TelemetryGrid"; -export { TelemetryStrip } from "./TelemetryStrip"; +export * from "./LineChart"; +export * from "./SignalTrace"; +export * from "./Sparkline"; +export * from "./TelemetryCard"; +export * from "./TelemetryGrid"; +export * from "./TelemetryStrip"; diff --git a/packages/ui/src/storybook.test.ts b/packages/ui/src/storybook.test.ts index 050170b..9b73c69 100644 --- a/packages/ui/src/storybook.test.ts +++ b/packages/ui/src/storybook.test.ts @@ -131,12 +131,32 @@ describe("Storybook inventory", () => { for (const componentName of componentsByDomain.get(domain) ?? []) { expect(domainIndexSource).toContain( - `export { ${componentName} } from "./${componentName}";`, + `export * from "./${componentName}";`, ); } } }); + test("exports component prop interfaces through grouped package entrypoints", () => { + for (const component of componentInventory()) { + const source = readFileSync(component.path, "utf8"); + const propTypeName = `${component.name}Props`; + + if (!source.match(new RegExp(`export\\\\s+(interface|type)\\\\s+${propTypeName}\\\\b`))) { + continue; + } + + const domainIndexSource = readFileSync( + join(componentsDir, component.domain, "index.ts"), + "utf8", + ); + + expect(domainIndexSource).toContain( + `export * from "./${component.name}";`, + ); + } + }); + test("keeps Storybook fixtures generic and content-free", () => { const storyText = readdirSync(storiesDir) .filter((filename) => filename.endsWith(".tsx") || filename.endsWith(".ts")) From 5dccd70e0b3527fd827ee9b14f6cd9fd4a854f80 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 07:40:52 +0200 Subject: [PATCH 20/50] test(ui): assert public prop exports are detected --- packages/ui/src/storybook.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/storybook.test.ts b/packages/ui/src/storybook.test.ts index 9b73c69..9a18ecd 100644 --- a/packages/ui/src/storybook.test.ts +++ b/packages/ui/src/storybook.test.ts @@ -138,14 +138,20 @@ describe("Storybook inventory", () => { }); test("exports component prop interfaces through grouped package entrypoints", () => { + let propInterfaceCount = 0; + for (const component of componentInventory()) { const source = readFileSync(component.path, "utf8"); const propTypeName = `${component.name}Props`; + const propExportPattern = new RegExp( + `export\\s+(interface|type)\\s+${propTypeName}\\b`, + ); - if (!source.match(new RegExp(`export\\\\s+(interface|type)\\\\s+${propTypeName}\\\\b`))) { + if (!propExportPattern.test(source)) { continue; } + propInterfaceCount += 1; const domainIndexSource = readFileSync( join(componentsDir, component.domain, "index.ts"), "utf8", @@ -155,6 +161,8 @@ describe("Storybook inventory", () => { `export * from "./${component.name}";`, ); } + + expect(propInterfaceCount).toBeGreaterThan(0); }); test("keeps Storybook fixtures generic and content-free", () => { From 24e7e8eda0445a2fd45cfe78548521bfed1caf39 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 07:48:54 +0200 Subject: [PATCH 21/50] fix(turbo): hash local env inputs --- apps/web/src/lib/workspace-boundary.test.ts | 15 +++++++++++++++ turbo.json | 2 ++ 2 files changed, 17 insertions(+) diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 2f1bfb1..38a3f1b 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -74,6 +74,21 @@ describe("workspace boundaries", () => { ); }); + test("accounts for local env files in cacheable Vite and Storybook task hashes", () => { + const turboConfig = JSON.parse( + readFileSync(join(root, "turbo.json"), "utf8"), + ) as { + tasks?: Record; + }; + + for (const taskName of ["build", "build-storybook"]) { + expect(turboConfig.tasks?.[taskName]?.inputs).toEqual([ + "$TURBO_DEFAULT$", + ".env*", + ]); + } + }); + test("keeps the website app and reusable UI library as separate packages", () => { const webPackage = JSON.parse( readFileSync(join(root, "apps/web/package.json"), "utf8"), diff --git a/turbo.json b/turbo.json index f2d3137..e1b2e7c 100644 --- a/turbo.json +++ b/turbo.json @@ -9,6 +9,7 @@ "tasks": { "build": { "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", ".env*"], "outputs": ["dist/**", "build/**"], "env": ["NODE_ENV", "VITE_*"] }, @@ -29,6 +30,7 @@ }, "build-storybook": { "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", ".env*"], "outputs": ["storybook-static/**"], "env": ["NODE_ENV", "STORYBOOK_*", "VITE_*"] }, From 7a3a5b8df731f7a78273705cdc8ecde0a1c37313 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 20 Jun 2026 12:11:45 +0200 Subject: [PATCH 22/50] refactor: move ui package to submodule --- .gitmodules | 4 + packages/ui | 1 + packages/ui/.storybook/main.ts | 15 - packages/ui/.storybook/preview.ts | 51 - packages/ui/components.json | 25 - packages/ui/package.json | 79 -- .../ui/src/components/foundation/Badge.tsx | 11 - .../ui/src/components/foundation/Button.tsx | 39 - .../src/components/foundation/IconButton.tsx | 33 - .../src/components/foundation/IconGlyph.tsx | 21 - .../components/foundation/ProgressMeter.tsx | 34 - .../src/components/foundation/Separator.tsx | 19 - .../src/components/foundation/StatusBadge.tsx | 14 - .../src/components/foundation/ThemeToggle.tsx | 36 - .../ui/src/components/foundation/index.ts | 8 - .../components/frames/CornerBracketFrame.tsx | 26 - .../src/components/frames/DashboardFrame.tsx | 59 -- .../src/components/frames/DashboardHeader.tsx | 30 - .../components/frames/DiagonalStripeField.tsx | 24 - .../ui/src/components/frames/FooterCell.tsx | 42 - .../components/frames/FooterStatusCell.tsx | 6 - .../ui/src/components/frames/GridFrame.tsx | 13 - .../ui/src/components/frames/ModuleCard.tsx | 32 - packages/ui/src/components/frames/Panel.tsx | 19 - .../src/components/frames/ScanlineField.tsx | 21 - packages/ui/src/components/frames/index.ts | 10 - .../operations/ServiceGroupPanel.tsx | 6 - .../components/operations/ServicePanel.tsx | 27 - .../src/components/operations/ServiceRow.tsx | 56 - .../src/components/operations/StatusStrip.tsx | 23 - .../src/components/operations/SystemState.tsx | 26 - .../components/operations/WeatherModule.tsx | 6 - .../ui/src/components/operations/index.ts | 6 - packages/ui/src/components/render.test.tsx | 204 ---- packages/ui/src/components/styles.css | 957 ------------------ .../ui/src/components/telemetry/LineChart.tsx | 114 --- .../src/components/telemetry/SignalTrace.tsx | 26 - .../ui/src/components/telemetry/Sparkline.tsx | 37 - .../components/telemetry/TelemetryCard.tsx | 59 -- .../components/telemetry/TelemetryGrid.tsx | 29 - .../components/telemetry/TelemetryStrip.tsx | 6 - packages/ui/src/components/telemetry/index.ts | 6 - packages/ui/src/content-boundary.test.ts | 87 -- packages/ui/src/css.d.ts | 1 - packages/ui/src/fixtures.ts | 132 --- packages/ui/src/format.ts | 43 - packages/ui/src/index.ts | 54 - packages/ui/src/primitives/alert.tsx | 76 -- packages/ui/src/primitives/badge.tsx | 49 - packages/ui/src/primitives/button.tsx | 67 -- packages/ui/src/primitives/card.tsx | 103 -- packages/ui/src/primitives/progress.tsx | 29 - packages/ui/src/primitives/separator.tsx | 28 - packages/ui/src/primitives/skeleton.tsx | 13 - packages/ui/src/stories/Badge.stories.tsx | 16 - packages/ui/src/stories/Button.stories.tsx | 21 - .../stories/CornerBracketFrame.stories.tsx | 16 - .../ui/src/stories/DashboardFrame.stories.tsx | 21 - .../src/stories/DashboardHeader.stories.tsx | 19 - .../src/stories/DashboardOnePager.stories.tsx | 16 - .../stories/DiagonalStripeField.stories.tsx | 16 - .../ui/src/stories/FooterCell.stories.tsx | 21 - .../src/stories/FooterStatusCell.stories.tsx | 16 - packages/ui/src/stories/GridFrame.stories.tsx | 24 - .../ui/src/stories/IconButton.stories.tsx | 16 - packages/ui/src/stories/IconGlyph.stories.tsx | 17 - packages/ui/src/stories/LineChart.stories.tsx | 17 - .../ui/src/stories/ModuleCard.stories.tsx | 21 - packages/ui/src/stories/Panel.stories.tsx | 22 - .../ui/src/stories/ProgressMeter.stories.tsx | 17 - .../ui/src/stories/ScanlineField.stories.tsx | 16 - packages/ui/src/stories/Separator.stories.tsx | 15 - .../src/stories/ServiceGroupPanel.stories.tsx | 16 - .../ui/src/stories/ServicePanel.stories.tsx | 16 - .../ui/src/stories/ServiceRow.stories.tsx | 21 - .../ui/src/stories/SignalTrace.stories.tsx | 16 - packages/ui/src/stories/Sparkline.stories.tsx | 16 - .../ui/src/stories/StatusBadge.stories.tsx | 16 - .../ui/src/stories/StatusStrip.stories.tsx | 17 - .../ui/src/stories/SystemState.stories.tsx | 18 - .../ui/src/stories/TelemetryCard.stories.tsx | 21 - .../ui/src/stories/TelemetryGrid.stories.tsx | 21 - .../ui/src/stories/TelemetryStrip.stories.tsx | 16 - .../ui/src/stories/ThemeToggle.stories.tsx | 26 - .../ui/src/stories/WeatherModule.stories.tsx | 16 - packages/ui/src/stories/story-data.ts | 258 ----- packages/ui/src/storybook.test.ts | 309 ------ packages/ui/src/styles.css | 4 - packages/ui/src/theme.test.ts | 56 - packages/ui/src/theme.ts | 68 -- packages/ui/src/tokens.css | 160 --- packages/ui/src/types.ts | 88 -- packages/ui/src/utils.ts | 6 - packages/ui/tsconfig.build.json | 19 - packages/ui/tsconfig.json | 8 - 95 files changed, 5 insertions(+), 4476 deletions(-) create mode 100644 .gitmodules create mode 160000 packages/ui delete mode 100644 packages/ui/.storybook/main.ts delete mode 100644 packages/ui/.storybook/preview.ts delete mode 100644 packages/ui/components.json delete mode 100644 packages/ui/package.json delete mode 100644 packages/ui/src/components/foundation/Badge.tsx delete mode 100644 packages/ui/src/components/foundation/Button.tsx delete mode 100644 packages/ui/src/components/foundation/IconButton.tsx delete mode 100644 packages/ui/src/components/foundation/IconGlyph.tsx delete mode 100644 packages/ui/src/components/foundation/ProgressMeter.tsx delete mode 100644 packages/ui/src/components/foundation/Separator.tsx delete mode 100644 packages/ui/src/components/foundation/StatusBadge.tsx delete mode 100644 packages/ui/src/components/foundation/ThemeToggle.tsx delete mode 100644 packages/ui/src/components/foundation/index.ts delete mode 100644 packages/ui/src/components/frames/CornerBracketFrame.tsx delete mode 100644 packages/ui/src/components/frames/DashboardFrame.tsx delete mode 100644 packages/ui/src/components/frames/DashboardHeader.tsx delete mode 100644 packages/ui/src/components/frames/DiagonalStripeField.tsx delete mode 100644 packages/ui/src/components/frames/FooterCell.tsx delete mode 100644 packages/ui/src/components/frames/FooterStatusCell.tsx delete mode 100644 packages/ui/src/components/frames/GridFrame.tsx delete mode 100644 packages/ui/src/components/frames/ModuleCard.tsx delete mode 100644 packages/ui/src/components/frames/Panel.tsx delete mode 100644 packages/ui/src/components/frames/ScanlineField.tsx delete mode 100644 packages/ui/src/components/frames/index.ts delete mode 100644 packages/ui/src/components/operations/ServiceGroupPanel.tsx delete mode 100644 packages/ui/src/components/operations/ServicePanel.tsx delete mode 100644 packages/ui/src/components/operations/ServiceRow.tsx delete mode 100644 packages/ui/src/components/operations/StatusStrip.tsx delete mode 100644 packages/ui/src/components/operations/SystemState.tsx delete mode 100644 packages/ui/src/components/operations/WeatherModule.tsx delete mode 100644 packages/ui/src/components/operations/index.ts delete mode 100644 packages/ui/src/components/render.test.tsx delete mode 100644 packages/ui/src/components/styles.css delete mode 100644 packages/ui/src/components/telemetry/LineChart.tsx delete mode 100644 packages/ui/src/components/telemetry/SignalTrace.tsx delete mode 100644 packages/ui/src/components/telemetry/Sparkline.tsx delete mode 100644 packages/ui/src/components/telemetry/TelemetryCard.tsx delete mode 100644 packages/ui/src/components/telemetry/TelemetryGrid.tsx delete mode 100644 packages/ui/src/components/telemetry/TelemetryStrip.tsx delete mode 100644 packages/ui/src/components/telemetry/index.ts delete mode 100644 packages/ui/src/content-boundary.test.ts delete mode 100644 packages/ui/src/css.d.ts delete mode 100644 packages/ui/src/fixtures.ts delete mode 100644 packages/ui/src/format.ts delete mode 100644 packages/ui/src/index.ts delete mode 100644 packages/ui/src/primitives/alert.tsx delete mode 100644 packages/ui/src/primitives/badge.tsx delete mode 100644 packages/ui/src/primitives/button.tsx delete mode 100644 packages/ui/src/primitives/card.tsx delete mode 100644 packages/ui/src/primitives/progress.tsx delete mode 100644 packages/ui/src/primitives/separator.tsx delete mode 100644 packages/ui/src/primitives/skeleton.tsx delete mode 100644 packages/ui/src/stories/Badge.stories.tsx delete mode 100644 packages/ui/src/stories/Button.stories.tsx delete mode 100644 packages/ui/src/stories/CornerBracketFrame.stories.tsx delete mode 100644 packages/ui/src/stories/DashboardFrame.stories.tsx delete mode 100644 packages/ui/src/stories/DashboardHeader.stories.tsx delete mode 100644 packages/ui/src/stories/DashboardOnePager.stories.tsx delete mode 100644 packages/ui/src/stories/DiagonalStripeField.stories.tsx delete mode 100644 packages/ui/src/stories/FooterCell.stories.tsx delete mode 100644 packages/ui/src/stories/FooterStatusCell.stories.tsx delete mode 100644 packages/ui/src/stories/GridFrame.stories.tsx delete mode 100644 packages/ui/src/stories/IconButton.stories.tsx delete mode 100644 packages/ui/src/stories/IconGlyph.stories.tsx delete mode 100644 packages/ui/src/stories/LineChart.stories.tsx delete mode 100644 packages/ui/src/stories/ModuleCard.stories.tsx delete mode 100644 packages/ui/src/stories/Panel.stories.tsx delete mode 100644 packages/ui/src/stories/ProgressMeter.stories.tsx delete mode 100644 packages/ui/src/stories/ScanlineField.stories.tsx delete mode 100644 packages/ui/src/stories/Separator.stories.tsx delete mode 100644 packages/ui/src/stories/ServiceGroupPanel.stories.tsx delete mode 100644 packages/ui/src/stories/ServicePanel.stories.tsx delete mode 100644 packages/ui/src/stories/ServiceRow.stories.tsx delete mode 100644 packages/ui/src/stories/SignalTrace.stories.tsx delete mode 100644 packages/ui/src/stories/Sparkline.stories.tsx delete mode 100644 packages/ui/src/stories/StatusBadge.stories.tsx delete mode 100644 packages/ui/src/stories/StatusStrip.stories.tsx delete mode 100644 packages/ui/src/stories/SystemState.stories.tsx delete mode 100644 packages/ui/src/stories/TelemetryCard.stories.tsx delete mode 100644 packages/ui/src/stories/TelemetryGrid.stories.tsx delete mode 100644 packages/ui/src/stories/TelemetryStrip.stories.tsx delete mode 100644 packages/ui/src/stories/ThemeToggle.stories.tsx delete mode 100644 packages/ui/src/stories/WeatherModule.stories.tsx delete mode 100644 packages/ui/src/stories/story-data.ts delete mode 100644 packages/ui/src/storybook.test.ts delete mode 100644 packages/ui/src/styles.css delete mode 100644 packages/ui/src/theme.test.ts delete mode 100644 packages/ui/src/theme.ts delete mode 100644 packages/ui/src/tokens.css delete mode 100644 packages/ui/src/types.ts delete mode 100644 packages/ui/src/utils.ts delete mode 100644 packages/ui/tsconfig.build.json delete mode 100644 packages/ui/tsconfig.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..32d1c14 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "packages/ui"] + path = packages/ui + url = ssh://git@git.dimensionlab.net/vince/dimensionlab-ui.git + branch = main diff --git a/packages/ui b/packages/ui new file mode 160000 index 0000000..000a3ac --- /dev/null +++ b/packages/ui @@ -0,0 +1 @@ +Subproject commit 000a3ac643f33328ad9fa84d9213d7f77ddcae6e diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts deleted file mode 100644 index 34237be..0000000 --- a/packages/ui/.storybook/main.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { StorybookConfig } from "@storybook/react-vite"; - -const config: StorybookConfig = { - stories: ["../src/**/*.stories.@(js|ts|tsx)"], - addons: [ - "@storybook/addon-a11y", - "@storybook/addon-vitest", - ], - framework: { - name: "@storybook/react-vite", - options: {}, - }, -}; - -export default config; diff --git a/packages/ui/.storybook/preview.ts b/packages/ui/.storybook/preview.ts deleted file mode 100644 index 87df132..0000000 --- a/packages/ui/.storybook/preview.ts +++ /dev/null @@ -1,51 +0,0 @@ -import "../src/styles.css"; -import type { Preview } from "@storybook/react-vite"; - -const preview: Preview = { - decorators: [ - (Story, context) => { - const theme = context.globals.theme === "light" ? "light" : "dark"; - - if (typeof document !== "undefined") { - document.documentElement.setAttribute("data-ui-theme", theme); - } - - return Story(); - }, - ], - globalTypes: { - theme: { - description: "Dashboard component theme", - defaultValue: "dark", - toolbar: { - title: "Theme", - icon: "circlehollow", - items: [ - { value: "dark", title: "Dark" }, - { value: "light", title: "Light" }, - ], - dynamicTitle: true, - }, - }, - }, - parameters: { - backgrounds: { - default: "canvas", - values: [ - { name: "canvas", value: "#0b0f0d" }, - { name: "raised", value: "#151d18" }, - { name: "light canvas", value: "#eef2e7" }, - { name: "light raised", value: "#f1f5ea" }, - ], - }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - layout: "fullscreen", - }, -}; - -export default preview; diff --git a/packages/ui/components.json b/packages/ui/components.json deleted file mode 100644 index d951369..0000000 --- a/packages/ui/components.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "radix-nova", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "", - "css": "src/styles.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "iconLibrary": "lucide", - "rtl": false, - "aliases": { - "components": "src/components", - "utils": "src/utils", - "ui": "src/primitives", - "lib": "src", - "hooks": "src/hooks" - }, - "menuColor": "default", - "menuAccent": "subtle", - "registries": {} -} diff --git a/packages/ui/package.json b/packages/ui/package.json deleted file mode 100644 index a30db52..0000000 --- a/packages/ui/package.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "name": "@dimensionlab/ui", - "version": "0.0.1", - "private": true, - "type": "module", - "sideEffects": ["*.css", "**/*.css"], - "exports": { - ".": { - "types": "./dist/index.d.ts", - "development": "./src/index.ts", - "default": "./dist/index.js" - }, - "./foundation": { - "types": "./dist/components/foundation/index.d.ts", - "development": "./src/components/foundation/index.ts", - "default": "./dist/components/foundation/index.js" - }, - "./frames": { - "types": "./dist/components/frames/index.d.ts", - "development": "./src/components/frames/index.ts", - "default": "./dist/components/frames/index.js" - }, - "./operations": { - "types": "./dist/components/operations/index.d.ts", - "development": "./src/components/operations/index.ts", - "default": "./dist/components/operations/index.js" - }, - "./telemetry": { - "types": "./dist/components/telemetry/index.d.ts", - "development": "./src/components/telemetry/index.ts", - "default": "./dist/components/telemetry/index.js" - }, - "./styles.css": { - "development": "./src/styles.css", - "default": "./dist/styles.css" - }, - "./tokens.css": { - "development": "./src/tokens.css", - "default": "./dist/tokens.css" - } - }, - "scripts": { - "build": "rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/components && cp src/styles.css dist/styles.css && cp src/tokens.css dist/tokens.css && cp src/components/styles.css dist/components/styles.css", - "check": "tsc --noEmit", - "test": "vitest run", - "test:unit": "vitest run", - "storybook": "storybook dev -p 6006 --host 0.0.0.0", - "build-storybook": "storybook build" - }, - "dependencies": { - "@fontsource-variable/geist": "^5.2.9", - "@iconify/react": "^6.0.2", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "lucide-react": "^1.21.0", - "radix-ui": "^1.6.0", - "tailwind-merge": "^3.6.0", - "tw-animate-css": "^1.4.0", - "uplot": "^1.6.32" - }, - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" - }, - "devDependencies": { - "@storybook/addon-a11y": "^10.4.6", - "@storybook/addon-vitest": "^10.4.6", - "@storybook/react-vite": "^10.4.6", - "@types/bun": "^1.3.14", - "@types/node": "^25.9.3", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", - "bun-types": "^1.3.14", - "storybook": "^10.4.6", - "typescript": "^6.0.3", - "vite": "^8.0.16", - "vitest": "^4.1.9" - } -} diff --git a/packages/ui/src/components/foundation/Badge.tsx b/packages/ui/src/components/foundation/Badge.tsx deleted file mode 100644 index 5bdee48..0000000 --- a/packages/ui/src/components/foundation/Badge.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import type { UiSeverity } from "../../types"; -import { StatusBadge } from "./StatusBadge"; - -export interface BadgeProps { - label: string; - severity?: UiSeverity; -} - -export function Badge({ label, severity = "neutral" }: BadgeProps) { - return ; -} diff --git a/packages/ui/src/components/foundation/Button.tsx b/packages/ui/src/components/foundation/Button.tsx deleted file mode 100644 index 8ae3b4e..0000000 --- a/packages/ui/src/components/foundation/Button.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type { ButtonHTMLAttributes } from "react"; -import { IconGlyph } from "./IconGlyph"; - -export interface ButtonProps - extends Omit, "type"> { - label: string; - variant?: "primary" | "secondary" | "danger" | "ghost"; - size?: "default" | "compact"; - icon?: string; - loading?: boolean; - type?: "button" | "submit" | "reset"; -} - -export function Button({ - label, - variant = "primary", - size = "default", - icon, - disabled = false, - loading = false, - type = "button", - className = "", - ...buttonProps -}: ButtonProps) { - return ( - - ); -} diff --git a/packages/ui/src/components/foundation/IconButton.tsx b/packages/ui/src/components/foundation/IconButton.tsx deleted file mode 100644 index 92fcb46..0000000 --- a/packages/ui/src/components/foundation/IconButton.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { ButtonHTMLAttributes } from "react"; -import { IconGlyph } from "./IconGlyph"; - -export interface IconButtonProps - extends Omit, "type"> { - icon: string; - label: string; - active?: boolean; - type?: "button" | "submit" | "reset"; -} - -export function IconButton({ - icon, - label, - active = false, - disabled = false, - type = "button", - className = "", - ...buttonProps -}: IconButtonProps) { - return ( - - ); -} diff --git a/packages/ui/src/components/foundation/IconGlyph.tsx b/packages/ui/src/components/foundation/IconGlyph.tsx deleted file mode 100644 index d21feb1..0000000 --- a/packages/ui/src/components/foundation/IconGlyph.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Icon } from "@iconify/react"; - -export interface IconGlyphProps { - name?: string; - label?: string; - size?: "sm" | "md" | "lg"; -} - -export function IconGlyph({ name, label, size = "md" }: IconGlyphProps) { - return ( - - {name ? : null} - - ); -} diff --git a/packages/ui/src/components/foundation/ProgressMeter.tsx b/packages/ui/src/components/foundation/ProgressMeter.tsx deleted file mode 100644 index c3f3aad..0000000 --- a/packages/ui/src/components/foundation/ProgressMeter.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { CSSProperties } from "react"; -import { clampPercent } from "../../format"; -import type { UiSeverity } from "../../types"; - -export interface ProgressMeterProps { - value?: number; - severity?: UiSeverity; - label?: string; -} - -export function ProgressMeter({ - value, - severity = "neutral", - label = "Progress", -}: ProgressMeterProps) { - const progress = value === undefined ? null : clampPercent(value); - - return ( -
- {progress !== null ? ( - - ) : null} -
- ); -} diff --git a/packages/ui/src/components/foundation/Separator.tsx b/packages/ui/src/components/foundation/Separator.tsx deleted file mode 100644 index ca1a955..0000000 --- a/packages/ui/src/components/foundation/Separator.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export interface SeparatorProps { - orientation?: "horizontal" | "vertical"; - dense?: boolean; -} - -export function Separator({ - orientation = "horizontal", - dense = false, -}: SeparatorProps) { - return ( -
- ); -} diff --git a/packages/ui/src/components/foundation/StatusBadge.tsx b/packages/ui/src/components/foundation/StatusBadge.tsx deleted file mode 100644 index d2b01b3..0000000 --- a/packages/ui/src/components/foundation/StatusBadge.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { UiSeverity } from "../../types"; - -export interface StatusBadgeProps { - label: string; - severity?: UiSeverity; -} - -export function StatusBadge({ label, severity = "neutral" }: StatusBadgeProps) { - return ( - - {label} - - ); -} diff --git a/packages/ui/src/components/foundation/ThemeToggle.tsx b/packages/ui/src/components/foundation/ThemeToggle.tsx deleted file mode 100644 index 5d83031..0000000 --- a/packages/ui/src/components/foundation/ThemeToggle.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { UiTheme } from "../../theme"; -import { getNextUiTheme } from "../../theme"; -import { IconGlyph } from "./IconGlyph"; - -export interface ThemeToggleProps { - theme: UiTheme; - onThemeChange: (theme: UiTheme) => void; -} - -export function ThemeToggle({ theme, onThemeChange }: ThemeToggleProps) { - const nextTheme = getNextUiTheme(theme); - - return ( - - ); -} diff --git a/packages/ui/src/components/foundation/index.ts b/packages/ui/src/components/foundation/index.ts deleted file mode 100644 index b82cd47..0000000 --- a/packages/ui/src/components/foundation/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from "./Badge"; -export * from "./Button"; -export * from "./IconButton"; -export * from "./IconGlyph"; -export * from "./ProgressMeter"; -export * from "./Separator"; -export * from "./StatusBadge"; -export * from "./ThemeToggle"; diff --git a/packages/ui/src/components/frames/CornerBracketFrame.tsx b/packages/ui/src/components/frames/CornerBracketFrame.tsx deleted file mode 100644 index 1dfd4f7..0000000 --- a/packages/ui/src/components/frames/CornerBracketFrame.tsx +++ /dev/null @@ -1,26 +0,0 @@ -export interface CornerBracketFrameProps { - density?: "regular" | "tight"; - size?: "sm" | "md" | "lg"; - tone?: "neutral" | "accent" | "danger"; -} - -export function CornerBracketFrame({ - density = "regular", - size = "md", - tone = "neutral", -}: CornerBracketFrameProps) { - return ( - - ); -} diff --git a/packages/ui/src/components/frames/DashboardFrame.tsx b/packages/ui/src/components/frames/DashboardFrame.tsx deleted file mode 100644 index 08029cf..0000000 --- a/packages/ui/src/components/frames/DashboardFrame.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useId } from "react"; -import type { ReactNode } from "react"; -import type { UiDashboardPreview } from "../../types"; -import { ModuleCard } from "./ModuleCard"; -import { ServicePanel } from "../operations/ServicePanel"; -import { StatusStrip } from "../operations/StatusStrip"; -import { TelemetryGrid } from "../telemetry/TelemetryGrid"; - -export interface DashboardFrameProps { - actions?: ReactNode; - dashboard: UiDashboardPreview; - titleId?: string; -} - -export function DashboardFrame({ - actions, - dashboard, - titleId, -}: DashboardFrameProps) { - const generatedTitleId = useId(); - const resolvedTitleId = titleId || `${generatedTitleId}-title`; - - return ( -
-
-
-
- {dashboard.eyebrow ?

{dashboard.eyebrow}

: null} -

{dashboard.title}

- {dashboard.subtitle ? {dashboard.subtitle} : null} -
- {actions ? ( -
{actions}
- ) : null} -
- {dashboard.modules.length ? ( -
- {dashboard.modules.map((module) => ( - - ))} -
- ) : null} -
- - - -
- {dashboard.serviceGroups.map((group) => ( - - ))} -
- - -
- ); -} diff --git a/packages/ui/src/components/frames/DashboardHeader.tsx b/packages/ui/src/components/frames/DashboardHeader.tsx deleted file mode 100644 index 3f418fa..0000000 --- a/packages/ui/src/components/frames/DashboardHeader.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useId } from "react"; -import type { UiModuleBlock } from "../../types"; -import { ModuleCard } from "./ModuleCard"; - -export interface DashboardHeaderProps { - title: string; - subtitle?: string; - eyebrow?: string; - module?: UiModuleBlock; -} - -export function DashboardHeader({ - title, - subtitle, - eyebrow, - module, -}: DashboardHeaderProps) { - const titleId = useId(); - - return ( -
-
- {eyebrow ?

{eyebrow}

: null} -

{title}

- {subtitle ? {subtitle} : null} -
- {module ? : null} -
- ); -} diff --git a/packages/ui/src/components/frames/DiagonalStripeField.tsx b/packages/ui/src/components/frames/DiagonalStripeField.tsx deleted file mode 100644 index b4c60f8..0000000 --- a/packages/ui/src/components/frames/DiagonalStripeField.tsx +++ /dev/null @@ -1,24 +0,0 @@ -export interface DiagonalStripeFieldProps { - density?: "open" | "regular" | "tight"; - direction?: "forward" | "backward"; - size?: "sm" | "md" | "lg"; - tone?: "neutral" | "accent" | "warning" | "danger"; -} - -export function DiagonalStripeField({ - density = "regular", - direction = "forward", - size = "md", - tone = "accent", -}: DiagonalStripeFieldProps) { - return ( -