2026-03-18 20:07:28 +01:00
# Design System Packaging — Design Spec
**Date:** 2026-03-18
**Status:** Approved
**Package:** `@cameleer/design-system`
**Registry:** Gitea npm registry at `gitea.siegeln.net`
2026-03-18 20:12:28 +01:00
**Repository:** `https://gitea.siegeln.net/cameleer/design-system`
2026-03-18 20:07:28 +01:00
## 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
2026-03-18 20:12:28 +01:00
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` :
2026-03-18 20:07:28 +01:00
```ts
2026-03-18 20:12:28 +01:00
import './tokens.css'
import './reset.css'
2026-03-18 20:07:28 +01:00
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'
```
2026-03-18 20:12:28 +01:00
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:
2026-03-18 20:07:28 +01:00
```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`
2026-03-18 20:12:28 +01:00
- **CSS Modules scoping:** `cameleer_[name]_[local]_[hash:5]` — debuggable in consumer devtools, unique enough to avoid collisions
2026-03-18 20:07:28 +01:00
- **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"`
2026-03-18 20:12:28 +01:00
- **New devDependency:** `vite-plugin-dts` must be installed
`tsconfig.node.json` must be updated to include `vite.lib.config.ts` .
2026-03-18 20:07:28 +01:00
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": {
".": {
2026-03-18 20:12:28 +01:00
"types": "./dist/index.d.ts",
"import": "./dist/index.es.js"
2026-03-18 20:07:28 +01:00
},
"./style.css": "./dist/style.css"
},
"files": ["dist"],
"sideEffects": ["*.css"],
2026-03-18 20:12:28 +01:00
"publishConfig": {
"registry": "https://gitea.siegeln.net/api/packages/cameleer/npm/"
},
"repository": {
"type": "git",
"url": "https://gitea.siegeln.net/cameleer/design-system.git"
},
2026-03-18 20:07:28 +01:00
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.0.0"
}
}
```
- `"private": true` is removed
2026-03-18 20:12:28 +01:00
- `"types"` comes first in the exports conditions (TypeScript resolution requirement)
- `publishConfig` ensures `npm publish` targets the Gitea registry, not npmjs.org
2026-03-18 20:07:28 +01:00
- 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
2026-03-18 20:12:28 +01:00
6. Configure `.npmrc` with scoped registry + auth token
2026-03-18 20:07:28 +01:00
7. `npm publish --tag <dev|latest>`
**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
2026-03-18 20:12:28 +01:00
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
2026-03-18 20:07:28 +01:00
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}
```
2026-03-18 20:12:28 +01:00
Note: The consuming app's CI pipeline also needs this `.npmrc` and a `GITEA_TOKEN` secret to fetch the package during `npm ci` .
2026-03-18 20:07:28 +01:00
**2. Install:**
```bash
# During development (snapshot builds)
npm install @cameleer/design -system@dev
# For stable releases (later)
npm install @cameleer/design -system
```
2026-03-18 20:12:28 +01:00
**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:
```html
<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:**
2026-03-18 20:07:28 +01:00
```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
2026-03-18 20:12:28 +01:00
- How consuming apps should configure `.npmrc` (including CI)
- Font loading requirement (Google Fonts link)
2026-03-18 20:07:28 +01:00
- 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.