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) <noreply@anthropic.com>
17 KiB
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.tsxsrc/design-system/providers/GlobalFilterProvider.tsxsrc/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
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
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:
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:
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
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:
npm install -D @microsoft/api-extractor
- Step 2: Verify it's in devDependencies
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
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
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
npx tsc -b --noEmit
Expected: no errors
- Step 3: Commit
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.
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: trueconsolidates all.d.tsinto a singledist/index.d.ts -
generateScopedName: 'cameleer_[name]_[local]_[hash:5]'makes class names debuggable in consumer devtools -
react/jsx-runtimeis externalized (peer dep of React, not bundled) -
cssFileName: 'style'ensures output isdist/style.css -
fileName: () => 'index.es.js'forces a deterministic output filename — Vite 6 defaults to.mjsfor ES format which would mismatchpackage.jsonexports -
Step 2: Update
tsconfig.node.jsonto include the new config file
Change the include array from:
"include": ["vite.config.ts"]
to:
"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:
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
ls dist/
Expected: index.es.js, style.css, index.d.ts
Check that style.css contains token variables:
grep "bg-body" dist/style.css
Expected: matches found (proves tokens.css was included)
Check that type declarations contain exported components:
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
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:
- Change
"name"from"cameleer3"to"@cameleer/design-system" - Change
"version"from"0.0.0"to"0.1.0" - Remove
"private": true - Add
"main": "./dist/index.es.js" - Add
"module": "./dist/index.es.js" - Add
"types": "./dist/index.d.ts" - Add
"exports"block (note:typesmust come first):"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.es.js" }, "./style.css": "./dist/style.css" } - Add
"files": ["dist"] - Add
"sideEffects": ["*.css"] - Add
"publishConfig":"publishConfig": { "registry": "https://gitea.siegeln.net/api/packages/cameleer/npm/" } - Add
"repository":"repository": { "type": "git", "url": "https://gitea.siegeln.net/cameleer/design-system.git" } - Add
"peerDependencies":"peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.0.0" } - Add to
"scripts":"build:lib": "vite build --config vite.lib.config.ts"
The final package.json should look like:
{
"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
npm run build:lib
Expected: succeeds, dist/ contains index.es.js, style.css, index.d.ts
- Step 3: Clean up and commit
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
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
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:
## 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
- Add fonts to
index.html(required — the package does not bundle fonts):
<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.
- Import styles once at app root, then use components:
import '@cameleer/design-system/style.css'
import { Button, AppShell, ThemeProvider } from '@cameleer/design-system'
Import Paths (Consumer)
// 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)
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
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:
- Install deps
- Run tests
- Build the library
- Publish a snapshot version with the
devtag
- 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.