- 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>
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-dtsgeneratesdist/index.d.tswith full TypeScript declarations - Build script:
"build:lib": "vite build --config vite.lib.config.ts" - New devDependency:
vite-plugin-dtsmust 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": trueis removed"types"comes first in the exports conditions (TypeScript resolution requirement)publishConfigensuresnpm publishtargets the Gitea registry, not npmjs.org- Existing
scripts,dependencies, anddevDependenciesremain for the app build peerDependenciestells 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>) withdevdist-tag - Push tag
v*→ publish stable release (e.g.,1.0.0) withlatestdist-tag
Steps:
- Checkout at ref
npm ci(install deps)npx vitest run(gate: don't publish broken code)npm run build:lib(build the library)- Determine version from tag or generate snapshot version
- Configure
.npmrcwith scoped registry + auth token 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-systeminstead of relative paths) - Note that
style.cssmust be imported once at the app root
This ensures other AI agents working on consuming Cameleer apps understand how to use the design system.