@jay-framework/design-system-validator 0.21.0 → 0.22.0
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/agent-kit/designer/design-system.md +222 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +447 -214
- package/package.json +7 -6
- package/plugin.yaml +2 -3
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Design System (DESIGN.md)
|
|
2
|
+
|
|
3
|
+
The project uses a `DESIGN.md` file to define design tokens — colors, typography, spacing, rounded corners, and component specs. The validator enforces these tokens across all `.jay-html` pages.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
Place a `DESIGN.md` alongside your pages. It applies to that route and all child routes unless a child has its own `DESIGN.md`.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/pages/
|
|
11
|
+
DESIGN.md ← applies to all routes
|
|
12
|
+
products/
|
|
13
|
+
DESIGN.md ← overrides for /products and children
|
|
14
|
+
admin/
|
|
15
|
+
DESIGN.md ← separate system for /admin
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Token Format
|
|
19
|
+
|
|
20
|
+
DESIGN.md uses YAML frontmatter following the [DESIGN.md spec](https://github.com/google-labs-code/design.md):
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
---
|
|
24
|
+
colors:
|
|
25
|
+
primary: '#2563eb'
|
|
26
|
+
text: '#0f172a'
|
|
27
|
+
background: '#ffffff'
|
|
28
|
+
surface: '#f8fafc'
|
|
29
|
+
|
|
30
|
+
typography:
|
|
31
|
+
headline-lg:
|
|
32
|
+
fontFamily: Inter
|
|
33
|
+
fontSize: 2.5rem
|
|
34
|
+
fontWeight: 700
|
|
35
|
+
lineHeight: 1.2
|
|
36
|
+
body-md:
|
|
37
|
+
fontFamily: Inter
|
|
38
|
+
fontSize: 1rem
|
|
39
|
+
fontWeight: 400
|
|
40
|
+
lineHeight: 1.6
|
|
41
|
+
|
|
42
|
+
spacing:
|
|
43
|
+
sm: 0.5rem
|
|
44
|
+
md: 1rem
|
|
45
|
+
lg: 1.5rem
|
|
46
|
+
|
|
47
|
+
rounded:
|
|
48
|
+
sm: 0.25rem
|
|
49
|
+
md: 0.5rem
|
|
50
|
+
full: 9999px
|
|
51
|
+
|
|
52
|
+
breakpoints:
|
|
53
|
+
mobile: 600px
|
|
54
|
+
tablet: 768px
|
|
55
|
+
desktop: 1024px
|
|
56
|
+
|
|
57
|
+
components:
|
|
58
|
+
button-primary:
|
|
59
|
+
backgroundColor: '{colors.primary}'
|
|
60
|
+
textColor: '{colors.background}'
|
|
61
|
+
rounded: '{rounded.md}'
|
|
62
|
+
jay:product-card:
|
|
63
|
+
backgroundColor: '{colors.surface}'
|
|
64
|
+
rounded: '{rounded.md}'
|
|
65
|
+
|
|
66
|
+
animations:
|
|
67
|
+
fade-in:
|
|
68
|
+
duration: 300ms
|
|
69
|
+
easing: cubic-bezier(0, 0, 0.2, 1)
|
|
70
|
+
slide-up:
|
|
71
|
+
duration: 500ms
|
|
72
|
+
easing: cubic-bezier(0.4, 0, 0.2, 1)
|
|
73
|
+
micro:
|
|
74
|
+
duration: 150ms
|
|
75
|
+
easing: ease-in-out
|
|
76
|
+
|
|
77
|
+
rules:
|
|
78
|
+
max-font-weights: 3
|
|
79
|
+
max-primary-buttons: 1
|
|
80
|
+
require-contrast-aa: true
|
|
81
|
+
---
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Values use `{path.to.token}` references — e.g., `{colors.primary}` resolves to `"#2563eb"`.
|
|
85
|
+
|
|
86
|
+
## Using Tokens in CSS
|
|
87
|
+
|
|
88
|
+
Use the token values directly in your `<style>` blocks:
|
|
89
|
+
|
|
90
|
+
```css
|
|
91
|
+
.card {
|
|
92
|
+
background-color: #f8fafc; /* matches {colors.surface} ✓ */
|
|
93
|
+
border-radius: 0.5rem; /* matches {rounded.md} ✓ */
|
|
94
|
+
padding: 1rem; /* matches {spacing.md} ✓ */
|
|
95
|
+
color: #0f172a; /* matches {colors.text} ✓ */
|
|
96
|
+
transition: opacity 300ms cubic-bezier(0, 0, 0.2, 1); /* matches fade-in preset ✓ */
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
CSS custom properties (`var(--name)`) are resolved statically when defined in `:root`. Unresolvable vars are accepted without validation.
|
|
101
|
+
|
|
102
|
+
## Breakpoints
|
|
103
|
+
|
|
104
|
+
The `breakpoints` section defines approved responsive breakpoints. Media queries using `max-width` values not in this list are flagged. Breakpoint names are used in validation output for readability.
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
breakpoints:
|
|
108
|
+
mobile: 600px
|
|
109
|
+
tablet: 768px
|
|
110
|
+
desktop: 1024px
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Use only these values in `@media (max-width: ...)` queries:
|
|
114
|
+
|
|
115
|
+
```css
|
|
116
|
+
@media (max-width: 768px) {
|
|
117
|
+
/* tablet ✓ */
|
|
118
|
+
}
|
|
119
|
+
@media (max-width: 750px) {
|
|
120
|
+
/* not in breakpoints ✗ */
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Component Specs
|
|
125
|
+
|
|
126
|
+
The `components` section defines expected styles for HTML elements (matched by class) and jay headless components (matched by `jay:` prefix):
|
|
127
|
+
|
|
128
|
+
- `button-primary` → validates elements with class `.button-primary`
|
|
129
|
+
- `jay:product-card` → validates the inline template inside `<jay:product-card>`
|
|
130
|
+
|
|
131
|
+
## Exceptions
|
|
132
|
+
|
|
133
|
+
When a value intentionally breaks the design system, exempt it:
|
|
134
|
+
|
|
135
|
+
**In CSS** — add a comment after the declaration:
|
|
136
|
+
|
|
137
|
+
```css
|
|
138
|
+
.hero {
|
|
139
|
+
padding: 7.5rem 0; /* design-system: allow */
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**On elements** — add the `jay-design="allow"` attribute:
|
|
144
|
+
|
|
145
|
+
```html
|
|
146
|
+
<div style="margin-top: 7.5rem" jay-design="allow"></div>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Validation Errors
|
|
150
|
+
|
|
151
|
+
These are the errors the validator produces. Use them to align your CSS with the design system.
|
|
152
|
+
|
|
153
|
+
**Hardcoded color:**
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
⚠ Hardcoded color "#ff0000" for color not in design system
|
|
157
|
+
Suggestion: Use token {colors.error} ("#dc2626") or add to DESIGN.md
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Off-scale spacing:**
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
⚠ padding value "13px" not in spacing scale
|
|
164
|
+
Suggestion: Use a spacing token: {spacing.md} ("1rem"), {spacing.lg} ("1.5rem")
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Off-scale border-radius:**
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
⚠ border-radius "10px" not in rounded scale
|
|
171
|
+
Suggestion: Use a rounded token: {rounded.md} ("0.5rem"), {rounded.full} ("9999px")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Component mismatch:**
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
⚠ <jay:product-card> inline template: background-color should be "#f8fafc"
|
|
178
|
+
per jay:product-card component spec, found "#ffffff"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Too many font weights:**
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
⚠ 4 unique font-weight values found (max: 3)
|
|
185
|
+
Suggestion: Reduce to 3 font-weight values from the typography tokens
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Animation duration not in presets:**
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
⚠ transition-duration "200ms" not in animation presets
|
|
192
|
+
Suggestion: Use an animation preset duration: fade-in (300ms), slide-up (500ms), micro (150ms)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Animation easing not in presets:**
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
⚠ transition-timing-function "ease" not in animation presets
|
|
199
|
+
Suggestion: Use an animation preset easing: fade-in (cubic-bezier(0, 0, 0.2, 1)), micro (ease-in-out)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Missing reduced-motion media query:**
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
⚠ Page uses transitions/animations but has no @media (prefers-reduced-motion) override
|
|
206
|
+
Suggestion: Add @media (prefers-reduced-motion: reduce) { * { transition-duration: 0s !important; animation-duration: 0s !important; } }
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Non-standard breakpoint:**
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
⚠ Media query @media (max-width: 750px) not in DESIGN.md breakpoints
|
|
213
|
+
Suggestion: Use a DESIGN.md breakpoint: mobile (600px), tablet (768px), desktop (1024px)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Low contrast:**
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
⚠ Contrast ratio 2.8:1 below WCAG AA (4.5:1) for color "#94a3b8"
|
|
220
|
+
on background "#f8fafc"
|
|
221
|
+
Suggestion: Darken text color or lighten background
|
|
222
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { JayHtmlValidatorFn } from '@jay-framework/compiler-shared';
|
|
2
|
-
import {
|
|
2
|
+
import { PluginAgentKitContext, PluginAgentKitResult } from '@jay-framework/stack-server-runtime';
|
|
3
3
|
|
|
4
4
|
declare const validateTokens: JayHtmlValidatorFn;
|
|
5
5
|
|
|
@@ -13,9 +13,9 @@ declare const validateContrast: JayHtmlValidatorFn;
|
|
|
13
13
|
* References handler for design-system-validator plugin.
|
|
14
14
|
*
|
|
15
15
|
* Runs during `jay-stack agent-kit` to generate AIditor add-menu entries
|
|
16
|
-
* from the project's DESIGN.md tokens.
|
|
16
|
+
* from the project's DESIGN.md tokens — one item per design token, with HTML previews.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
declare function
|
|
19
|
+
declare function generateDesignSystemAgentKit(ctx: PluginAgentKitContext): Promise<PluginAgentKitResult>;
|
|
20
20
|
|
|
21
|
-
export {
|
|
21
|
+
export { generateDesignSystemAgentKit, validateComponents, validateContrast, validateStructure, validateTokens };
|
package/dist/index.js
CHANGED
|
@@ -54,6 +54,12 @@ function parseDesignMd(content) {
|
|
|
54
54
|
const raw = yaml.load(frontmatter);
|
|
55
55
|
if (!raw || typeof raw !== "object")
|
|
56
56
|
return null;
|
|
57
|
+
const rawComponents = {};
|
|
58
|
+
if (raw.components) {
|
|
59
|
+
for (const [name, spec] of Object.entries(raw.components)) {
|
|
60
|
+
rawComponents[name] = { ...spec };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
57
63
|
resolveReferences(raw);
|
|
58
64
|
return {
|
|
59
65
|
name: raw.name,
|
|
@@ -61,8 +67,10 @@ function parseDesignMd(content) {
|
|
|
61
67
|
typography: raw.typography || {},
|
|
62
68
|
spacing: normalizeStringValues(raw.spacing),
|
|
63
69
|
rounded: normalizeStringValues(raw.rounded),
|
|
70
|
+
breakpoints: normalizeStringValues(raw.breakpoints),
|
|
64
71
|
animations: raw.animations || {},
|
|
65
72
|
components: raw.components || {},
|
|
73
|
+
rawComponents,
|
|
66
74
|
rules: raw.rules || {}
|
|
67
75
|
};
|
|
68
76
|
}
|
|
@@ -122,11 +130,21 @@ function getMediaQuery(rule) {
|
|
|
122
130
|
}
|
|
123
131
|
return void 0;
|
|
124
132
|
}
|
|
125
|
-
function parseRules(css, sourceIndexOffset = 0) {
|
|
133
|
+
function parseRules(css, sourceIndexOffset = 0, customProperties) {
|
|
126
134
|
const rules = [];
|
|
127
135
|
let ruleIndex = sourceIndexOffset;
|
|
128
136
|
const root = postcss.parse(css);
|
|
129
137
|
root.walkRules((rule) => {
|
|
138
|
+
if (customProperties && !getMediaQuery(rule)) {
|
|
139
|
+
const isRoot = rule.selectors.some((s) => s.trim() === ":root" || s.trim() === "html");
|
|
140
|
+
if (isRoot) {
|
|
141
|
+
rule.walkDecls((decl) => {
|
|
142
|
+
if (decl.prop.startsWith("--")) {
|
|
143
|
+
customProperties.set(decl.prop, decl.value);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
130
148
|
const declarations = [];
|
|
131
149
|
rule.walkDecls((decl) => {
|
|
132
150
|
declarations.push({
|
|
@@ -153,6 +171,19 @@ function parseRules(css, sourceIndexOffset = 0) {
|
|
|
153
171
|
});
|
|
154
172
|
return rules;
|
|
155
173
|
}
|
|
174
|
+
function resolveVarReferences(value, customProperties) {
|
|
175
|
+
return value.replace(
|
|
176
|
+
/var\(\s*(--[a-zA-Z0-9-]+)(?:\s*,\s*([^)]+))?\s*\)/g,
|
|
177
|
+
(_, name, fallback) => {
|
|
178
|
+
const resolved = customProperties.get(name);
|
|
179
|
+
if (resolved !== void 0)
|
|
180
|
+
return resolveVarReferences(resolved, customProperties);
|
|
181
|
+
if (fallback !== void 0)
|
|
182
|
+
return fallback.trim();
|
|
183
|
+
return _;
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
}
|
|
156
187
|
function parseInlineStyles(style) {
|
|
157
188
|
const declarations = [];
|
|
158
189
|
for (const part of style.split(";")) {
|
|
@@ -167,13 +198,19 @@ function parseInlineStyles(style) {
|
|
|
167
198
|
}
|
|
168
199
|
return declarations;
|
|
169
200
|
}
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
201
|
+
function buildSelectorCache(rules, root) {
|
|
202
|
+
const cache = /* @__PURE__ */ new Map();
|
|
203
|
+
for (const rule of rules) {
|
|
204
|
+
if (cache.has(rule.selector))
|
|
205
|
+
continue;
|
|
206
|
+
try {
|
|
207
|
+
const matches = root.querySelectorAll(rule.selector);
|
|
208
|
+
cache.set(rule.selector, new Set(matches));
|
|
209
|
+
} catch {
|
|
210
|
+
cache.set(rule.selector, /* @__PURE__ */ new Set());
|
|
211
|
+
}
|
|
176
212
|
}
|
|
213
|
+
return cache;
|
|
177
214
|
}
|
|
178
215
|
function compareSpecificity(a, b) {
|
|
179
216
|
if (a[0] !== b[0])
|
|
@@ -182,9 +219,11 @@ function compareSpecificity(a, b) {
|
|
|
182
219
|
return a[1] - b[1];
|
|
183
220
|
return a[2] - b[2];
|
|
184
221
|
}
|
|
185
|
-
function resolveForElement(element, rules,
|
|
222
|
+
function resolveForElement(element, rules, selectorCache) {
|
|
186
223
|
const resolved = {};
|
|
187
|
-
const matchingRules = rules.filter(
|
|
224
|
+
const matchingRules = rules.filter(
|
|
225
|
+
(rule) => selectorCache.get(rule.selector)?.has(element) ?? false
|
|
226
|
+
);
|
|
188
227
|
matchingRules.sort((a, b) => {
|
|
189
228
|
const specCmp = compareSpecificity(a.specificity, b.specificity);
|
|
190
229
|
if (specCmp !== 0)
|
|
@@ -231,15 +270,24 @@ function resolveForElement(element, rules, root) {
|
|
|
231
270
|
function resolveCascade(cssSources, root) {
|
|
232
271
|
let offset = 0;
|
|
233
272
|
const allRules = [];
|
|
273
|
+
const customProperties = /* @__PURE__ */ new Map();
|
|
234
274
|
for (const css of cssSources) {
|
|
235
|
-
const rules = parseRules(css, offset);
|
|
275
|
+
const rules = parseRules(css, offset, customProperties);
|
|
236
276
|
allRules.push(...rules);
|
|
237
277
|
offset += rules.length;
|
|
238
278
|
}
|
|
279
|
+
const selectorCache = buildSelectorCache(allRules, root);
|
|
239
280
|
const result = /* @__PURE__ */ new Map();
|
|
240
281
|
function walk(el) {
|
|
241
|
-
const resolved = resolveForElement(el, allRules,
|
|
282
|
+
const resolved = resolveForElement(el, allRules, selectorCache);
|
|
242
283
|
if (Object.keys(resolved).length > 0) {
|
|
284
|
+
if (customProperties.size > 0) {
|
|
285
|
+
for (const style of Object.values(resolved)) {
|
|
286
|
+
if (style.value.includes("var(")) {
|
|
287
|
+
style.value = resolveVarReferences(style.value, customProperties);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
243
291
|
result.set(el, resolved);
|
|
244
292
|
}
|
|
245
293
|
for (const child of el.childNodes) {
|
|
@@ -256,8 +304,9 @@ function resolveCascade(cssSources, root) {
|
|
|
256
304
|
function resolveCascadeByBreakpoint(cssSources, root) {
|
|
257
305
|
let offset = 0;
|
|
258
306
|
const allRules = [];
|
|
307
|
+
const customProperties = /* @__PURE__ */ new Map();
|
|
259
308
|
for (const css of cssSources) {
|
|
260
|
-
const rules = parseRules(css, offset);
|
|
309
|
+
const rules = parseRules(css, offset, customProperties);
|
|
261
310
|
allRules.push(...rules);
|
|
262
311
|
offset += rules.length;
|
|
263
312
|
}
|
|
@@ -268,8 +317,15 @@ function resolveCascadeByBreakpoint(cssSources, root) {
|
|
|
268
317
|
const result = /* @__PURE__ */ new Map();
|
|
269
318
|
for (const breakpoint of breakpoints) {
|
|
270
319
|
let walk = function(el) {
|
|
271
|
-
const resolved = resolveForElement(el, rulesForBreakpoint,
|
|
320
|
+
const resolved = resolveForElement(el, rulesForBreakpoint, selectorCache);
|
|
272
321
|
if (Object.keys(resolved).length > 0) {
|
|
322
|
+
if (customProperties.size > 0) {
|
|
323
|
+
for (const style of Object.values(resolved)) {
|
|
324
|
+
if (style.value.includes("var(")) {
|
|
325
|
+
style.value = resolveVarReferences(style.value, customProperties);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
273
329
|
breakpointResult.set(el, resolved);
|
|
274
330
|
}
|
|
275
331
|
for (const child of el.childNodes) {
|
|
@@ -280,6 +336,7 @@ function resolveCascadeByBreakpoint(cssSources, root) {
|
|
|
280
336
|
const rulesForBreakpoint = allRules.filter(
|
|
281
337
|
(r) => r.mediaQuery === void 0 || r.mediaQuery === breakpoint
|
|
282
338
|
);
|
|
339
|
+
const selectorCache = buildSelectorCache(rulesForBreakpoint, root);
|
|
283
340
|
const breakpointResult = /* @__PURE__ */ new Map();
|
|
284
341
|
for (const child of root.childNodes) {
|
|
285
342
|
if (child.nodeType === 1)
|
|
@@ -289,29 +346,6 @@ function resolveCascadeByBreakpoint(cssSources, root) {
|
|
|
289
346
|
}
|
|
290
347
|
return result;
|
|
291
348
|
}
|
|
292
|
-
function extractCssSources(root, filePath) {
|
|
293
|
-
const sources = [];
|
|
294
|
-
const linkedFiles = root.querySelectorAll('link[rel="stylesheet"]');
|
|
295
|
-
for (const link of linkedFiles) {
|
|
296
|
-
const href = link.getAttribute("href");
|
|
297
|
-
if (href && !href.startsWith("http")) {
|
|
298
|
-
try {
|
|
299
|
-
const dir = require("node:path").dirname(filePath);
|
|
300
|
-
const cssPath = require("node:path").resolve(dir, href);
|
|
301
|
-
const css = require("node:fs").readFileSync(cssPath, "utf-8");
|
|
302
|
-
sources.push(css);
|
|
303
|
-
} catch {
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const styleBlocks = root.querySelectorAll("style");
|
|
308
|
-
for (const style of styleBlocks) {
|
|
309
|
-
const text = style.textContent;
|
|
310
|
-
if (text)
|
|
311
|
-
sources.push(text);
|
|
312
|
-
}
|
|
313
|
-
return sources;
|
|
314
|
-
}
|
|
315
349
|
function normalizeHex(color) {
|
|
316
350
|
const hex = color.trim().toLowerCase();
|
|
317
351
|
if (!/^#[0-9a-f]{3,8}$/.test(hex))
|
|
@@ -445,10 +479,9 @@ function matchSpacing(value, spacingTokens) {
|
|
|
445
479
|
if (part === "0" || part === "auto" || part.startsWith("var("))
|
|
446
480
|
continue;
|
|
447
481
|
if (!tokenValues.has(part)) {
|
|
448
|
-
const suggestions = Object.entries(spacingTokens).map(([name, val]) => `{spacing.${name}} ("${val}")`).join(", ");
|
|
449
482
|
return {
|
|
450
483
|
matches: false,
|
|
451
|
-
suggestion: `Use a spacing token
|
|
484
|
+
suggestion: `Use a DESIGN.md spacing token`
|
|
452
485
|
};
|
|
453
486
|
}
|
|
454
487
|
}
|
|
@@ -461,10 +494,9 @@ function matchRounded(value, roundedTokens) {
|
|
|
461
494
|
const tokenValues = new Set(Object.values(roundedTokens));
|
|
462
495
|
tokenValues.add("0");
|
|
463
496
|
if (!tokenValues.has(value)) {
|
|
464
|
-
const suggestions = Object.entries(roundedTokens).map(([name, val]) => `{rounded.${name}} ("${val}")`).join(", ");
|
|
465
497
|
return {
|
|
466
498
|
matches: false,
|
|
467
|
-
suggestion: `Use a rounded token
|
|
499
|
+
suggestion: `Use a DESIGN.md rounded token`
|
|
468
500
|
};
|
|
469
501
|
}
|
|
470
502
|
return { matches: true };
|
|
@@ -500,7 +532,7 @@ function matchTypographyProperty(property, value, typographyTokens) {
|
|
|
500
532
|
}
|
|
501
533
|
return {
|
|
502
534
|
matches: false,
|
|
503
|
-
suggestion: `
|
|
535
|
+
suggestion: `Use a DESIGN.md typography token`
|
|
504
536
|
};
|
|
505
537
|
}
|
|
506
538
|
function matchAnimationDuration(value, animationTokens) {
|
|
@@ -516,10 +548,9 @@ function matchAnimationDuration(value, animationTokens) {
|
|
|
516
548
|
return { matches: true };
|
|
517
549
|
if (durations.has(value))
|
|
518
550
|
return { matches: true };
|
|
519
|
-
const suggestions = Object.entries(animationTokens).filter(([, p]) => p.duration).map(([name, p]) => `${name} (${p.duration})`).join(", ");
|
|
520
551
|
return {
|
|
521
552
|
matches: false,
|
|
522
|
-
suggestion: `Use
|
|
553
|
+
suggestion: `Use a DESIGN.md animation preset duration`
|
|
523
554
|
};
|
|
524
555
|
}
|
|
525
556
|
function matchAnimationEasing(value, animationTokens) {
|
|
@@ -540,14 +571,13 @@ function matchAnimationEasing(value, animationTokens) {
|
|
|
540
571
|
if (e.replace(/\s/g, "") === normalizedValue)
|
|
541
572
|
return { matches: true };
|
|
542
573
|
}
|
|
543
|
-
const suggestions = Object.entries(animationTokens).filter(([, p]) => p.easing).map(([name, p]) => `${name} (${p.easing})`).join(", ");
|
|
544
574
|
return {
|
|
545
575
|
matches: false,
|
|
546
|
-
suggestion: `Use
|
|
576
|
+
suggestion: `Use a DESIGN.md animation preset easing`
|
|
547
577
|
};
|
|
548
578
|
}
|
|
549
|
-
function matchComponent(elementStyles, componentSpec,
|
|
550
|
-
const
|
|
579
|
+
function matchComponent(elementStyles, componentSpec, rawSpec) {
|
|
580
|
+
const mismatches = [];
|
|
551
581
|
const propertyMapping = {
|
|
552
582
|
backgroundColor: "background-color",
|
|
553
583
|
textColor: "color",
|
|
@@ -562,13 +592,72 @@ function matchComponent(elementStyles, componentSpec, componentName) {
|
|
|
562
592
|
const cssProp = propertyMapping[specProp] || specProp;
|
|
563
593
|
const actualValue = elementStyles[cssProp];
|
|
564
594
|
if (actualValue && actualValue !== expectedValue) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
595
|
+
const rawValue = rawSpec?.[specProp];
|
|
596
|
+
mismatches.push({
|
|
597
|
+
cssProp,
|
|
598
|
+
expected: expectedValue,
|
|
599
|
+
expectedRaw: rawValue !== expectedValue ? rawValue : void 0,
|
|
600
|
+
actual: actualValue
|
|
568
601
|
});
|
|
569
602
|
}
|
|
570
603
|
}
|
|
571
|
-
return
|
|
604
|
+
return mismatches;
|
|
605
|
+
}
|
|
606
|
+
function formatComponentMismatches(componentName, mismatches, elementDescription) {
|
|
607
|
+
const specPath = `DESIGN.md components.${componentName}`;
|
|
608
|
+
const header = elementDescription ? `${specPath} on ${elementDescription}:` : `${specPath}:`;
|
|
609
|
+
const lines = [header];
|
|
610
|
+
for (let i = 0; i < mismatches.length; i++) {
|
|
611
|
+
const m = mismatches[i];
|
|
612
|
+
const expectedDisplay = m.expectedRaw ? `${m.expectedRaw} (${m.expected})` : `"${m.expected}"`;
|
|
613
|
+
lines.push(` ${i + 1}. ${m.cssProp} should be ${expectedDisplay}, found "${m.actual}"`);
|
|
614
|
+
}
|
|
615
|
+
return lines.join("\n");
|
|
616
|
+
}
|
|
617
|
+
function isBackgroundShorthand(property) {
|
|
618
|
+
return property === "background";
|
|
619
|
+
}
|
|
620
|
+
function splitTopLevelCommas(value) {
|
|
621
|
+
const parts = [];
|
|
622
|
+
let depth = 0;
|
|
623
|
+
let current = "";
|
|
624
|
+
for (const ch of value) {
|
|
625
|
+
if (ch === "(" || ch === "[")
|
|
626
|
+
depth++;
|
|
627
|
+
else if (ch === ")" || ch === "]")
|
|
628
|
+
depth--;
|
|
629
|
+
if (ch === "," && depth === 0) {
|
|
630
|
+
parts.push(current.trim());
|
|
631
|
+
current = "";
|
|
632
|
+
} else {
|
|
633
|
+
current += ch;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (current.trim())
|
|
637
|
+
parts.push(current.trim());
|
|
638
|
+
return parts;
|
|
639
|
+
}
|
|
640
|
+
function extractBackgroundColors(value) {
|
|
641
|
+
if (value.startsWith("var(") || value === "transparent" || value === "none" || value === "inherit" || value === "initial" || value === "unset") {
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
const layers = splitTopLevelCommas(value);
|
|
645
|
+
const colors = [];
|
|
646
|
+
for (const layer of layers) {
|
|
647
|
+
if (/^(linear-gradient|radial-gradient|conic-gradient|repeating-|url)\s*\(/i.test(layer)) {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
const hexMatch = layer.match(/(^|\s)(#[0-9a-fA-F]{3,8})(\s|$)/);
|
|
651
|
+
if (hexMatch) {
|
|
652
|
+
colors.push(hexMatch[2]);
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
const rgbMatch = layer.match(/(^|\s)(rgba?\s*\([^)]+\))/);
|
|
656
|
+
if (rgbMatch) {
|
|
657
|
+
colors.push(rgbMatch[2]);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return colors;
|
|
572
661
|
}
|
|
573
662
|
function hexToRgbValues(hex) {
|
|
574
663
|
const normalized = normalizeHex(hex);
|
|
@@ -591,15 +680,25 @@ function contrastRatio(l1, l2) {
|
|
|
591
680
|
const darker = Math.min(l1, l2);
|
|
592
681
|
return (lighter + 0.05) / (darker + 0.05);
|
|
593
682
|
}
|
|
594
|
-
const
|
|
595
|
-
function
|
|
596
|
-
|
|
597
|
-
|
|
683
|
+
const GUIDE_SUGGESTION$1 = "See design-system-validator agent-kit/designer/design-system.md for usage guide";
|
|
684
|
+
function elementHint(el) {
|
|
685
|
+
const tag = el.rawTagName?.toLowerCase() || "element";
|
|
686
|
+
const cls = el.getAttribute?.("class");
|
|
687
|
+
const text = el.textContent?.trim();
|
|
688
|
+
const words = text ? text.split(/\s+/).slice(0, 3).join(" ") : "";
|
|
689
|
+
const truncated = words && text.split(/\s+/).length > 3 ? words + "..." : words;
|
|
690
|
+
const parts = [`<${tag}`];
|
|
691
|
+
if (cls)
|
|
692
|
+
parts.push(`class="${cls}"`);
|
|
693
|
+
parts.push(">");
|
|
694
|
+
if (truncated)
|
|
695
|
+
parts.push(`"${truncated}"`);
|
|
696
|
+
return parts.join(" ");
|
|
598
697
|
}
|
|
599
698
|
function validateElementStyles(el, styles, tokens, breakpointLabel, designMdPath, findings) {
|
|
600
|
-
const
|
|
699
|
+
const hint = elementHint(el);
|
|
601
700
|
const prefix = breakpointLabel ? `[${breakpointLabel}] ` : "";
|
|
602
|
-
const
|
|
701
|
+
const sug = (s) => s?.replace("DESIGN.md", designMdPath);
|
|
603
702
|
for (const [property, resolved] of Object.entries(styles)) {
|
|
604
703
|
if (resolved.allowed)
|
|
605
704
|
continue;
|
|
@@ -608,9 +707,9 @@ function validateElementStyles(el, styles, tokens, breakpointLabel, designMdPath
|
|
|
608
707
|
if (!result.matches) {
|
|
609
708
|
findings.push({
|
|
610
709
|
severity: "warning",
|
|
611
|
-
message: `${prefix}Hardcoded color "${resolved.value}" for ${property} not in design system`,
|
|
612
|
-
suggestion: result.suggestion
|
|
613
|
-
element:
|
|
710
|
+
message: `${prefix}${hint} — Hardcoded color "${resolved.value}" for ${property} not in design system`,
|
|
711
|
+
suggestion: sug(result.suggestion),
|
|
712
|
+
element: hint
|
|
614
713
|
});
|
|
615
714
|
}
|
|
616
715
|
}
|
|
@@ -619,9 +718,9 @@ function validateElementStyles(el, styles, tokens, breakpointLabel, designMdPath
|
|
|
619
718
|
if (!result.matches) {
|
|
620
719
|
findings.push({
|
|
621
720
|
severity: "warning",
|
|
622
|
-
message: `${prefix}${property} value "${resolved.value}" not in spacing scale`,
|
|
623
|
-
suggestion: result.suggestion
|
|
624
|
-
element:
|
|
721
|
+
message: `${prefix}${hint} — ${property} value "${resolved.value}" not in spacing scale`,
|
|
722
|
+
suggestion: sug(result.suggestion),
|
|
723
|
+
element: hint
|
|
625
724
|
});
|
|
626
725
|
}
|
|
627
726
|
}
|
|
@@ -630,9 +729,9 @@ function validateElementStyles(el, styles, tokens, breakpointLabel, designMdPath
|
|
|
630
729
|
if (!result.matches) {
|
|
631
730
|
findings.push({
|
|
632
731
|
severity: "warning",
|
|
633
|
-
message: `${prefix}border-radius "${resolved.value}" not in rounded scale`,
|
|
634
|
-
suggestion: result.suggestion
|
|
635
|
-
element:
|
|
732
|
+
message: `${prefix}${hint} — border-radius "${resolved.value}" not in rounded scale`,
|
|
733
|
+
suggestion: sug(result.suggestion),
|
|
734
|
+
element: hint
|
|
636
735
|
});
|
|
637
736
|
}
|
|
638
737
|
}
|
|
@@ -641,9 +740,9 @@ function validateElementStyles(el, styles, tokens, breakpointLabel, designMdPath
|
|
|
641
740
|
if (!result.matches) {
|
|
642
741
|
findings.push({
|
|
643
742
|
severity: "warning",
|
|
644
|
-
message: `${prefix}${property} value "${resolved.value}" not in typography tokens`,
|
|
645
|
-
suggestion: result.suggestion
|
|
646
|
-
element:
|
|
743
|
+
message: `${prefix}${hint} — ${property} value "${resolved.value}" not in typography tokens`,
|
|
744
|
+
suggestion: sug(result.suggestion),
|
|
745
|
+
element: hint
|
|
647
746
|
});
|
|
648
747
|
}
|
|
649
748
|
}
|
|
@@ -652,9 +751,9 @@ function validateElementStyles(el, styles, tokens, breakpointLabel, designMdPath
|
|
|
652
751
|
if (!result.matches) {
|
|
653
752
|
findings.push({
|
|
654
753
|
severity: "warning",
|
|
655
|
-
message: `${prefix}${property} "${resolved.value}" not in animation presets`,
|
|
656
|
-
suggestion: result.suggestion
|
|
657
|
-
element:
|
|
754
|
+
message: `${prefix}${hint} — ${property} "${resolved.value}" not in animation presets`,
|
|
755
|
+
suggestion: sug(result.suggestion),
|
|
756
|
+
element: hint
|
|
658
757
|
});
|
|
659
758
|
}
|
|
660
759
|
}
|
|
@@ -663,12 +762,26 @@ function validateElementStyles(el, styles, tokens, breakpointLabel, designMdPath
|
|
|
663
762
|
if (!result.matches) {
|
|
664
763
|
findings.push({
|
|
665
764
|
severity: "warning",
|
|
666
|
-
message: `${prefix}${property} "${resolved.value}" not in animation presets`,
|
|
667
|
-
suggestion: result.suggestion
|
|
668
|
-
element:
|
|
765
|
+
message: `${prefix}${hint} — ${property} "${resolved.value}" not in animation presets`,
|
|
766
|
+
suggestion: sug(result.suggestion),
|
|
767
|
+
element: hint
|
|
669
768
|
});
|
|
670
769
|
}
|
|
671
770
|
}
|
|
771
|
+
if (isBackgroundShorthand(property) && Object.keys(tokens.colors).length > 0) {
|
|
772
|
+
const bgColors = extractBackgroundColors(resolved.value);
|
|
773
|
+
for (const bgColor of bgColors) {
|
|
774
|
+
const result = matchColor(bgColor, tokens.colors);
|
|
775
|
+
if (!result.matches) {
|
|
776
|
+
findings.push({
|
|
777
|
+
severity: "warning",
|
|
778
|
+
message: `${prefix}${hint} — Hardcoded color "${bgColor}" in background not in design system`,
|
|
779
|
+
suggestion: sug(result.suggestion),
|
|
780
|
+
element: hint
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
672
785
|
}
|
|
673
786
|
}
|
|
674
787
|
const validateTokens = (ctx) => {
|
|
@@ -677,18 +790,39 @@ const validateTokens = (ctx) => {
|
|
|
677
790
|
return [];
|
|
678
791
|
const { tokens, designMdPath } = found;
|
|
679
792
|
const findings = [];
|
|
680
|
-
|
|
681
|
-
if (cssSources.length === 0)
|
|
793
|
+
if (!ctx.css)
|
|
682
794
|
return [];
|
|
795
|
+
const cssSources = [ctx.css];
|
|
796
|
+
const breakpointNames = /* @__PURE__ */ new Map();
|
|
797
|
+
for (const [name, value] of Object.entries(tokens.breakpoints)) {
|
|
798
|
+
breakpointNames.set(`(max-width: ${value})`, name);
|
|
799
|
+
}
|
|
800
|
+
const hasBreakpoints = Object.keys(tokens.breakpoints).length > 0;
|
|
801
|
+
const flaggedBreakpoints = /* @__PURE__ */ new Set();
|
|
683
802
|
const byBreakpoint = resolveCascadeByBreakpoint(cssSources, ctx.body);
|
|
684
803
|
for (const [breakpoint, cascade] of byBreakpoint) {
|
|
685
|
-
|
|
804
|
+
if (breakpoint && hasBreakpoints && breakpoint.includes("max-width") && !breakpointNames.has(breakpoint)) {
|
|
805
|
+
if (!flaggedBreakpoints.has(breakpoint)) {
|
|
806
|
+
flaggedBreakpoints.add(breakpoint);
|
|
807
|
+
const defined = Object.entries(tokens.breakpoints).map(([n, v]) => `${n} (${v})`).join(", ");
|
|
808
|
+
findings.push({
|
|
809
|
+
severity: "warning",
|
|
810
|
+
message: `Media query @media ${breakpoint} not in DESIGN.md breakpoints`,
|
|
811
|
+
suggestion: `Use a ${designMdPath} breakpoint: ${defined}`
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const name = breakpoint ? breakpointNames.get(breakpoint) : void 0;
|
|
816
|
+
const label = name ? `${name}` : breakpoint || "";
|
|
686
817
|
for (const [el, styles] of cascade) {
|
|
687
818
|
validateElementStyles(el, styles, tokens, label, designMdPath, findings);
|
|
688
819
|
}
|
|
689
820
|
}
|
|
690
821
|
if (Object.keys(tokens.animations).length > 0) {
|
|
691
|
-
checkReducedMotion(cssSources,
|
|
822
|
+
checkReducedMotion(cssSources, findings);
|
|
823
|
+
}
|
|
824
|
+
if (findings.length > 0) {
|
|
825
|
+
findings.push({ severity: "warning", message: "", suggestion: GUIDE_SUGGESTION$1 });
|
|
692
826
|
}
|
|
693
827
|
return findings;
|
|
694
828
|
};
|
|
@@ -700,7 +834,7 @@ const ANIMATION_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
700
834
|
"animation-duration",
|
|
701
835
|
"animation-name"
|
|
702
836
|
]);
|
|
703
|
-
function checkReducedMotion(cssSources,
|
|
837
|
+
function checkReducedMotion(cssSources, findings) {
|
|
704
838
|
let hasAnimations = false;
|
|
705
839
|
let hasReducedMotion = false;
|
|
706
840
|
for (const css of cssSources) {
|
|
@@ -720,21 +854,35 @@ function checkReducedMotion(cssSources, designMdPath, findings) {
|
|
|
720
854
|
findings.push({
|
|
721
855
|
severity: "warning",
|
|
722
856
|
message: "Page uses transitions/animations but has no @media (prefers-reduced-motion) override",
|
|
723
|
-
suggestion: "Add @media (prefers-reduced-motion: reduce) { * { transition-duration: 0s !important; animation-duration: 0s !important; } }"
|
|
857
|
+
suggestion: "Add @media (prefers-reduced-motion: reduce) { * { transition-duration: 0s !important; animation-duration: 0s !important; } }"
|
|
724
858
|
});
|
|
725
859
|
}
|
|
726
860
|
}
|
|
727
|
-
const
|
|
861
|
+
const GUIDE_SUGGESTION = "See design-system-validator agent-kit/designer/design-system.md for usage guide";
|
|
862
|
+
function describeElement(el) {
|
|
863
|
+
const tag = el.rawTagName?.toLowerCase() || "element";
|
|
864
|
+
const cls = el.getAttribute?.("class");
|
|
865
|
+
const ref = el.getAttribute?.("ref");
|
|
866
|
+
const text = el.textContent?.trim().slice(0, 30);
|
|
867
|
+
const parts = [`<${tag}`];
|
|
868
|
+
if (cls)
|
|
869
|
+
parts.push(`class="${cls}"`);
|
|
870
|
+
if (ref)
|
|
871
|
+
parts.push(`ref="${ref}"`);
|
|
872
|
+
parts.push(">");
|
|
873
|
+
if (text)
|
|
874
|
+
parts.push(`"${text}${el.textContent.trim().length > 30 ? "…" : ""}"`);
|
|
875
|
+
return parts.join(" ");
|
|
876
|
+
}
|
|
728
877
|
const validateComponents = (ctx) => {
|
|
729
878
|
const found = findDesignMd(ctx.filePath, ctx.projectRoot);
|
|
730
879
|
if (!found || Object.keys(found.tokens.components).length === 0)
|
|
731
880
|
return [];
|
|
732
881
|
const { tokens, designMdPath } = found;
|
|
733
|
-
const refs = `
|
|
734
|
-
See ${designMdPath} for component specs, ${DESIGNER_GUIDE$2} for usage guide.`;
|
|
735
882
|
const findings = [];
|
|
736
|
-
|
|
737
|
-
|
|
883
|
+
if (!ctx.css)
|
|
884
|
+
return [];
|
|
885
|
+
const cascade = resolveCascade([ctx.css], ctx.body);
|
|
738
886
|
const jayComponents = /* @__PURE__ */ new Map();
|
|
739
887
|
const htmlComponents = /* @__PURE__ */ new Map();
|
|
740
888
|
for (const componentName of Object.keys(tokens.components)) {
|
|
@@ -764,6 +912,7 @@ See ${designMdPath} for component specs, ${DESIGNER_GUIDE$2} for usage guide.`;
|
|
|
764
912
|
for (const [contractName, elements] of jayComponents) {
|
|
765
913
|
const componentName = `jay:${contractName}`;
|
|
766
914
|
const spec = tokens.components[componentName];
|
|
915
|
+
const rawSpec = tokens.rawComponents[componentName];
|
|
767
916
|
if (!spec)
|
|
768
917
|
continue;
|
|
769
918
|
for (const el of elements) {
|
|
@@ -775,21 +924,21 @@ See ${designMdPath} for component specs, ${DESIGNER_GUIDE$2} for usage guide.`;
|
|
|
775
924
|
if (!resolved.allowed)
|
|
776
925
|
styleValues[prop] = resolved.value;
|
|
777
926
|
}
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
}
|
|
927
|
+
const mismatches = matchComponent(styleValues, spec, rawSpec);
|
|
928
|
+
if (mismatches.length > 0) {
|
|
929
|
+
const desc = describeElement(el);
|
|
930
|
+
findings.push({
|
|
931
|
+
severity: "warning",
|
|
932
|
+
message: formatComponentMismatches(componentName, mismatches, desc),
|
|
933
|
+
suggestion: `See ${designMdPath} components section`,
|
|
934
|
+
element: desc
|
|
935
|
+
});
|
|
788
936
|
}
|
|
789
937
|
}
|
|
790
938
|
}
|
|
791
939
|
for (const [componentName, selector] of htmlComponents) {
|
|
792
940
|
const spec = tokens.components[componentName];
|
|
941
|
+
const rawSpec = tokens.rawComponents[componentName];
|
|
793
942
|
if (!spec)
|
|
794
943
|
continue;
|
|
795
944
|
for (const [el, styles] of cascade) {
|
|
@@ -805,33 +954,31 @@ See ${designMdPath} for component specs, ${DESIGNER_GUIDE$2} for usage guide.`;
|
|
|
805
954
|
if (!resolved.allowed)
|
|
806
955
|
styleValues[prop] = resolved.value;
|
|
807
956
|
}
|
|
808
|
-
const
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
}
|
|
957
|
+
const mismatches = matchComponent(styleValues, spec, rawSpec);
|
|
958
|
+
if (mismatches.length > 0) {
|
|
959
|
+
const desc = describeElement(el);
|
|
960
|
+
findings.push({
|
|
961
|
+
severity: "warning",
|
|
962
|
+
message: formatComponentMismatches(componentName, mismatches, desc),
|
|
963
|
+
suggestion: `See ${designMdPath} components section`,
|
|
964
|
+
element: desc
|
|
965
|
+
});
|
|
818
966
|
}
|
|
819
967
|
}
|
|
820
968
|
}
|
|
969
|
+
if (findings.length > 0) {
|
|
970
|
+
findings.push({ severity: "warning", message: "", suggestion: GUIDE_SUGGESTION });
|
|
971
|
+
}
|
|
821
972
|
return findings;
|
|
822
973
|
};
|
|
823
|
-
const DESIGNER_GUIDE$1 = "agent-kit/designer/design-system.md";
|
|
824
974
|
const validateStructure = (ctx) => {
|
|
825
975
|
const found = findDesignMd(ctx.filePath, ctx.projectRoot);
|
|
826
976
|
if (!found)
|
|
827
977
|
return [];
|
|
828
|
-
const { tokens
|
|
829
|
-
const refs = `
|
|
830
|
-
See ${designMdPath} for rules, ${DESIGNER_GUIDE$1} for usage guide.`;
|
|
978
|
+
const { tokens } = found;
|
|
831
979
|
const findings = [];
|
|
832
|
-
if (tokens.rules["max-font-weights"]) {
|
|
833
|
-
const
|
|
834
|
-
const cascade = resolveCascade(cssSources, ctx.body);
|
|
980
|
+
if (tokens.rules["max-font-weights"] && ctx.css) {
|
|
981
|
+
const cascade = resolveCascade([ctx.css], ctx.body);
|
|
835
982
|
const fontWeights = /* @__PURE__ */ new Set();
|
|
836
983
|
for (const [, styles] of cascade) {
|
|
837
984
|
const fw = styles["font-weight"];
|
|
@@ -843,7 +990,7 @@ See ${designMdPath} for rules, ${DESIGNER_GUIDE$1} for usage guide.`;
|
|
|
843
990
|
findings.push({
|
|
844
991
|
severity: "warning",
|
|
845
992
|
message: `${fontWeights.size} unique font-weight values found (max: ${max}): ${[...fontWeights].join(", ")}`,
|
|
846
|
-
suggestion: `Reduce to ${max} font-weight values from the typography tokens
|
|
993
|
+
suggestion: `Reduce to ${max} font-weight values from the DESIGN.md typography tokens`
|
|
847
994
|
});
|
|
848
995
|
}
|
|
849
996
|
}
|
|
@@ -856,9 +1003,9 @@ See ${designMdPath} for rules, ${DESIGNER_GUIDE$1} for usage guide.`;
|
|
|
856
1003
|
const classes = el.getAttribute?.("class") || "";
|
|
857
1004
|
if (!classes.includes("primary") && !classes.includes("btn-primary"))
|
|
858
1005
|
return;
|
|
859
|
-
const
|
|
1006
|
+
const ref = el.getAttribute?.("ref") || "";
|
|
860
1007
|
const text = el.textContent?.trim() || "";
|
|
861
|
-
const identity = `${
|
|
1008
|
+
const identity = `${ref}|${text}`;
|
|
862
1009
|
primaryButtons.set(identity, true);
|
|
863
1010
|
});
|
|
864
1011
|
const max = tokens.rules["max-primary-buttons"];
|
|
@@ -866,13 +1013,12 @@ See ${designMdPath} for rules, ${DESIGNER_GUIDE$1} for usage guide.`;
|
|
|
866
1013
|
findings.push({
|
|
867
1014
|
severity: "warning",
|
|
868
1015
|
message: `${primaryButtons.size} distinct primary buttons found (max: ${max})`,
|
|
869
|
-
suggestion: `Reduce to ${max} primary action button per page
|
|
1016
|
+
suggestion: `Reduce to ${max} primary action button per page`
|
|
870
1017
|
});
|
|
871
1018
|
}
|
|
872
1019
|
}
|
|
873
1020
|
return findings;
|
|
874
1021
|
};
|
|
875
|
-
const DESIGNER_GUIDE = "agent-kit/designer/design-system.md";
|
|
876
1022
|
function resolveColorValue(value) {
|
|
877
1023
|
if (value.startsWith("#"))
|
|
878
1024
|
return value;
|
|
@@ -904,14 +1050,10 @@ const validateContrast = (ctx) => {
|
|
|
904
1050
|
const found = findDesignMd(ctx.filePath, ctx.projectRoot);
|
|
905
1051
|
if (!found || !found.tokens.rules["require-contrast-aa"])
|
|
906
1052
|
return [];
|
|
907
|
-
const { tokens, designMdPath } = found;
|
|
908
|
-
const refs = `
|
|
909
|
-
See ${designMdPath} for color tokens, ${DESIGNER_GUIDE} for usage guide.`;
|
|
910
1053
|
const findings = [];
|
|
911
|
-
|
|
912
|
-
if (cssSources.length === 0)
|
|
1054
|
+
if (!ctx.css)
|
|
913
1055
|
return [];
|
|
914
|
-
const cascade = resolveCascade(
|
|
1056
|
+
const cascade = resolveCascade([ctx.css], ctx.body);
|
|
915
1057
|
for (const [el, styles] of cascade) {
|
|
916
1058
|
const colorStyle = styles["color"];
|
|
917
1059
|
const bgStyle = styles["background-color"] || styles["background"];
|
|
@@ -937,7 +1079,7 @@ See ${designMdPath} for color tokens, ${DESIGNER_GUIDE} for usage guide.`;
|
|
|
937
1079
|
findings.push({
|
|
938
1080
|
severity: "warning",
|
|
939
1081
|
message: `Contrast ratio ${ratio.toFixed(1)}:1 below WCAG AA (${threshold}:1) for color "${colorStyle.value}" on background "${bgStyle.value}"`,
|
|
940
|
-
suggestion:
|
|
1082
|
+
suggestion: "Darken text color or lighten background to meet minimum contrast",
|
|
941
1083
|
element: `<${tag}>`
|
|
942
1084
|
});
|
|
943
1085
|
}
|
|
@@ -945,99 +1087,187 @@ See ${designMdPath} for color tokens, ${DESIGNER_GUIDE} for usage guide.`;
|
|
|
945
1087
|
return findings;
|
|
946
1088
|
};
|
|
947
1089
|
const ADD_MENU_OUTPUT_REL = "agent-kit/aiditor/add-menu/design-system.yaml";
|
|
948
|
-
function
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
title: "Color palette",
|
|
957
|
-
category: "Design System",
|
|
958
|
-
subCategory: "Colors",
|
|
959
|
-
prompt: `Apply colors from the project design system.
|
|
960
|
-
Available color tokens: ${palette}.
|
|
961
|
-
Use the exact token values from DESIGN.md. Do not use hardcoded colors outside this palette.`
|
|
962
|
-
}
|
|
963
|
-
];
|
|
1090
|
+
function categoryName(designMdPath, projectRoot, tokens) {
|
|
1091
|
+
if (tokens.name)
|
|
1092
|
+
return tokens.name;
|
|
1093
|
+
const rel = path.relative(projectRoot, designMdPath);
|
|
1094
|
+
if (rel === "DESIGN.md")
|
|
1095
|
+
return "Design System";
|
|
1096
|
+
const dir = path.dirname(rel).replace(/^src\/pages\/?/, "");
|
|
1097
|
+
return dir ? `Design System (${dir})` : "Design System";
|
|
964
1098
|
}
|
|
965
|
-
function
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1099
|
+
function esc(s) {
|
|
1100
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1101
|
+
}
|
|
1102
|
+
function buildColorItems(tokens, category) {
|
|
1103
|
+
return Object.entries(tokens.colors).map(([name, value]) => ({
|
|
1104
|
+
id: `design-system:color-${name}`,
|
|
1105
|
+
title: `${name} (${value})`,
|
|
1106
|
+
category,
|
|
1107
|
+
subCategory: "Colors",
|
|
1108
|
+
prompt: `Use color token "${name}" with value ${value} from DESIGN.md.`,
|
|
1109
|
+
html: `<div style="display:flex;align-items:center;gap:10px;font-family:sans-serif;">
|
|
1110
|
+
<div style="width:40px;height:40px;border-radius:6px;background:${esc(value)};border:1px solid rgba(0,0,0,0.1);flex-shrink:0;"></div>
|
|
1111
|
+
<div>
|
|
1112
|
+
<div style="font-size:13px;font-weight:600;">${esc(name)}</div>
|
|
1113
|
+
<div style="font-size:11px;color:#888;font-family:monospace;">${esc(value)}</div>
|
|
1114
|
+
</div>
|
|
1115
|
+
</div>`
|
|
1116
|
+
}));
|
|
1117
|
+
}
|
|
1118
|
+
function buildTypographyItems(tokens, category) {
|
|
1119
|
+
return Object.entries(tokens.typography).map(([name, t]) => {
|
|
1120
|
+
const parts = [];
|
|
971
1121
|
if (t.fontFamily)
|
|
972
1122
|
parts.push(t.fontFamily);
|
|
973
1123
|
if (t.fontSize)
|
|
974
1124
|
parts.push(t.fontSize);
|
|
975
1125
|
if (t.fontWeight)
|
|
976
1126
|
parts.push(`weight ${t.fontWeight}`);
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1127
|
+
if (t.lineHeight)
|
|
1128
|
+
parts.push(`line-height ${t.lineHeight}`);
|
|
1129
|
+
if (t.letterSpacing)
|
|
1130
|
+
parts.push(`letter-spacing ${t.letterSpacing}`);
|
|
1131
|
+
const desc = parts.join(", ");
|
|
1132
|
+
const styles = [];
|
|
1133
|
+
if (t.fontFamily)
|
|
1134
|
+
styles.push(`font-family:${t.fontFamily},sans-serif`);
|
|
1135
|
+
if (t.fontSize)
|
|
1136
|
+
styles.push(`font-size:${t.fontSize}`);
|
|
1137
|
+
if (t.fontWeight)
|
|
1138
|
+
styles.push(`font-weight:${t.fontWeight}`);
|
|
1139
|
+
if (t.lineHeight)
|
|
1140
|
+
styles.push(`line-height:${t.lineHeight}`);
|
|
1141
|
+
if (t.letterSpacing)
|
|
1142
|
+
styles.push(`letter-spacing:${t.letterSpacing}`);
|
|
1143
|
+
return {
|
|
1144
|
+
id: `design-system:typography-${name}`,
|
|
1145
|
+
title: name,
|
|
1146
|
+
category,
|
|
984
1147
|
subCategory: "Typography",
|
|
985
|
-
prompt: `Apply typography
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1148
|
+
prompt: `Apply typography preset "${name}" from DESIGN.md: ${desc}.`,
|
|
1149
|
+
html: `<div style="font-family:sans-serif;">
|
|
1150
|
+
<div style="${styles.join(";")};margin:0;">The quick brown fox</div>
|
|
1151
|
+
<div style="font-size:10px;color:#888;margin-top:6px;font-family:monospace;">${esc(name)}: ${esc(desc)}</div>
|
|
1152
|
+
</div>`
|
|
1153
|
+
};
|
|
1154
|
+
});
|
|
990
1155
|
}
|
|
991
|
-
function buildSpacingItems(tokens) {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
{
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1156
|
+
function buildSpacingItems(tokens, category) {
|
|
1157
|
+
return Object.entries(tokens.spacing).map(([name, value]) => ({
|
|
1158
|
+
id: `design-system:spacing-${name}`,
|
|
1159
|
+
title: `${name} (${value})`,
|
|
1160
|
+
category,
|
|
1161
|
+
subCategory: "Spacing",
|
|
1162
|
+
prompt: `Use spacing token "${name}" with value ${value} from DESIGN.md for padding, margin, or gap.`,
|
|
1163
|
+
html: `<div style="display:flex;align-items:center;gap:8px;font-family:sans-serif;">
|
|
1164
|
+
<div style="display:flex;align-items:center;gap:0;">
|
|
1165
|
+
<div style="width:16px;height:24px;background:#cbd5e1;border-radius:2px 0 0 2px;"></div>
|
|
1166
|
+
<div style="width:${esc(value)};height:24px;background:repeating-linear-gradient(45deg,#e0f2fe,#e0f2fe 2px,#bae6fd 2px,#bae6fd 4px);"></div>
|
|
1167
|
+
<div style="width:16px;height:24px;background:#cbd5e1;border-radius:0 2px 2px 0;"></div>
|
|
1168
|
+
</div>
|
|
1169
|
+
<div>
|
|
1170
|
+
<span style="font-size:12px;font-weight:600;">${esc(name)}</span>
|
|
1171
|
+
<span style="font-size:11px;color:#888;font-family:monospace;margin-left:4px;">${esc(value)}</span>
|
|
1172
|
+
</div>
|
|
1173
|
+
</div>`
|
|
1174
|
+
}));
|
|
1007
1175
|
}
|
|
1008
|
-
function buildRoundedItems(tokens) {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
{
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1176
|
+
function buildRoundedItems(tokens, category) {
|
|
1177
|
+
return Object.entries(tokens.rounded).map(([name, value]) => ({
|
|
1178
|
+
id: `design-system:rounded-${name}`,
|
|
1179
|
+
title: `${name} (${value})`,
|
|
1180
|
+
category,
|
|
1181
|
+
subCategory: "Rounded",
|
|
1182
|
+
prompt: `Use border-radius token "${name}" with value ${value} from DESIGN.md.`,
|
|
1183
|
+
html: `<div style="display:flex;align-items:center;gap:10px;font-family:sans-serif;">
|
|
1184
|
+
<div style="width:40px;height:40px;border-radius:${esc(value)};background:#e2e8f0;border:1.5px solid #94a3b8;flex-shrink:0;"></div>
|
|
1185
|
+
<div>
|
|
1186
|
+
<div style="font-size:12px;font-weight:600;">${esc(name)}</div>
|
|
1187
|
+
<div style="font-size:11px;color:#888;font-family:monospace;">${esc(value)}</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
</div>`
|
|
1190
|
+
}));
|
|
1191
|
+
}
|
|
1192
|
+
function buildBreakpointItems(tokens, category) {
|
|
1193
|
+
return Object.entries(tokens.breakpoints).map(([name, value]) => ({
|
|
1194
|
+
id: `design-system:breakpoint-${name}`,
|
|
1195
|
+
title: `${name} (${value})`,
|
|
1196
|
+
category,
|
|
1197
|
+
subCategory: "Breakpoints",
|
|
1198
|
+
prompt: `Use breakpoint "${name}" at max-width ${value} from DESIGN.md: @media (max-width: ${value}) { ... }`
|
|
1199
|
+
}));
|
|
1200
|
+
}
|
|
1201
|
+
function buildAnimationItems(tokens, category) {
|
|
1202
|
+
return Object.entries(tokens.animations).map(([name, preset]) => {
|
|
1203
|
+
const parts = [];
|
|
1204
|
+
if (preset.duration)
|
|
1205
|
+
parts.push(preset.duration);
|
|
1206
|
+
if (preset.easing)
|
|
1207
|
+
parts.push(preset.easing);
|
|
1208
|
+
const desc = parts.join(", ");
|
|
1209
|
+
const dur = preset.duration || "300ms";
|
|
1210
|
+
const ease = preset.easing || "ease";
|
|
1211
|
+
return {
|
|
1212
|
+
id: `design-system:animation-${name}`,
|
|
1213
|
+
title: name,
|
|
1214
|
+
category,
|
|
1215
|
+
subCategory: "Animations",
|
|
1216
|
+
prompt: `Use animation preset "${name}" from DESIGN.md: ${desc}. Apply to transition-duration and transition-timing-function.`,
|
|
1217
|
+
html: `<div style="font-family:sans-serif;">
|
|
1218
|
+
<style>
|
|
1219
|
+
@scope {
|
|
1220
|
+
.anim-preview { width:40px;height:40px;border-radius:6px;background:#8b5cf6;transition:transform ${esc(dur)} ${esc(ease)};cursor:pointer; }
|
|
1221
|
+
.anim-preview:hover { transform:scale(1.3); }
|
|
1022
1222
|
}
|
|
1023
|
-
|
|
1223
|
+
</style>
|
|
1224
|
+
<div style="display:flex;align-items:center;gap:10px;">
|
|
1225
|
+
<div class="anim-preview"></div>
|
|
1226
|
+
<div>
|
|
1227
|
+
<div style="font-size:12px;font-weight:600;">${esc(name)}</div>
|
|
1228
|
+
<div style="font-size:11px;color:#888;font-family:monospace;">${esc(desc)}</div>
|
|
1229
|
+
<div style="font-size:10px;color:#aaa;">hover to preview</div>
|
|
1230
|
+
</div>
|
|
1231
|
+
</div>
|
|
1232
|
+
</div>`
|
|
1233
|
+
};
|
|
1234
|
+
});
|
|
1024
1235
|
}
|
|
1025
|
-
function buildComponentItems(tokens) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
const
|
|
1236
|
+
function buildComponentItems(tokens, category) {
|
|
1237
|
+
return Object.entries(tokens.components).map(([name, spec]) => {
|
|
1238
|
+
const rawSpec = tokens.rawComponents[name];
|
|
1239
|
+
const props = Object.entries(spec).map(([prop, value]) => {
|
|
1240
|
+
const raw = rawSpec?.[prop];
|
|
1241
|
+
return raw && raw !== value ? `${prop}: ${raw} (${value})` : `${prop}: ${value}`;
|
|
1242
|
+
}).join(", ");
|
|
1243
|
+
const previewStyles = [];
|
|
1244
|
+
const propertyMapping = {
|
|
1245
|
+
backgroundColor: "background-color",
|
|
1246
|
+
textColor: "color",
|
|
1247
|
+
rounded: "border-radius",
|
|
1248
|
+
padding: "padding",
|
|
1249
|
+
borderColor: "border-color"
|
|
1250
|
+
};
|
|
1251
|
+
for (const [prop, value] of Object.entries(spec)) {
|
|
1252
|
+
if (prop === "typography")
|
|
1253
|
+
continue;
|
|
1254
|
+
const cssProp = propertyMapping[prop] || prop;
|
|
1255
|
+
previewStyles.push(`${cssProp}:${value}`);
|
|
1256
|
+
}
|
|
1257
|
+
if (!spec.padding)
|
|
1258
|
+
previewStyles.push("padding:8px 16px");
|
|
1259
|
+
if (spec.borderColor)
|
|
1260
|
+
previewStyles.push("border:1.5px solid " + spec.borderColor);
|
|
1033
1261
|
return {
|
|
1034
1262
|
id: `design-system:component-${name}`,
|
|
1035
|
-
title,
|
|
1036
|
-
category
|
|
1263
|
+
title: name,
|
|
1264
|
+
category,
|
|
1037
1265
|
subCategory: "Components",
|
|
1038
|
-
prompt: `Apply the "${name}" component spec from
|
|
1039
|
-
|
|
1040
|
-
|
|
1266
|
+
prompt: `Apply the "${name}" component spec from DESIGN.md. Required styles: ${props}. The design-system validator will flag mismatches.`,
|
|
1267
|
+
html: `<div style="font-family:sans-serif;">
|
|
1268
|
+
<div style="${previewStyles.join(";")};display:inline-block;font-size:13px;min-width:80px;text-align:center;">${esc(name)}</div>
|
|
1269
|
+
<div style="font-size:10px;color:#888;margin-top:6px;font-family:monospace;">${esc(props)}</div>
|
|
1270
|
+
</div>`
|
|
1041
1271
|
};
|
|
1042
1272
|
});
|
|
1043
1273
|
}
|
|
@@ -1061,10 +1291,10 @@ function findAllDesignMdFiles(projectRoot) {
|
|
|
1061
1291
|
}
|
|
1062
1292
|
return files;
|
|
1063
1293
|
}
|
|
1064
|
-
async function
|
|
1294
|
+
async function generateDesignSystemAgentKit(ctx) {
|
|
1065
1295
|
const designMdFiles = findAllDesignMdFiles(ctx.projectRoot);
|
|
1066
1296
|
if (designMdFiles.length === 0) {
|
|
1067
|
-
return {
|
|
1297
|
+
return { agentKitCreated: [], message: "No DESIGN.md found in project" };
|
|
1068
1298
|
}
|
|
1069
1299
|
const allItems = [];
|
|
1070
1300
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1073,12 +1303,15 @@ async function generateDesignSystemReferences(ctx) {
|
|
|
1073
1303
|
const tokens = parseDesignMd(content);
|
|
1074
1304
|
if (!tokens)
|
|
1075
1305
|
continue;
|
|
1306
|
+
const category = categoryName(filePath, ctx.projectRoot, tokens);
|
|
1076
1307
|
const items = [
|
|
1077
|
-
...buildColorItems(tokens),
|
|
1078
|
-
...buildTypographyItems(tokens),
|
|
1079
|
-
...buildSpacingItems(tokens),
|
|
1080
|
-
...buildRoundedItems(tokens),
|
|
1081
|
-
...
|
|
1308
|
+
...buildColorItems(tokens, category),
|
|
1309
|
+
...buildTypographyItems(tokens, category),
|
|
1310
|
+
...buildSpacingItems(tokens, category),
|
|
1311
|
+
...buildRoundedItems(tokens, category),
|
|
1312
|
+
...buildBreakpointItems(tokens, category),
|
|
1313
|
+
...buildAnimationItems(tokens, category),
|
|
1314
|
+
...buildComponentItems(tokens, category)
|
|
1082
1315
|
];
|
|
1083
1316
|
for (const item of items) {
|
|
1084
1317
|
if (!seen.has(item.id)) {
|
|
@@ -1088,18 +1321,18 @@ async function generateDesignSystemReferences(ctx) {
|
|
|
1088
1321
|
}
|
|
1089
1322
|
}
|
|
1090
1323
|
if (allItems.length === 0) {
|
|
1091
|
-
return {
|
|
1324
|
+
return { agentKitCreated: [], message: "DESIGN.md found but no tokens defined" };
|
|
1092
1325
|
}
|
|
1093
1326
|
const outputPath = path.join(ctx.projectRoot, ADD_MENU_OUTPUT_REL);
|
|
1094
1327
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
1095
1328
|
fs.writeFileSync(outputPath, yaml.dump({ items: allItems }, { lineWidth: 120 }), "utf-8");
|
|
1096
1329
|
return {
|
|
1097
|
-
|
|
1098
|
-
message: `Generated ${allItems.length} add-menu items from DESIGN.md
|
|
1330
|
+
agentKitCreated: [ADD_MENU_OUTPUT_REL],
|
|
1331
|
+
message: `Generated ${allItems.length} add-menu items from ${designMdFiles.length} DESIGN.md file(s)`
|
|
1099
1332
|
};
|
|
1100
1333
|
}
|
|
1101
1334
|
export {
|
|
1102
|
-
|
|
1335
|
+
generateDesignSystemAgentKit,
|
|
1103
1336
|
validateComponents,
|
|
1104
1337
|
validateContrast,
|
|
1105
1338
|
validateStructure,
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/design-system-validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Design system validation plugin for Jay Framework — validates jay-html CSS against DESIGN.md tokens",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
|
-
"plugin.yaml"
|
|
10
|
+
"plugin.yaml",
|
|
11
|
+
"agent-kit"
|
|
11
12
|
],
|
|
12
13
|
"exports": {
|
|
13
14
|
".": "./dist/index.js",
|
|
@@ -25,15 +26,15 @@
|
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"@csstools/selector-specificity": "^5.0.0",
|
|
28
|
-
"@jay-framework/compiler-shared": "^0.
|
|
29
|
-
"@jay-framework/stack-server-runtime": "^0.
|
|
29
|
+
"@jay-framework/compiler-shared": "^0.22.0",
|
|
30
|
+
"@jay-framework/stack-server-runtime": "^0.22.0",
|
|
30
31
|
"js-yaml": "^4.1.0",
|
|
31
32
|
"postcss": "^8.5.6",
|
|
32
33
|
"postcss-selector-parser": "^7.1.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@jay-framework/dev-environment": "^0.
|
|
36
|
-
"@jay-framework/jay-stack-cli": "^0.
|
|
36
|
+
"@jay-framework/dev-environment": "^0.22.0",
|
|
37
|
+
"@jay-framework/jay-stack-cli": "^0.22.0",
|
|
37
38
|
"@types/js-yaml": "^4.0.9",
|
|
38
39
|
"@types/node": "^22.15.21",
|
|
39
40
|
"node-html-parser": "^6.1.0",
|
package/plugin.yaml
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
name: design-system-validator
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
description: Generate AIditor add-menu entries from DESIGN.md tokens
|
|
3
|
+
agentkit: generateDesignSystemAgentKit
|
|
4
|
+
description: Generate AIditor add-menu entries from DESIGN.md tokens
|
|
6
5
|
|
|
7
6
|
validators:
|
|
8
7
|
- name: design-tokens
|