@jay-framework/design-system-validator 0.23.1 → 0.24.1

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.
@@ -97,7 +97,7 @@ Use the token values directly in your `<style>` blocks:
97
97
  }
98
98
  ```
99
99
 
100
- CSS custom properties (`var(--name)`) are resolved statically when defined in `:root`. Unresolvable vars are accepted without validation.
100
+ CSS custom properties (`var(--name)`) are resolved statically when defined in the page's CSS (inline `<style>` or linked local stylesheets). Variables that are used but never defined are flagged as warnings.
101
101
 
102
102
  ## Breakpoints
103
103
 
@@ -132,12 +132,18 @@ The `components` section defines expected styles for HTML elements (matched by c
132
132
 
133
133
  When a value intentionally breaks the design system, exempt it:
134
134
 
135
- **In CSS** — add a comment after the declaration:
135
+ **In CSS** — add a comment on the same line or the line immediately after the declaration:
136
136
 
137
137
  ```css
138
138
  .hero {
139
139
  padding: 7.5rem 0; /* design-system: allow */
140
140
  }
141
+
142
+ /* or on the next line: */
143
+ .hero {
144
+ padding: 7.5rem 0;
145
+ /* design-system: allow */
146
+ }
141
147
  ```
142
148
 
143
149
  **On elements** — add the `jay-design="allow"` attribute:
@@ -213,6 +219,14 @@ These are the errors the validator produces. Use them to align your CSS with the
213
219
  Suggestion: Use a DESIGN.md breakpoint: mobile (600px), tablet (768px), desktop (1024px)
214
220
  ```
215
221
 
222
+ **Undefined CSS variable:**
223
+
224
+ ```
225
+ ⚠ CSS variable "--color-surface-tint" is used but never defined
226
+ Suggestion: Define --color-surface-tint in a :root block, or replace with a DESIGN.md token value directly.
227
+ To suppress: add /* design-system: allow */ on the same line or the line after the declaration.
228
+ ```
229
+
216
230
  **Low contrast:**
217
231
 
