# Design System Packaging 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. > > **IMPORTANT: Use an isolated git worktree** — another agent may be working on the main tree. Create a worktree before starting any task. The worktree must be created from the current working state (not a clean `main`) because several provider/util files are uncommitted. **Goal:** Package the Cameleer3 design system as `@cameleer/design-system` and publish it to Gitea's npm registry via CI/CD. **Architecture:** Vite library mode builds the design system into an ESM bundle + CSS + TypeScript declarations. A Gitea Actions workflow publishes snapshot versions on every push to main, and stable versions on `v*` tags. Consuming apps install from Gitea's npm registry. **Tech Stack:** Vite (library mode), vite-plugin-dts, TypeScript, CSS Modules, Gitea Actions **Spec:** `docs/superpowers/specs/2026-03-18-design-system-packaging-design.md` --- ## Prerequisites Before starting, ensure these currently-untracked files are committed (they are referenced by the library entry point): - `src/design-system/providers/CommandPaletteProvider.tsx` - `src/design-system/providers/GlobalFilterProvider.tsx` - `src/design-system/utils/timePresets.ts` The git remote should be added **before** creating a worktree, since remotes are repo-level config shared across all worktrees. --- ## File Map | Action | File | Responsibility | |--------|------|----------------| | Create | `src/design-system/index.ts` | Library entry point — re-exports all components, imports global CSS | | Create | `vite.lib.config.ts` | Vite library build config (separate from app build) | | Create | `.gitea/workflows/publish.yml` | CI/CD: test, build, publish on push to main / tag | | Modify | `.gitignore` | Add `dist/` to prevent build artifacts from being committed | | Modify | `package.json` | Rename, add exports/peers/publishConfig/files/repository | | Modify | `tsconfig.node.json` | Include `vite.lib.config.ts` | | Modify | `CLAUDE.md` | Add consumer usage docs for AI agents | | Modify | `COMPONENT_GUIDE.md` | Add package import paths for consumers | --- ### Task 1: Add git remote and commit untracked files **Files:** - None created (git operations + staging existing untracked files) - [ ] **Step 1: Add the origin remote** ```bash git remote add origin https://gitea.siegeln.net/cameleer/design-system.git ``` If `origin` already exists, use `git remote set-url origin https://gitea.siegeln.net/cameleer/design-system.git` instead. - [ ] **Step 2: Verify remote** ```bash git remote -v ``` Expected: `origin https://gitea.siegeln.net/cameleer/design-system.git (fetch)` and `(push)` - [ ] **Step 3: Commit untracked provider/util files** These files are required by the library entry point (Task 3) but are currently untracked: ```bash git add src/design-system/providers/CommandPaletteProvider.tsx src/design-system/providers/GlobalFilterProvider.tsx src/design-system/utils/timePresets.ts git commit -m "feat: add CommandPaletteProvider, GlobalFilterProvider, and timePresets" ``` - [ ] **Step 4: Commit any other pending changes** Check `git status` — there may be other modified files from a prior agent's work. Stage and commit anything that belongs on main before proceeding: ```bash git status ``` If there are modified files, commit them with an appropriate message before continuing. --- ### Task 2: Install vite-plugin-dts and add dist/ to .gitignore **Files:** - Modify: `package.json` (devDependencies) - Modify: `.gitignore` - [ ] **Step 1: Install the plugin** ```bash npm install -D vite-plugin-dts ``` Note: `vite-plugin-dts` with `rollupTypes: true` requires `@microsoft/api-extractor` as a peer dependency. If the install warns about this, also install it: ```bash npm install -D @microsoft/api-extractor ``` - [ ] **Step 2: Verify it's in devDependencies** ```bash node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).devDependencies['vite-plugin-dts'])" ``` Expected: a version string like `^4.x.x` - [ ] **Step 3: Add `dist/` to `.gitignore`** Append to `.gitignore`: ``` dist/ ``` This prevents build artifacts from being accidentally committed. - [ ] **Step 4: Commit** ```bash git add package.json package-lock.json .gitignore git commit -m "chore: add vite-plugin-dts and ignore dist/" ``` --- ### Task 3: Create library entry point **Files:** - Create: `src/design-system/index.ts` - [ ] **Step 1: Create `src/design-system/index.ts`** ```ts import './tokens.css' import './reset.css' export * from './primitives' export * from './composites' export * from './layout' export * from './providers/ThemeProvider' export * from './providers/CommandPaletteProvider' export * from './providers/GlobalFilterProvider' export * from './utils/hashColor' export * from './utils/timePresets' ``` This file imports `tokens.css` and `reset.css` at the top so Vite includes them in the bundled `style.css`. Without these imports, all `var(--*)` tokens would resolve to nothing in consuming apps. - [ ] **Step 2: Verify TypeScript is happy** ```bash npx tsc -b --noEmit ``` Expected: no errors - [ ] **Step 3: Commit** ```bash git add src/design-system/index.ts git commit -m "feat: add library entry point for design system package" ``` --- ### Task 4: Create Vite library build config **Files:** - Create: `vite.lib.config.ts` - Modify: `tsconfig.node.json` - [ ] **Step 1: Create `vite.lib.config.ts`** Note: `__dirname` works in Vite config files despite this being an ESM project — Vite transpiles config files before executing them. ```ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import dts from 'vite-plugin-dts' import { resolve } from 'path' export default defineConfig({ plugins: [ react(), dts({ include: ['src/design-system'], outDir: 'dist', rollupTypes: true, }), ], css: { modules: { localsConvention: 'camelCase', generateScopedName: 'cameleer_[name]_[local]_[hash:5]', }, }, build: { lib: { entry: resolve(__dirname, 'src/design-system/index.ts'), formats: ['es'], fileName: () => 'index.es.js', }, rollupOptions: { external: ['react', 'react-dom', 'react-router-dom', 'react/jsx-runtime'], }, cssFileName: 'style', }, }) ``` Key choices: - `rollupTypes: true` consolidates all `.d.ts` into a single `dist/index.d.ts` - `generateScopedName: 'cameleer_[name]_[local]_[hash:5]'` makes class names debuggable in consumer devtools - `react/jsx-runtime` is externalized (peer dep of React, not bundled) - `cssFileName: 'style'` ensures output is `dist/style.css` - `fileName: () => 'index.es.js'` forces a deterministic output filename — Vite 6 defaults to `.mjs` for ES format which would mismatch `package.json` exports - [ ] **Step 2: Update `tsconfig.node.json` to include the new config file** Change the `include` array from: ```json "include": ["vite.config.ts"] ``` to: ```json "include": ["vite.config.ts", "vite.lib.config.ts"] ``` - [ ] **Step 3: Test the library build** The `build:lib` script isn't in `package.json` yet (added in Task 5). Run directly: ```bash npx vite build --config vite.lib.config.ts ``` Expected: `dist/` directory created with `index.es.js`, `style.css`, and `index.d.ts` - [ ] **Step 4: Verify the output** ```bash ls dist/ ``` Expected: `index.es.js`, `style.css`, `index.d.ts` Check that `style.css` contains token variables: ```bash grep "bg-body" dist/style.css ``` Expected: matches found (proves tokens.css was included) Check that type declarations contain exported components: ```bash grep "Button" dist/index.d.ts ``` Expected: matches found (proves types were generated) If `index.d.ts` is missing or empty, `rollupTypes` may have failed silently. In that case, install `@microsoft/api-extractor` and rebuild, or set `rollupTypes: false` (which produces individual `.d.ts` files — less clean but functional). - [ ] **Step 5: Clean up and commit** ```bash rm -rf dist git add vite.lib.config.ts tsconfig.node.json git commit -m "feat: add Vite library build config with dts generation" ``` --- ### Task 5: Update package.json for library publishing **Files:** - Modify: `package.json` - [ ] **Step 1: Update package.json** Apply these changes to the existing `package.json`: 1. Change `"name"` from `"cameleer3"` to `"@cameleer/design-system"` 2. Change `"version"` from `"0.0.0"` to `"0.1.0"` 3. Remove `"private": true` 4. Add `"main": "./dist/index.es.js"` 5. Add `"module": "./dist/index.es.js"` 6. Add `"types": "./dist/index.d.ts"` 7. Add `"exports"` block (note: `types` must come first): ```json "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.es.js" }, "./style.css": "./dist/style.css" } ``` 8. Add `"files": ["dist"]` 9. Add `"sideEffects": ["*.css"]` 10. Add `"publishConfig"`: ```json "publishConfig": { "registry": "https://gitea.siegeln.net/api/packages/cameleer/npm/" } ``` 11. Add `"repository"`: ```json "repository": { "type": "git", "url": "https://gitea.siegeln.net/cameleer/design-system.git" } ``` 12. Add `"peerDependencies"`: ```json "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.0.0" } ``` 13. Add to `"scripts"`: ```json "build:lib": "vite build --config vite.lib.config.ts" ``` The final `package.json` should look like: ```json { "name": "@cameleer/design-system", "version": "0.1.0", "type": "module", "main": "./dist/index.es.js", "module": "./dist/index.es.js", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.es.js" }, "./style.css": "./dist/style.css" }, "files": ["dist"], "sideEffects": ["*.css"], "publishConfig": { "registry": "https://gitea.siegeln.net/api/packages/cameleer/npm/" }, "repository": { "type": "git", "url": "https://gitea.siegeln.net/cameleer/design-system.git" }, "scripts": { "dev": "vite", "build": "tsc -b && vite build", "build:lib": "vite build --config vite.lib.config.ts", "lint": "eslint .", "preview": "vite preview", "test": "vitest" }, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.0.0" }, "dependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.0.0" }, "devDependencies": { "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.3.0", "happy-dom": "^20.8.4", "typescript": "^5.6.0", "vite": "^6.0.0", "vite-plugin-dts": "", "vitest": "^3.0.0" } } ``` - [ ] **Step 2: Verify the full library build works end-to-end** ```bash npm run build:lib ``` Expected: succeeds, `dist/` contains `index.es.js`, `style.css`, `index.d.ts` - [ ] **Step 3: Clean up and commit** ```bash rm -rf dist git add package.json git commit -m "feat: configure package.json for @cameleer/design-system publishing" ``` --- ### Task 6: Create Gitea Actions workflow **Files:** - Create: `.gitea/workflows/publish.yml` - [ ] **Step 1: Create `.gitea/workflows/publish.yml`** ```yaml name: Build & Publish on: push: branches: [main] tags: ['v*'] jobs: publish: runs-on: linux-arm64 container: image: node:22-bookworm-slim steps: - uses: actions/checkout@v4 - name: Install dependencies run: npm ci - name: Run tests run: npx vitest run - name: Build library run: npm run build:lib - name: Publish package run: | if [[ "$GITHUB_REF" == refs/tags/v* ]]; then VERSION="${GITHUB_REF_NAME#v}" npm version "$VERSION" --no-git-tag-version TAG="latest" else SHORT_SHA=$(echo "$GITHUB_SHA" | head -c 7) DATE=$(date +%Y%m%d) npm version "0.0.0-snapshot.${DATE}.${SHORT_SHA}" --no-git-tag-version TAG="dev" fi echo '@cameleer:registry=https://gitea.siegeln.net/api/packages/cameleer/npm/' > .npmrc echo '//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${{ secrets.REGISTRY_TOKEN }}' >> .npmrc npm publish --tag "$TAG" ``` Note: The `.npmrc` is written with `echo` commands (not a heredoc) to avoid YAML indentation being included in the file content, which would break npm's parsing. - [ ] **Step 2: Commit** ```bash mkdir -p .gitea/workflows git add .gitea/workflows/publish.yml git commit -m "ci: add Gitea Actions workflow for npm publishing" ``` --- ### Task 7: Update documentation for consumers **Files:** - Modify: `CLAUDE.md` - Modify: `COMPONENT_GUIDE.md` - [ ] **Step 1: Add consumer section to `CLAUDE.md`** Add the following section at the end of `CLAUDE.md`: ```markdown ## Using This Design System in Other Apps This design system is published as `@cameleer/design-system` to the Gitea npm registry. ### Registry: `https://gitea.siegeln.net/api/packages/cameleer/npm/` ### Setup in a consuming app 1. Add `.npmrc` to the project root: ``` @cameleer:registry=https://gitea.siegeln.net/api/packages/cameleer/npm/ //gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${GITEA_TOKEN} ``` Note: CI pipelines for consuming apps also need this `.npmrc` and a `GITEA_TOKEN` secret to fetch the package during `npm ci`. 2. Install: ```bash # Snapshot builds (during development) npm install @cameleer/design-system@dev # Stable releases npm install @cameleer/design-system ``` 3. Add fonts to `index.html` (required — the package does not bundle fonts): ```html ``` Without these, `var(--font-body)` and `var(--font-mono)` fall back to `system-ui` / `monospace`. 4. Import styles once at app root, then use components: ```tsx import '@cameleer/design-system/style.css' import { Button, AppShell, ThemeProvider } from '@cameleer/design-system' ``` ### Import Paths (Consumer) ```tsx // All components from single entry import { Button, Input, Modal, DataTable, AppShell } from '@cameleer/design-system' // Types import type { Column, DataTableProps, SearchResult } from '@cameleer/design-system' // Providers import { ThemeProvider, useTheme } from '@cameleer/design-system' import { CommandPaletteProvider, useCommandPalette } from '@cameleer/design-system' import { GlobalFilterProvider, useGlobalFilters } from '@cameleer/design-system' // Utils import { hashColor } from '@cameleer/design-system' // Styles (once, at app root) import '@cameleer/design-system/style.css' ``` ``` - [ ] **Step 2: Update the `## Import Paths` section in `COMPONENT_GUIDE.md`** Find the `## Import Paths` section heading and replace everything from that heading down to the next `##` heading (or end of file) with: ```markdown ## Import Paths ### Within this repo (design system development) ```tsx import { Button, Input, Badge } from './design-system/primitives' import { DataTable, Modal, Toast } from './design-system/composites' import type { Column, SearchResult, FeedEvent } from './design-system/composites' import { AppShell } from './design-system/layout/AppShell' import { ThemeProvider, useTheme } from './design-system/providers/ThemeProvider' ``` ### From consuming apps (via npm package) ```tsx import '@cameleer/design-system/style.css' // once at app root import { Button, Input, Modal, DataTable, AppShell, ThemeProvider } from '@cameleer/design-system' import type { Column, DataTableProps, SearchResult } from '@cameleer/design-system' ``` See `CLAUDE.md` "Using This Design System in Other Apps" for full setup instructions. ``` - [ ] **Step 3: Commit** ```bash git add CLAUDE.md COMPONENT_GUIDE.md git commit -m "docs: add consumer usage guide for @cameleer/design-system package" ``` --- ### Task 8: Push to Gitea and verify CI **Files:** - None (git operations only) - [ ] **Step 1: Push all commits to Gitea** ```bash git push -u origin main ``` - [ ] **Step 2: Check CI status** Use the Gitea MCP server or visit `https://gitea.siegeln.net/cameleer/design-system/actions` to monitor the workflow run. The workflow should: 1. Install deps 2. Run tests 3. Build the library 4. Publish a snapshot version with the `dev` tag - [ ] **Step 3: Verify the package is published** Check the Gitea package registry at `https://gitea.siegeln.net/cameleer/-/packages`. The `@cameleer/design-system` package should appear with a `0.0.0-snapshot.*` version tagged as `dev`. If the workflow fails, check the job logs via the Gitea MCP server's `actions_run_read` tool for diagnostics.