Files
design-system/docs/superpowers/plans/2026-03-18-design-system-packaging.md
hsiegeln 2c427a31a1 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) <noreply@anthropic.com>
2026-03-18 20:19:16 +01:00

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.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

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: 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:

"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:

  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):
    "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":
    "publishConfig": {
      "registry": "https://gitea.siegeln.net/api/packages/cameleer/npm/"
    }
    
  11. Add "repository":
    "repository": {
      "type": "git",
      "url": "https://gitea.siegeln.net/cameleer/design-system.git"
    }
    
  12. Add "peerDependencies":
    "peerDependencies": {
      "react": "^19.0.0",
      "react-dom": "^19.0.0",
      "react-router-dom": "^7.0.0"
    }
    
  13. 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
  1. 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.

  1. 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:

  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.