Files
design-system/docs/superpowers/plans/2026-03-18-design-system-packaging.md

613 lines
17 KiB
Markdown
Raw Permalink Normal View History

# 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": "<version installed in Task 2>",
"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
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
```
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.