Files
design-system/docs/superpowers/specs/2026-03-18-design-system-packaging-design.md
hsiegeln 727a5de9dc docs: fix spec issues from review
- Add tokens.css/reset.css imports to entry point (critical: CSS tokens would be missing)
- Add font loading docs for consumers (DM Sans, JetBrains Mono)
- Add publishConfig for Gitea registry (npm publish would target npmjs.org)
- Fix exports map: types condition first
- Add scoped registry line to CI .npmrc
- Note vite-plugin-dts as required devDependency
- Add repository field to package.json spec
- Note tsconfig.node.json update needed
- CSS Module scoping strategy for debuggable class names
- Note CI auth requirements for consuming apps

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 20:12:28 +01:00

7.5 KiB

Design System Packaging — Design Spec

Date: 2026-03-18 Status: Approved Package: @cameleer/design-system Registry: Gitea npm registry at gitea.siegeln.net Repository: https://gitea.siegeln.net/cameleer/design-system

Goal

Package the Cameleer3 design system as a reusable npm library so other React applications in the Cameleer ecosystem can consume it via npm install. Publishing is automated via Gitea Actions.

Decisions

Decision Choice Rationale
Registry Gitea built-in npm registry Already have Gitea infrastructure
Package scope @cameleer/design-system Matches the org
Export style Single package, flat exports Simple DX, tree-shaking handles unused code
What's included Everything (primitives, composites, layout, providers, utils, tokens) All consuming apps are Cameleer apps
Build tool Vite library mode Already using Vite, CSS Modules first-class
Output format ESM only All consumers are Vite/ESM
Versioning Tag-based releases + snapshot on every main push Snapshots for dev, tags for milestones
Runner arch ARM64 Gitea runner is ARM64
Auth secret REGISTRY_TOKEN (org-level) Existing all-access token

1. Library Entry Point

New file src/design-system/index.ts — the single public API. It must import global CSS at the top so that tokens.css and reset.css are included in the bundled dist/style.css:

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'

Without the CSS imports, all var(--*) tokens used in component CSS Modules would resolve to nothing in consuming apps.

Consumers import the bundled CSS once at their app root:

import '@cameleer/design-system/style.css'

2. Vite Library Build Config

A separate vite.lib.config.ts to keep library and app builds independent:

  • Entry: src/design-system/index.ts
  • Output: dist/index.es.js (ESM)
  • CSS: Extracted to dist/style.css
  • CSS Modules scoping: cameleer_[name]_[local]_[hash:5] — debuggable in consumer devtools, unique enough to avoid collisions
  • Externals: react, react-dom, react-router-dom (peer deps, not bundled)
  • Types: vite-plugin-dts generates dist/index.d.ts with full TypeScript declarations
  • Build script: "build:lib": "vite build --config vite.lib.config.ts"
  • New devDependency: vite-plugin-dts must be installed

tsconfig.node.json must be updated to include vite.lib.config.ts.

Output structure:

dist/
  index.es.js      # ESM bundle
  style.css         # All CSS (tokens + reset + component modules)
  index.d.ts        # TypeScript declarations

3. Package Configuration

Updates to package.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"
  },
  "peerDependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-router-dom": "^7.0.0"
  }
}
  • "private": true is removed
  • "types" comes first in the exports conditions (TypeScript resolution requirement)
  • publishConfig ensures npm publish targets the Gitea registry, not npmjs.org
  • Existing scripts, dependencies, and devDependencies remain for the app build
  • peerDependencies tells consumers what to provide

4. Gitea Actions CI/CD Pipeline

Workflow at .gitea/workflows/publish.yml:

Triggers:

  • Push to main → publish snapshot (0.0.0-snapshot.<YYYYMMDD>.<short-sha>) with dev dist-tag
  • Push tag v* → publish stable release (e.g., 1.0.0) with latest dist-tag

Steps:

  1. Checkout at ref
  2. npm ci (install deps)
  3. npx vitest run (gate: don't publish broken code)
  4. npm run build:lib (build the library)
  5. Determine version from tag or generate snapshot version
  6. Configure .npmrc with scoped registry + auth token
  7. npm publish --tag <dev|latest>

Runner: ARM64 with node:22-bookworm-slim container image.

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
      - run: npm ci
      - run: npx vitest run
      - run: npm run build:lib
      - 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
          cat > .npmrc << 'NPMRC'
          @cameleer:registry=https://gitea.siegeln.net/api/packages/cameleer/npm/
          //gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${{ secrets.REGISTRY_TOKEN }}
          NPMRC
          npm publish --tag "$TAG"

5. Consumer Setup

In any consuming app (e.g., cameleer3-server/ui):

1. Add .npmrc to project root:

@cameleer:registry=https://gitea.siegeln.net/api/packages/cameleer/npm/
//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${GITEA_TOKEN}

Note: The consuming app's CI pipeline also needs this .npmrc and a GITEA_TOKEN secret to fetch the package during npm ci.

2. Install:

# During development (snapshot builds)
npm install @cameleer/design-system@dev

# For stable releases (later)
npm install @cameleer/design-system

3. Add fonts to index.html:

The design system uses DM Sans and JetBrains Mono via Google Fonts. These must be loaded by the consuming app since font <link> tags are not part of the library output:

<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) will fall back to system-ui / monospace.

4. Use:

// Import styles once at app root
import '@cameleer/design-system/style.css'

// Import components
import { Button, Input, Modal, AppShell, ThemeProvider } from '@cameleer/design-system'

6. Documentation Updates

Update CLAUDE.md and COMPONENT_GUIDE.md in this repo with:

  • The package name and registry URL
  • How consuming apps should configure .npmrc (including CI)
  • Font loading requirement (Google Fonts link)
  • Import patterns for consumers (@cameleer/design-system instead of relative paths)
  • Note that style.css must be imported once at the app root

This ensures other AI agents working on consuming Cameleer apps understand how to use the design system.