From 2c427a31a1696eea4f390f85db833c403802e00a Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:19:16 +0100 Subject: [PATCH] docs: add implementation plan for design system packaging 8-task plan covering: git remote, vite-plugin-dts, library entry point, Vite lib config, package.json, Gitea Actions CI/CD, consumer docs, push. Fixes from review: deterministic output filename, .npmrc echo instead of heredoc, dist/ in .gitignore, prerequisite for untracked files, dts verification, worktree guidance. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-03-18-design-system-packaging.md | 612 ++++++++++++++++++ 1 file changed, 612 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-18-design-system-packaging.md diff --git a/docs/superpowers/plans/2026-03-18-design-system-packaging.md b/docs/superpowers/plans/2026-03-18-design-system-packaging.md new file mode 100644 index 0000000..d852fe3 --- /dev/null +++ b/docs/superpowers/plans/2026-03-18-design-system-packaging.md @@ -0,0 +1,612 @@ +# 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.