# 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`: ```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' ``` 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: ```ts 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`: ```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..`) 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 ` **Runner:** ARM64 with `node:22-bookworm-slim` container image. ```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 - 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:** ```bash # 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 `` tags are not part of the library output: ```html ``` Without these, `var(--font-body)` and `var(--font-mono)` will fall back to `system-ui` / `monospace`. **4. Use:** ```tsx // 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.