218
232
  ```
package/dist/index.d.ts CHANGED
@@ -15,6 +15,8 @@ declare const validateContrast: JayHtmlValidatorFn;
15
15
 
16
16
  declare const validateFontFallbacks: JayHtmlValidatorFn;
17
17
 
18
+ declare const validateUndefinedVars: JayHtmlValidatorFn;
19
+
18
20
  interface FontFallbackInput {
19
21
  primary: string;
20
22
  fallback: string;
@@ -106,4 +108,4 @@ declare const regenerateDesignSystemAddMenu: _jay_framework_fullstack_component.
106
108
  message: any;
107
109
  }, []>;
108
110
 
109
- export { ADD_MENU_GENERATED_REL, fontFallback, generateDesignSystemAgentKit, getDesignSystemSettingsStatus, loadDesignSystemAddMenuCatalog, regenerateDesignSystemAddMenu, runDesignSystemAnalysisAction as runDesignSystemAnalysis, saveDesignSystemAddMenuCatalog, validateComponents, validateContrast, validateFontFallbacks, validateStructure, validateTokens };
111
+ export { ADD_MENU_GENERATED_REL, fontFallback, generateDesignSystemAgentKit, getDesignSystemSettingsStatus, loadDesignSystemAddMenuCatalog, regenerateDesignSystemAddMenu, runDesignSystemAnalysisAction as runDesignSystemAnalysis, saveDesignSystemAddMenuCatalog, validateComponents, validateContrast, validateFontFallbacks, validateStructure, validateTokens, validateUndefinedVars };
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ function findDesignMd(filePath, projectRoot) {
91
91
  }
92
92
  return null;
93
93
  }
94
- const ALLOW_COMMENT = "design-system: allow";
94
+ const ALLOW_COMMENT$1 = "design-system: allow";
95
95
  function computeSpecificity(selector) {
96
96
  let result = [0, 0, 0];
97
97
  selectorParser((selectors) => {
@@ -103,15 +103,15 @@ function computeSpecificity(selector) {
103
103
  }).processSync(selector);
104
104
  return result;
105
105
  }
106
- function hasAllowComment(decl) {
106
+ function hasAllowComment$1(decl) {
107
107
  const next = decl.next();
108
- if (next?.type === "comment" && next.text.trim() === ALLOW_COMMENT) {
108
+ if (next?.type === "comment" && next.text.trim() === ALLOW_COMMENT$1) {
109
109
  return true;
110
110
  }
111
111
  const raw = decl.raws.value;
112
112
  if (raw && typeof raw === "object" && "raw" in raw) {
113
113
  const rawStr = raw.raw;
114
- if (rawStr.includes(`/*${ALLOW_COMMENT}*/`) || rawStr.includes(`/* ${ALLOW_COMMENT} */`)) {
114
+ if (rawStr.includes(`/*${ALLOW_COMMENT$1}*/`) || rawStr.includes(`/* ${ALLOW_COMMENT$1} */`)) {
115
115
  return true;
116
116
  }
117
117
  }
@@ -148,7 +148,7 @@ function parseRules(css, sourceIndexOffset = 0, customProperties) {
148
148
  property: decl.prop,
149
149
  value: decl.value,
150
150
  important: decl.important,
151
- allowed: hasAllowComment(decl)
151
+ allowed: hasAllowComment$1(decl)
152
152
  });
153
153
  });
154
154
  const mediaQuery = getMediaQuery(rule);
@@ -1181,6 +1181,62 @@ const validateFontFallbacks = (ctx) => {
1181
1181
  }
1182
1182
  return findings;
1183
1183
  };
1184
+ const ALLOW_COMMENT = "design-system: allow";
1185
+ const VAR_REGEX = /var\(\s*(--[a-zA-Z0-9-]+)(?:\s*,\s*([^)]+))?\s*\)/g;
1186
+ function hasAllowComment(decl) {
1187
+ const next = decl.next();
1188
+ if (next?.type === "comment" && next.text.trim() === ALLOW_COMMENT) {
1189
+ return true;
1190
+ }
1191
+ const raw = decl.raws.value;
1192
+ if (raw && typeof raw === "object" && "raw" in raw) {
1193
+ const rawStr = raw.raw;
1194
+ if (rawStr.includes(`/*${ALLOW_COMMENT}*/`) || rawStr.includes(`/* ${ALLOW_COMMENT} */`)) {
1195
+ return true;
1196
+ }
1197
+ }
1198
+ return false;
1199
+ }
1200
+ const validateUndefinedVars = (ctx) => {
1201
+ if (!ctx.css) return [];
1202
+ const findings = [];
1203
+ const definedVars = /* @__PURE__ */ new Set();
1204
+ const usedVars = /* @__PURE__ */ new Map();
1205
+ const root = postcss.parse(ctx.css);
1206
+ root.walkDecls((decl) => {
1207
+ if (decl.prop.startsWith("--")) {
1208
+ definedVars.add(decl.prop);
1209
+ }
1210
+ if (decl.value.includes("var(") && !hasAllowComment(decl)) {
1211
+ const selector = decl.parent && "selector" in decl.parent ? decl.parent.selector : "[unknown]";
1212
+ let match;
1213
+ VAR_REGEX.lastIndex = 0;
1214
+ while ((match = VAR_REGEX.exec(decl.value)) !== null) {
1215
+ const varName = match[1];
1216
+ if (!usedVars.has(varName)) {
1217
+ usedVars.set(varName, {
1218
+ property: decl.prop,
1219
+ selector,
1220
+ hasFallback: match[2] !== void 0,
1221
+ fallbackValue: match[2]?.trim()
1222
+ });
1223
+ }
1224
+ }
1225
+ }
1226
+ });
1227
+ for (const [varName, usage] of usedVars) {
1228
+ if (definedVars.has(varName)) continue;
1229
+ const fallbackNote = usage.hasFallback ? ` (falls back to "${usage.fallbackValue}")` : "";
1230
+ findings.push({
1231
+ severity: "warning",
1232
+ message: `CSS variable "${varName}" is used but never defined${fallbackNote}`,
1233
+ suggestion: `Define ${varName} in a :root block, or replace with a DESIGN.md token value directly.
1234
+ To suppress: add /* design-system: allow */ on the same line or the line after the declaration.
1235
+ See agent-kit/designer/design-system.md for usage guide.`
1236
+ });
1237
+ }
1238
+ return findings;
1239
+ };
1184
1240
  async function loadMetrics(familyName) {
1185
1241
  const camelCase = fontFamilyToCamelCase(familyName);
1186
1242
  try {
@@ -1823,5 +1879,6 @@ export {
1823
1879
  validateContrast,
1824
1880
  validateFontFallbacks,
1825
1881
  validateStructure,
1826
- validateTokens
1882
+ validateTokens,
1883
+ validateUndefinedVars
1827
1884
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/design-system-validator",
3
- "version": "0.23.1",
3
+ "version": "0.24.1",
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",
@@ -36,21 +36,21 @@
36
36
  "@capsizecss/metrics": "^4.2.0",
37
37
  "@capsizecss/unpack": "^4.0.1",
38
38
  "@csstools/selector-specificity": "^5.0.0",
39
- "@jay-framework/compiler-jay-html": "^0.23.1",
40
- "@jay-framework/compiler-shared": "^0.23.1",
41
- "@jay-framework/component": "^0.23.1",
42
- "@jay-framework/fullstack-component": "^0.23.1",
43
- "@jay-framework/plugin-validator": "^0.23.1",
44
- "@jay-framework/stack-server-runtime": "^0.23.1",
39
+ "@jay-framework/compiler-jay-html": "^0.24.1",
40
+ "@jay-framework/compiler-shared": "^0.24.1",
41
+ "@jay-framework/component": "^0.24.1",
42
+ "@jay-framework/fullstack-component": "^0.24.1",
43
+ "@jay-framework/plugin-validator": "^0.24.1",
44
+ "@jay-framework/stack-server-runtime": "^0.24.1",
45
45
  "js-yaml": "^4.1.0",
46
46
  "postcss": "^8.5.6",
47
47
  "postcss-selector-parser": "^7.1.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@jay-framework/compiler-jay-stack": "^0.23.1",
51
- "@jay-framework/dev-environment": "^0.23.1",
52
- "@jay-framework/jay-cli": "^0.23.1",
53
- "@jay-framework/jay-stack-cli": "^0.23.1",
50
+ "@jay-framework/compiler-jay-stack": "^0.24.1",
51
+ "@jay-framework/dev-environment": "^0.24.1",
52
+ "@jay-framework/jay-cli": "^0.24.1",
53
+ "@jay-framework/jay-stack-cli": "^0.24.1",
54
54
  "@types/js-yaml": "^4.0.9",
55
55
  "@types/node": "^22.15.21",
56
56
  "node-html-parser": "^6.1.0",
package/plugin.yaml CHANGED
@@ -39,3 +39,7 @@ validators:
39
39
  - name: font-fallbacks
40
40
  handler: validateFontFallbacks
41
41
  description: Detects web fonts loaded via @font-face URL without a metric-matched local fallback (causes CLS)
42
+
43
+ - name: undefined-vars
44
+ handler: validateUndefinedVars
45
+ description: Flags CSS var(--name) references that are never defined as custom properties