guardian-framework 0.1.24 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/exports.js +1 -1
- package/package.json +1 -1
- package/templates/pi/preflight_report.json +1 -1
- package/templates/pi/skills/agents/design-system-codegen.md +41 -0
- package/templates/pi/skills/design-system-enterprise-codegen.md +241 -237
- package/templates/pi/skills/nextjs-enterprise-codegen.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -1335,7 +1335,7 @@ __export(exports_package, {
|
|
|
1335
1335
|
bin: () => bin,
|
|
1336
1336
|
author: () => author
|
|
1337
1337
|
});
|
|
1338
|
-
var name = "guardian-framework", version = "0.1.
|
|
1338
|
+
var name = "guardian-framework", version = "0.1.25", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
|
|
1339
1339
|
var init_package = __esm(() => {
|
|
1340
1340
|
exports = {
|
|
1341
1341
|
".": {
|
package/dist/exports.js
CHANGED
|
@@ -995,7 +995,7 @@ __export(exports_package, {
|
|
|
995
995
|
bin: () => bin,
|
|
996
996
|
author: () => author
|
|
997
997
|
});
|
|
998
|
-
var name = "guardian-framework", version = "0.1.
|
|
998
|
+
var name = "guardian-framework", version = "0.1.25", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
|
|
999
999
|
var init_package = __esm(() => {
|
|
1000
1000
|
exports = {
|
|
1001
1001
|
".": {
|
package/package.json
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design-system-codegen
|
|
3
|
+
description: Minimal skill for design system architecture with DDD + adapter pattern. References full patterns on demand — never loads inline. Use when building design system components, tokens, or CSS integration.
|
|
4
|
+
model: inherit
|
|
5
|
+
tools: [Read, Grep]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Design System Code Generation — DDD + Adapter Pattern
|
|
9
|
+
|
|
10
|
+
> Full reference: `.pi/skills/design-system-enterprise-codegen.md`
|
|
11
|
+
|
|
12
|
+
## Quick Reference
|
|
13
|
+
|
|
14
|
+
| When you need... | Read this section |
|
|
15
|
+
|-----------------|-------------------|
|
|
16
|
+
| Structure overview | Section 1 — DDD module layout, framework agnostic |
|
|
17
|
+
| Design tokens | Section 2 — ColorToken, SpacingToken, TypeScale as domain values |
|
|
18
|
+
| Variant engine | Section 3 — defineVariants(), type-safe component props |
|
|
19
|
+
| CSS adapter (Tailwind) | Section 4 — Tailwind class generation from tokens |
|
|
20
|
+
| CSS adapter (CSS Modules) | Section 4 — CSS variable generation |
|
|
21
|
+
| CSS adapter (MUI/Material) | Section 4 — MUI theme mapping |
|
|
22
|
+
| Component pattern | Section 5 — Button component using application layer |
|
|
23
|
+
| Theming (dark mode) | Section 6 — ThemeService, data-theme attribute |
|
|
24
|
+
| Testing | Section 7 — token tests, variant engine tests |
|
|
25
|
+
| Choosing your stack | Section 8 — Tailwind vs CSS Modules vs MUI by use case |
|
|
26
|
+
|
|
27
|
+
## DDD Layer Contract for Design Systems
|
|
28
|
+
|
|
29
|
+
| Layer | Purpose | Example | CSS framework aware? |
|
|
30
|
+
|-------|---------|---------|---------------------|
|
|
31
|
+
| `domain/tokens/` | Typed design tokens | `ColorToken.PRIMARY` | No |
|
|
32
|
+
| `application/` | Variant resolution, theming | `defineVariants()` | No |
|
|
33
|
+
| `infrastructure/` | CSS adapter | `tailwind-adapter.ts` | **Yes** — this is the only layer |
|
|
34
|
+
| `interfaces/` | Rendered components | `button.tsx` | No (uses application layer) |
|
|
35
|
+
|
|
36
|
+
## Rules
|
|
37
|
+
- NEVER read full reference — read specific sections
|
|
38
|
+
- Design tokens are pure TypeScript — zero CSS, zero framework imports
|
|
39
|
+
- Components import from `application/`, never from `infrastructure/` directly
|
|
40
|
+
- The adapter is the **only file** that changes when you switch CSS frameworks
|
|
41
|
+
- Pick one adapter per project — never mix Tailwind and MUI in the same component
|
|
@@ -1,94 +1,116 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: design-system-enterprise-codegen
|
|
3
|
-
description: Full reference for design system architecture with DDD +
|
|
3
|
+
description: Full reference for design system architecture with DDD + adapter pattern. Covers design tokens, CSS framework integration (Tailwind/MUI/Material/CSS Modules), component API contracts, theming, accessibility, and testing. Framework-agnostic — use with any component library.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Design System & CSS Architecture — DDD
|
|
6
|
+
# Design System & CSS Architecture — DDD with Adapter Pattern
|
|
7
7
|
|
|
8
8
|
> Canonical skill for building enterprise design systems using DDD principles.
|
|
9
|
-
> Framework-agnostic — works with Next.js, Angular, Vue, or any component framework.
|
|
9
|
+
> **Framework-agnostic** — works with Next.js, Angular, Vue, or any component framework.
|
|
10
|
+
> **CSS-agnostic** — works with Tailwind, MUI, Material, CSS Modules, or any styling approach.
|
|
11
|
+
>
|
|
10
12
|
> Use alongside `.pi/skills/agents/nextjs-codegen.md` or `.pi/skills/agents/angular-codegen.md`.
|
|
11
13
|
|
|
12
14
|
---
|
|
13
15
|
|
|
14
|
-
##
|
|
16
|
+
## The Core Idea: Domain → Infrastructure Adapter
|
|
17
|
+
|
|
18
|
+
A design system shouldn't be coupled to a specific CSS framework. The DDD layer structure enforces this:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
domain/tokens/ ← Pure TypeScript: color values, spacing, typography
|
|
22
|
+
│ (zero CSS imports, zero framework imports)
|
|
23
|
+
▼
|
|
24
|
+
application/ ← Logic: variant resolution, theme switching
|
|
25
|
+
│ (pure functions, no rendering)
|
|
26
|
+
▼
|
|
27
|
+
infrastructure/ ← CSS ADAPTER: translates tokens into Tailwind/MUI/CSS Modules
|
|
28
|
+
│ (THIS is the only layer that knows about your CSS framework)
|
|
29
|
+
▼
|
|
30
|
+
interfaces/button/ ← Components: use the adapter, render the UI
|
|
31
|
+
```
|
|
15
32
|
|
|
16
|
-
|
|
33
|
+
| If you use... | Infrastructure adapter does | Components stay the same? |
|
|
34
|
+
|---------------|---------------------------|---------------------------|
|
|
35
|
+
| **Tailwind** | Generates `bg-primary text-white` class strings from tokens | ✅ Yes |
|
|
36
|
+
| **CSS Modules** | Generates `.module.css` with `var(--color-primary)` | ✅ Yes |
|
|
37
|
+
| **MUI** | Wraps MUI's `<Button>` component, maps tokens to MUI theme | ✅ Yes |
|
|
38
|
+
| **shadcn/ui** | Re-exports with token-based variants | ✅ Yes |
|
|
39
|
+
| **Styled Components** | Generates template literals | ✅ (but breaks RSC) |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 1. Design System Structure (DDD Pattern)
|
|
17
44
|
|
|
18
45
|
```
|
|
19
46
|
shared/
|
|
20
|
-
ui/
|
|
21
|
-
domain/ #
|
|
22
|
-
tokens/ # Design tokens
|
|
23
|
-
colors.ts #
|
|
24
|
-
typography.ts #
|
|
25
|
-
spacing.ts #
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
ui/
|
|
48
|
+
domain/ # Framework-free. Zero CSS imports.
|
|
49
|
+
tokens/ # Design tokens as typed value objects
|
|
50
|
+
colors.ts # ColorToken — typed color palette
|
|
51
|
+
typography.ts # TypeScale — font families, sizes
|
|
52
|
+
spacing.ts # SpacingScale — margin, padding units
|
|
53
|
+
shadows.ts # ShadowToken — elevation levels
|
|
54
|
+
breakpoints.ts # Breakpoint — responsive breakpoints
|
|
55
|
+
types.ts # Component API contracts
|
|
56
|
+
component-api.ts # Base interfaces for all components
|
|
57
|
+
application/ # Logic layer (pure functions)
|
|
58
|
+
variant-engine.ts # Variant resolution (cva-like)
|
|
59
|
+
theme-service.ts # Dark/light theme switching
|
|
60
|
+
token-resolver.ts # Token → value lookup
|
|
61
|
+
infrastructure/ # CSS ADAPTER — swap this per framework
|
|
62
|
+
tailwind-adapter.ts # Tailwind class generation from tokens
|
|
63
|
+
css-modules-adapter.ts # CSS Modules variable injection
|
|
64
|
+
mui-adapter.ts # MUI theme provider wrapper
|
|
65
|
+
material-adapter.ts # Angular Material theme mapping
|
|
66
|
+
interfaces/ # Rendered components
|
|
37
67
|
button/
|
|
38
|
-
button.tsx # Component
|
|
39
|
-
button.module.css # Styles
|
|
68
|
+
button.tsx # Component (imports from application + infrastructure)
|
|
40
69
|
button.test.tsx # Tests
|
|
41
70
|
button.stories.tsx # Storybook stories
|
|
42
71
|
card/
|
|
43
72
|
modal/
|
|
44
73
|
form/
|
|
45
|
-
input/
|
|
46
|
-
select/
|
|
47
|
-
checkbox/
|
|
48
74
|
```
|
|
49
75
|
|
|
50
|
-
### Why DDD for a Design System?
|
|
51
|
-
|
|
52
|
-
| Concern | Without DDD | With DDD |
|
|
53
|
-
|---------|-------------|----------|
|
|
54
|
-
| Design tokens | Scattered as CSS variables | Typed domain values in `domain/tokens/` |
|
|
55
|
-
| Component API | Implicit, documented elsewhere | Explicit interfaces in `domain/types.ts` |
|
|
56
|
-
| Variant logic | Inline in component | Shared `application/variant-engine.ts` |
|
|
57
|
-
| CSS framework | Tightly coupled | Adaptable via `infrastructure/css-adapter.ts` |
|
|
58
|
-
| Theming | Hardcoded colors | Injected through `infrastructure/theme-adapter.ts` |
|
|
59
|
-
|
|
60
76
|
---
|
|
61
77
|
|
|
62
|
-
## 2. Design Tokens
|
|
78
|
+
## 2. Domain Layer — Design Tokens (Framework-Free)
|
|
63
79
|
|
|
64
|
-
|
|
80
|
+
This layer has zero CSS, zero framework, zero styling. It's pure TypeScript values.
|
|
65
81
|
|
|
66
82
|
```typescript
|
|
67
|
-
//
|
|
83
|
+
// domain/tokens/colors.ts
|
|
68
84
|
export class ColorToken {
|
|
69
85
|
private constructor(
|
|
70
86
|
public readonly name: string,
|
|
71
|
-
public readonly value: string,
|
|
87
|
+
public readonly value: string, // Hex: '#0f766e'
|
|
72
88
|
public readonly description: string,
|
|
73
89
|
) {}
|
|
74
90
|
|
|
75
91
|
static readonly PRIMARY = new ColorToken('primary', '#0f766e', 'Primary brand color');
|
|
76
|
-
static readonly PRIMARY_HOVER = new ColorToken('primary-hover', '#0d5e56'
|
|
77
|
-
static readonly DESTRUCTIVE = new ColorToken('destructive', '#dc2626'
|
|
78
|
-
static readonly BACKGROUND = new ColorToken('background', '#ffffff'
|
|
79
|
-
static readonly FOREGROUND = new ColorToken('foreground', '#0f172a'
|
|
92
|
+
static readonly PRIMARY_HOVER = new ColorToken('primary-hover', '#0d5e56');
|
|
93
|
+
static readonly DESTRUCTIVE = new ColorToken('destructive', '#dc2626');
|
|
94
|
+
static readonly BACKGROUND = new ColorToken('background', '#ffffff');
|
|
95
|
+
static readonly FOREGROUND = new ColorToken('foreground', '#0f172a');
|
|
96
|
+
|
|
97
|
+
// Dark mode overrides — still pure data, no CSS
|
|
98
|
+
static readonly DARK: Record<string, string> = {
|
|
99
|
+
background: '#0f172a',
|
|
100
|
+
foreground: '#f8fafc',
|
|
101
|
+
};
|
|
80
102
|
|
|
81
103
|
static all(): ColorToken[] {
|
|
82
|
-
return
|
|
104
|
+
return Object.values(this).filter(v => v instanceof ColorToken);
|
|
83
105
|
}
|
|
84
106
|
}
|
|
85
107
|
|
|
86
|
-
//
|
|
108
|
+
// domain/tokens/spacing.ts
|
|
87
109
|
export class SpacingToken {
|
|
88
|
-
|
|
110
|
+
constructor(
|
|
89
111
|
public readonly name: string,
|
|
90
|
-
public readonly value: string, //
|
|
91
|
-
public readonly px: number, //
|
|
112
|
+
public readonly value: string, // CSS value: '0.25rem'
|
|
113
|
+
public readonly px: number, // Design value: 4
|
|
92
114
|
) {}
|
|
93
115
|
|
|
94
116
|
static readonly XS = new SpacingToken('xs', '0.25rem', 4);
|
|
@@ -97,251 +119,233 @@ export class SpacingToken {
|
|
|
97
119
|
static readonly LG = new SpacingToken('lg', '1.5rem', 24);
|
|
98
120
|
static readonly XL = new SpacingToken('xl', '2rem', 32);
|
|
99
121
|
}
|
|
100
|
-
```
|
|
101
122
|
|
|
102
|
-
|
|
123
|
+
// domain/tokens/typography.ts
|
|
124
|
+
export class TypeScale {
|
|
125
|
+
constructor(
|
|
126
|
+
public readonly name: string,
|
|
127
|
+
public readonly fontSize: string,
|
|
128
|
+
public readonly lineHeight: string,
|
|
129
|
+
public readonly fontWeight: number,
|
|
130
|
+
) {}
|
|
103
131
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
description: string;
|
|
109
|
-
properties: Record<string, string>;
|
|
132
|
+
static readonly H1 = new TypeScale('h1', '2.5rem', '1.2', 700);
|
|
133
|
+
static readonly H2 = new TypeScale('h2', '2rem', '1.3', 600);
|
|
134
|
+
static readonly BODY = new TypeScale('body', '1rem', '1.5', 400);
|
|
135
|
+
static readonly SMALL = new TypeScale('small', '0.875rem', '1.5', 400);
|
|
110
136
|
}
|
|
137
|
+
```
|
|
111
138
|
|
|
112
|
-
|
|
113
|
-
name: string;
|
|
114
|
-
description: string;
|
|
115
|
-
props: ComponentProp[];
|
|
116
|
-
variants: ComponentVariant[];
|
|
117
|
-
accessibility: AccessibilityGuide;
|
|
118
|
-
examples: CodeExample[];
|
|
119
|
-
}
|
|
139
|
+
### Why tokens as domain objects?
|
|
120
140
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
default?: unknown;
|
|
126
|
-
description: string;
|
|
127
|
-
}
|
|
128
|
-
```
|
|
141
|
+
- **Type-safe** — `ColorToken.PRIMARY.value` is a `string`, can't pass an invalid color
|
|
142
|
+
- **Documented** — each token has a `description` field, self-documenting
|
|
143
|
+
- **Enumerable** — `ColorToken.all()` can generate CSS variables, Tailwind config, or MUI theme
|
|
144
|
+
- **Framework-independent** — the exact same tokens feed Tailwind, CSS Modules, or MUI
|
|
129
145
|
|
|
130
146
|
---
|
|
131
147
|
|
|
132
|
-
## 3. Variant Engine
|
|
148
|
+
## 3. Application Layer — Variant Engine
|
|
133
149
|
|
|
134
150
|
```typescript
|
|
135
|
-
//
|
|
136
|
-
export type
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
resolve(variant: string, size: string): string {
|
|
142
|
-
const variantClasses = this.config.variants?.[variant] ?? '';
|
|
143
|
-
const sizeClasses = this.config.sizes?.[size] ?? '';
|
|
144
|
-
return [variantClasses, sizeClasses].filter(Boolean).join(' ');
|
|
145
|
-
}
|
|
151
|
+
// application/variant-engine.ts
|
|
152
|
+
export type VariantDefinition = {
|
|
153
|
+
base: string;
|
|
154
|
+
variants: Record<string, Record<string, string>>;
|
|
155
|
+
defaults: Record<string, string>;
|
|
156
|
+
};
|
|
146
157
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return [base, engine.resolve(v, s)].filter(Boolean).join(' ');
|
|
157
|
-
};
|
|
158
|
-
}
|
|
158
|
+
export function defineVariants(config: VariantDefinition) {
|
|
159
|
+
return (props: Record<string, string>): string => {
|
|
160
|
+
const classes = [config.base];
|
|
161
|
+
for (const [key, values] of Object.entries(config.variants)) {
|
|
162
|
+
const value = props[key] ?? config.defaults[key];
|
|
163
|
+
if (value && values[value]) classes.push(values[value]);
|
|
164
|
+
}
|
|
165
|
+
return classes.filter(Boolean).join(' ');
|
|
166
|
+
};
|
|
159
167
|
}
|
|
160
168
|
```
|
|
161
169
|
|
|
162
170
|
---
|
|
163
171
|
|
|
164
|
-
## 4. CSS
|
|
172
|
+
## 4. Infrastructure — CSS Framework Adapters
|
|
165
173
|
|
|
166
|
-
###
|
|
174
|
+
### Tailwind Adapter
|
|
167
175
|
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
--color-primary-hover: #0d5e56;
|
|
183
|
-
--color-destructive: #dc2626;
|
|
184
|
-
--color-background: #ffffff;
|
|
185
|
-
--color-foreground: #0f172a;
|
|
186
|
-
--spacing-xs: 0.25rem;
|
|
187
|
-
--spacing-sm: 0.5rem;
|
|
188
|
-
--spacing-md: 1rem;
|
|
189
|
-
--radius-sm: 4px;
|
|
190
|
-
--radius-md: 8px;
|
|
191
|
-
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
|
|
192
|
-
--shadow-md: 0 4px 6px rgba(0,0,0,0.1);
|
|
193
|
-
--font-sans: 'Inter', system-ui, sans-serif;
|
|
194
|
-
--font-mono: 'JetBrains Mono', monospace;
|
|
176
|
+
```typescript
|
|
177
|
+
// infrastructure/tailwind-adapter.ts
|
|
178
|
+
// Translates domain tokens → Tailwind utility classes
|
|
179
|
+
import { ColorToken } from '../domain/tokens/colors';
|
|
180
|
+
import { SpacingToken } from '../domain/tokens/spacing';
|
|
181
|
+
|
|
182
|
+
export function tailwindColor(token: ColorToken): string {
|
|
183
|
+
const map: Record<string, string> = {
|
|
184
|
+
'#0f766e': 'bg-teal-700 text-white',
|
|
185
|
+
'#dc2626': 'bg-red-600 text-white',
|
|
186
|
+
'#ffffff': 'bg-white text-gray-900',
|
|
187
|
+
'#0f172a': 'bg-slate-900 text-white',
|
|
188
|
+
};
|
|
189
|
+
return map[token.value] ?? '';
|
|
195
190
|
}
|
|
196
191
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
192
|
+
export function tailwindSpacing(token: SpacingToken): string {
|
|
193
|
+
const map: Record<number, string> = {
|
|
194
|
+
4: 'p-1', 8: 'p-2', 16: 'p-4', 24: 'p-6', 32: 'p-8',
|
|
195
|
+
};
|
|
196
|
+
return map[token.px] ?? '';
|
|
200
197
|
}
|
|
201
198
|
```
|
|
202
199
|
|
|
203
|
-
###
|
|
204
|
-
|
|
205
|
-
```scss
|
|
206
|
-
// interfaces/button/button.module.scss
|
|
207
|
-
@use '../../infrastructure/tokens' as *;
|
|
208
|
-
|
|
209
|
-
.button {
|
|
210
|
-
display: inline-flex;
|
|
211
|
-
align-items: center;
|
|
212
|
-
justify-content: center;
|
|
213
|
-
border-radius: var(--radius-md); // Reference tokens via CSS vars
|
|
214
|
-
font-family: var(--font-sans);
|
|
215
|
-
font-weight: 500;
|
|
216
|
-
transition: all 0.2s;
|
|
217
|
-
cursor: pointer;
|
|
218
|
-
|
|
219
|
-
&:focus-visible {
|
|
220
|
-
outline: 2px solid var(--color-primary);
|
|
221
|
-
outline-offset: 2px;
|
|
222
|
-
}
|
|
200
|
+
### CSS Modules Adapter
|
|
223
201
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
202
|
+
```typescript
|
|
203
|
+
// infrastructure/css-modules-adapter.ts
|
|
204
|
+
// Generates CSS custom properties from domain tokens
|
|
205
|
+
import { ColorToken } from '../domain/tokens/colors';
|
|
206
|
+
import { SpacingToken } from '../domain/tokens/spacing';
|
|
229
207
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
color:
|
|
233
|
-
|
|
234
|
-
}
|
|
208
|
+
export function generateCSSVariables(): string {
|
|
209
|
+
const colors = ColorToken.all().map(t =>
|
|
210
|
+
` --color-${t.name}: ${t.value};`
|
|
211
|
+
).join('\n');
|
|
235
212
|
|
|
236
|
-
|
|
237
|
-
background: transparent;
|
|
238
|
-
border: 1px solid var(--color-primary);
|
|
239
|
-
color: var(--color-primary);
|
|
213
|
+
return `:root {\n${colors}\n}`;
|
|
240
214
|
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### MUI Adapter
|
|
241
218
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
219
|
+
```typescript
|
|
220
|
+
// infrastructure/mui-adapter.ts
|
|
221
|
+
// Maps domain tokens to MUI theme structure
|
|
222
|
+
import { createTheme } from '@mui/material';
|
|
223
|
+
import { ColorToken } from '../domain/tokens/colors';
|
|
224
|
+
|
|
225
|
+
export function createMuiTheme() {
|
|
226
|
+
return createTheme({
|
|
227
|
+
palette: {
|
|
228
|
+
primary: { main: ColorToken.PRIMARY.value },
|
|
229
|
+
background: { default: ColorToken.BACKGROUND.value },
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
}
|
|
245
233
|
```
|
|
246
234
|
|
|
247
|
-
###
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
235
|
+
### Choosing an Adapter
|
|
236
|
+
|
|
237
|
+
| Adapter | When to use | Tradeoffs |
|
|
238
|
+
|---------|-------------|-----------|
|
|
239
|
+
| **Tailwind** | Startups, rapid prototyping, utility-first | Class strings get long; needs `clsx`/`tailwind-merge` |
|
|
240
|
+
| **CSS Modules** | Enterprise, strict design systems, component libraries | More boilerplate; full control over CSS |
|
|
241
|
+
| **MUI** | Need a complete component library out of the box | Bundle size; customization can be complex |
|
|
242
|
+
| **Material (Angular)** | Angular projects needing Material Design | Framework-locked; heavy |
|
|
243
|
+
| **shadcn/ui** | Next.js projects wanting Radix primitives with Tailwind | Requires Tailwind; new ecosystem |
|
|
244
|
+
|
|
245
|
+
**Rule:** Pick one adapter per project. Never mix. The adapter is the only file that changes when you switch CSS frameworks.
|
|
255
246
|
|
|
256
247
|
---
|
|
257
248
|
|
|
258
|
-
## 5.
|
|
249
|
+
## 5. Component Implementation Pattern
|
|
259
250
|
|
|
260
251
|
```typescript
|
|
261
|
-
//
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
252
|
+
// interfaces/button/button.tsx
|
|
253
|
+
import { defineVariants } from '../../application/variant-engine';
|
|
254
|
+
// ↑ import from application, not infrastructure
|
|
255
|
+
// Components DON'T import the adapter directly
|
|
256
|
+
|
|
257
|
+
const buttonVariants = defineVariants({
|
|
258
|
+
base: 'inline-flex items-center justify-center rounded-md transition-colors',
|
|
259
|
+
variants: {
|
|
260
|
+
variant: {
|
|
261
|
+
primary: 'bg-primary text-white hover:bg-primary-hover',
|
|
262
|
+
secondary: 'border border-primary text-primary hover:bg-primary/10',
|
|
263
|
+
ghost: 'hover:bg-accent',
|
|
264
|
+
},
|
|
265
|
+
size: { sm: 'h-9 px-3', md: 'h-10 px-4', lg: 'h-11 px-8' },
|
|
266
|
+
},
|
|
267
|
+
defaults: { variant: 'primary', size: 'md' },
|
|
268
|
+
});
|
|
268
269
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
description: string;
|
|
270
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
271
|
+
variant?: 'primary' | 'secondary' | 'ghost';
|
|
272
|
+
size?: 'sm' | 'md' | 'lg';
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
{ key: 'Enter', action: 'activate', description: 'Activates the button' },
|
|
284
|
-
{ key: 'Space', action: 'activate', description: 'Activates the button' },
|
|
285
|
-
],
|
|
286
|
-
focusOrder: 1,
|
|
287
|
-
};
|
|
275
|
+
export function Button({ variant, size, className, ...props }: ButtonProps) {
|
|
276
|
+
return (
|
|
277
|
+
<button
|
|
278
|
+
className={[buttonVariants({ variant, size }), className].filter(Boolean).join(' ')}
|
|
279
|
+
{...props}
|
|
280
|
+
/>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
288
283
|
```
|
|
289
284
|
|
|
285
|
+
**Key rule:** Components import from `application/variant-engine.ts`, not from `infrastructure/`. The infrastructure adapter is only used at the app root (theme provider, CSS variable injection).
|
|
286
|
+
|
|
290
287
|
---
|
|
291
288
|
|
|
292
|
-
## 6.
|
|
289
|
+
## 6. Theming (Dark Mode)
|
|
293
290
|
|
|
294
291
|
```typescript
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
it('all tokens have unique names', () => {
|
|
298
|
-
const names = ColorToken.all().map(t => t.name);
|
|
299
|
-
expect(new Set(names).size).toBe(names.length);
|
|
300
|
-
});
|
|
301
|
-
});
|
|
292
|
+
// application/theme-service.ts
|
|
293
|
+
import { ColorToken } from '../domain/tokens/colors';
|
|
302
294
|
|
|
303
|
-
|
|
304
|
-
describe('Button', () => {
|
|
305
|
-
it('renders with variant classes', () => {
|
|
306
|
-
render(<Button variant="primary">Click</Button>);
|
|
307
|
-
expect(screen.getByRole('button')).toHaveClass('primary');
|
|
308
|
-
});
|
|
295
|
+
export type Theme = 'light' | 'dark';
|
|
309
296
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
expect(screen.getByRole('button')).toBeDisabled();
|
|
313
|
-
});
|
|
314
|
-
});
|
|
297
|
+
export class ThemeService {
|
|
298
|
+
private current: Theme = 'light';
|
|
315
299
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
300
|
+
get theme(): Theme { return this.current; }
|
|
301
|
+
|
|
302
|
+
setTheme(theme: Theme): void {
|
|
303
|
+
this.current = theme;
|
|
304
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
toggle(): void {
|
|
308
|
+
this.setTheme(this.current === 'light' ? 'dark' : 'light');
|
|
309
|
+
}
|
|
310
|
+
}
|
|
321
311
|
```
|
|
322
312
|
|
|
323
313
|
---
|
|
324
314
|
|
|
325
|
-
## 7.
|
|
315
|
+
## 7. Testing — Framework-Independent
|
|
326
316
|
|
|
327
317
|
```typescript
|
|
328
|
-
//
|
|
329
|
-
|
|
318
|
+
// domain/tokens/colors.test.ts
|
|
319
|
+
describe('ColorToken', () => {
|
|
320
|
+
it('all tokens have valid hex values', () => {
|
|
321
|
+
for (const t of ColorToken.all()) {
|
|
322
|
+
expect(t.value).toMatch(/^#[0-9a-fA-F]{6}$/);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
});
|
|
330
326
|
|
|
331
|
-
//
|
|
332
|
-
|
|
327
|
+
// application/variant-engine.test.ts
|
|
328
|
+
describe('VariantEngine', () => {
|
|
329
|
+
it('resolves variant classes', () => {
|
|
330
|
+
const button = defineVariants({ base: 'btn', variants: { color: { red: 'bg-red' } }, defaults: { color: 'red' } });
|
|
331
|
+
expect(button({})).toBe('btn bg-red');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
```
|
|
333
335
|
|
|
334
|
-
|
|
335
|
-
<div style={{ marginTop: 16 }}> // BAD — use spacing tokens
|
|
336
|
+
---
|
|
336
337
|
|
|
337
|
-
|
|
338
|
-
<Button primary large /> // BAD — use explicit variant/size props
|
|
338
|
+
## 8. Choosing Your Stack — Quick Reference
|
|
339
339
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
| Project type | Recommended adapter | Why |
|
|
341
|
+
|-------------|-------------------|-----|
|
|
342
|
+
| Next.js app, small team | Tailwind + shadcn/ui | Fastest path, good defaults, RSC-compatible |
|
|
343
|
+
| Next.js app, large team | CSS Modules + custom tokens | Full control, strict design system |
|
|
344
|
+
| Angular app, Material look | Angular Material adapter | Native integration, theming built-in |
|
|
345
|
+
| Angular app, custom design | CSS Modules + SCSS tokens | Full control, SCSS features |
|
|
346
|
+
| Multi-framework design system | CSS Modules + CSS Custom Properties | Works everywhere, zero framework lock-in |
|
|
343
347
|
|
|
344
348
|
---
|
|
345
349
|
|
|
346
|
-
*Version: 1.
|
|
350
|
+
*Version: 1.1.0*
|
|
347
351
|
*Last updated: 2026-07-03*
|
|
@@ -8,7 +8,7 @@ description: Full reference for Next.js enterprise code generation with DDD + Cl
|
|
|
8
8
|
> Canonical skill for generating production-grade Next.js code with DDD patterns.
|
|
9
9
|
> All code MUST follow these patterns. Validators enforce compliance.
|
|
10
10
|
>
|
|
11
|
-
> For design system and styling patterns, see: `.pi/skills/design-system-enterprise-codegen.md`
|
|
11
|
+
> For design system and styling patterns, see: `.pi/skills/design-system-enterprise-codegen.md` — tokens, CSS adapters, theming, component API contracts
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|