css-is-awesome 1.13.2 → 1.14.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.
- package/CHANGELOG.md +14 -0
- package/mcp/server.cjs +27 -0
- package/package.json +2 -1
- package/scripts/audit-pairs.json +25 -0
- package/scripts/theme-validator.js +33 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.14.1](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.14.0...v1.14.1) (2026-09-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **package:** ship scripts/audit-pairs.json — the a11y module was broken on every real install ([d7e18ef](https://github.com/Jerry2d3d/css-is-awesome/commit/d7e18efdabfe8eaa9e722f1ac68e4b874d45077b))
|
|
7
|
+
|
|
8
|
+
# [1.14.0](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.13.2...v1.14.0) (2026-09-11)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **mcp:** add validate_theme tool — the real validator, exposed as a tool call ([7a8b497](https://github.com/Jerry2d3d/css-is-awesome/commit/7a8b4971640f6fd6b13148f05288e9e3e76ade10))
|
|
14
|
+
|
|
1
15
|
## [1.13.2](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.13.1...v1.13.2) (2026-09-11)
|
|
2
16
|
|
|
3
17
|
|
package/mcp/server.cjs
CHANGED
|
@@ -685,6 +685,19 @@ const handlers = {
|
|
|
685
685
|
return { total: matches.length, items: matches };
|
|
686
686
|
},
|
|
687
687
|
|
|
688
|
+
// Validates ANY theme CSS against the real token contract + a11y audit —
|
|
689
|
+
// not scoped to cia's own shipped themes. Works on cia's themes, a fully
|
|
690
|
+
// custom one built by an agent (e.g. via the derive-theme assemble_prompt
|
|
691
|
+
// intent), anything — whatever CSS text is passed in. Reuses the same
|
|
692
|
+
// validateText() the CLI (`npm run validate-themes`) calls, so a pass/fail
|
|
693
|
+
// here is exactly what `node scripts/theme-validator.js` would report,
|
|
694
|
+
// not a reimplementation that could quietly drift from it.
|
|
695
|
+
validate_theme({ css, label } = {}) {
|
|
696
|
+
if (!css || typeof css !== 'string') throw new Error('validate_theme: css is required');
|
|
697
|
+
const { validateText, loadContract } = require(path.join(SCRIPTS_DIR, 'theme-validator.js'));
|
|
698
|
+
return validateText(css, loadContract(), { label: label || undefined });
|
|
699
|
+
},
|
|
700
|
+
|
|
688
701
|
// ─── Mixins ────────────────────────────────────────────────────────────
|
|
689
702
|
|
|
690
703
|
list_mixins({ category, component, limit = 500, offset = 0 } = {}) {
|
|
@@ -1327,6 +1340,20 @@ async function startServer() {
|
|
|
1327
1340
|
},
|
|
1328
1341
|
}, async (a) => ok(handlers.search_themes(a || {})));
|
|
1329
1342
|
|
|
1343
|
+
server.registerTool('validate_theme', {
|
|
1344
|
+
description:
|
|
1345
|
+
'Validate ANY theme CSS against cia\'s real token contract and WCAG contrast audit — the same check ' +
|
|
1346
|
+
'`npm run validate-themes` runs, exposed as a tool call. Not scoped to cia\'s own themes: works on a ' +
|
|
1347
|
+
'fully custom theme you (or another agent) just built, e.g. via the derive-theme assemble_prompt intent. ' +
|
|
1348
|
+
'Pass compiled CSS (a :root or [data-theme="..."] block) — this does not compile Sass, so give it the ' +
|
|
1349
|
+
'output, not .scss source. Returns missing required tokens (if any, mode "per-file" or "consolidated" ' +
|
|
1350
|
+
'depending on shape) and a11y warnings per contrast pair.',
|
|
1351
|
+
inputSchema: {
|
|
1352
|
+
css: z.string().describe('Compiled theme CSS to validate — the :root/[data-theme] block(s), not .scss source.'),
|
|
1353
|
+
label: z.string().optional().describe('Optional name for the result (e.g. the intended theme name); purely cosmetic.'),
|
|
1354
|
+
},
|
|
1355
|
+
}, async (a) => ok(handlers.validate_theme(a || {})));
|
|
1356
|
+
|
|
1330
1357
|
// Mixins
|
|
1331
1358
|
server.registerTool('list_mixins', {
|
|
1332
1359
|
description: `List all ${MIXIN_COUNT} public @mixins across core, layout, animation, icons, generator, per-component and recipe sources. Filter by category (core/layout/animation/icons/generator/component/recipe) or component name.`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "css-is-awesome",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.1",
|
|
4
4
|
"description": "A token-driven SCSS design system with light/dark theming, semantic color tokens, and a 800+ LOC mixin API.",
|
|
5
5
|
"homepage": "https://github.com/Jerry2d3d/css-is-awesome#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"scripts/theme-contract.json",
|
|
71
71
|
"scripts/theme-validator.js",
|
|
72
72
|
"scripts/theme-a11y.js",
|
|
73
|
+
"scripts/audit-pairs.json",
|
|
73
74
|
"scripts/icon-validator.js",
|
|
74
75
|
"scripts/icon-contract.json",
|
|
75
76
|
"bin",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "fg": "--text-primary", "bg": "--paper", "required": 4.5, "kind": "text", "note": "primary body text on the page background" },
|
|
3
|
+
{ "fg": "--text-secondary", "bg": "--paper", "required": 4.5, "kind": "text", "note": "secondary body text on the page background" },
|
|
4
|
+
{ "fg": "--text-tertiary", "bg": "--paper", "required": 3.0, "kind": "large-text", "warnIfBelow": 4.5, "note": "tertiary text - legal at large sizes; warn if used as body" },
|
|
5
|
+
{ "fg": "--text-muted", "bg": "--paper", "required": 4.5, "kind": "text", "note": "muted UI text on the page background" },
|
|
6
|
+
{ "fg": "--ink", "bg": "--paper", "required": 4.5, "kind": "text", "note": "native ink on native paper" },
|
|
7
|
+
{ "fg": "--ink-soft", "bg": "--paper", "required": 4.5, "kind": "text", "note": "soft ink on native paper" },
|
|
8
|
+
{ "fg": "--ink-faint", "bg": "--paper", "required": 3.0, "kind": "large-text", "warnIfBelow": 4.5, "note": "faint ink - large/UI only; warn if used as body" },
|
|
9
|
+
{ "fg": "--text-link", "bg": "--paper", "required": 4.5, "kind": "text", "note": "inline link color on the page background" },
|
|
10
|
+
{ "fg": "--text-inverse", "bg": "--action-primary-default", "required": 4.5, "kind": "text", "note": "button label on the primary action surface" },
|
|
11
|
+
{ "fg": "--text-inverse", "bg": "--ai", "required": 4.5, "kind": "text", "note": "inverse text on the AI/brand accent surface" },
|
|
12
|
+
{ "fg": "--success-text", "bg": "--success-subtle", "required": 4.5, "kind": "text", "note": "success message text on its subtle background" },
|
|
13
|
+
{ "fg": "--warning-text", "bg": "--warning-subtle", "required": 4.5, "kind": "text", "note": "warning message text on its subtle background" },
|
|
14
|
+
{ "fg": "--error-text", "bg": "--error-subtle", "required": 4.5, "kind": "text", "note": "error message text on its subtle background" },
|
|
15
|
+
{ "fg": "--info-text", "bg": "--info-subtle", "required": 4.5, "kind": "text", "note": "info message text on its subtle background" },
|
|
16
|
+
{ "fg": "--border-default", "bg": "--paper", "required": 3.0, "kind": "non-text", "decorative": true, "note": "default border against the page background - decorative; WCAG 2.2 SC 1.4.11 applies only to graphical objects essential for understanding content" },
|
|
17
|
+
{ "fg": "--border-focus", "bg": "--paper", "required": 3.0, "kind": "non-text", "note": "focus ring against the page background" },
|
|
18
|
+
{ "fg": "--shu", "bg": "--paper", "required": 3.0, "kind": "non-text", "note": "seal accent - must read as a UI mark on paper" },
|
|
19
|
+
|
|
20
|
+
{ "fg": "--code-ink", "bg": "--code-bg", "required": 4.5, "kind": "text", "note": "default code text on the code surface" },
|
|
21
|
+
{ "fg": "--code-muted", "bg": "--code-bg", "required": 4.5, "kind": "text", "note": "code comments on the code surface - read as prose, so full text contrast" },
|
|
22
|
+
{ "fg": "--code-accent", "bg": "--code-bg", "required": 4.5, "kind": "text", "note": "keyword / accent token on the code surface" },
|
|
23
|
+
{ "fg": "--code-blue", "bg": "--code-bg", "required": 4.5, "kind": "text", "note": "blue syntax token on the code surface" },
|
|
24
|
+
{ "fg": "--code-green", "bg": "--code-bg", "required": 4.5, "kind": "text", "note": "green syntax token (strings) on the code surface" }
|
|
25
|
+
]
|
|
@@ -338,15 +338,22 @@ function validateTokenSet(declared, contract) {
|
|
|
338
338
|
}
|
|
339
339
|
|
|
340
340
|
// -----------------------------------------------------------
|
|
341
|
-
// Validate
|
|
341
|
+
// Validate raw CSS text. Auto-detects per-file vs consolidated.
|
|
342
342
|
// Returns a unified result shape with either a flat pass/fail
|
|
343
343
|
// (per-file) or a list of per-theme pass/fails (consolidated).
|
|
344
|
+
//
|
|
345
|
+
// `options.label` names the result (shown as `result.file`) and, in the
|
|
346
|
+
// legacy per-file branch, seeds the a11y report's inferred theme name —
|
|
347
|
+
// there's no real file path when validating text handed over directly
|
|
348
|
+
// (e.g. from an MCP tool call), so callers that have one should pass it
|
|
349
|
+
// as label; callers that don't get '(pasted CSS)'.
|
|
344
350
|
// -----------------------------------------------------------
|
|
345
|
-
function
|
|
351
|
+
function validateText(text, contract, options) {
|
|
346
352
|
const opts = options || {};
|
|
347
353
|
const wantA11y = opts.a11y !== false;
|
|
354
|
+
const label = opts.label || '(pasted CSS)';
|
|
348
355
|
const result = {
|
|
349
|
-
file:
|
|
356
|
+
file: label,
|
|
350
357
|
mode: 'per-file',
|
|
351
358
|
ok: false,
|
|
352
359
|
declaredCount: 0,
|
|
@@ -356,14 +363,6 @@ function validateFile(filePath, contract, options) {
|
|
|
356
363
|
error: null,
|
|
357
364
|
};
|
|
358
365
|
|
|
359
|
-
let text;
|
|
360
|
-
try {
|
|
361
|
-
text = fs.readFileSync(filePath, 'utf8');
|
|
362
|
-
} catch (err) {
|
|
363
|
-
result.error = err.message;
|
|
364
|
-
return result;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
366
|
if (isConsolidated(text)) {
|
|
368
367
|
result.mode = 'consolidated';
|
|
369
368
|
const blocks = extractDataThemeBlocks(text);
|
|
@@ -409,12 +408,33 @@ function validateFile(filePath, contract, options) {
|
|
|
409
408
|
result.missing = v.missing;
|
|
410
409
|
result.ok = v.ok;
|
|
411
410
|
if (wantA11y) {
|
|
412
|
-
const inferredName = path.basename(path.dirname(
|
|
411
|
+
const inferredName = label !== '(pasted CSS)' ? (path.basename(path.dirname(label)) || path.basename(label, '.css')) : 'theme';
|
|
413
412
|
result.a11y = a11y.auditThemeTokens({ name: inferredName, values: root.values });
|
|
414
413
|
}
|
|
415
414
|
return result;
|
|
416
415
|
}
|
|
417
416
|
|
|
417
|
+
// Reads a file and delegates to validateText, with the file path as the
|
|
418
|
+
// result's label (and a11y's inferred-name source in the legacy branch).
|
|
419
|
+
function validateFile(filePath, contract, options) {
|
|
420
|
+
let text;
|
|
421
|
+
try {
|
|
422
|
+
text = fs.readFileSync(filePath, 'utf8');
|
|
423
|
+
} catch (err) {
|
|
424
|
+
return {
|
|
425
|
+
file: filePath,
|
|
426
|
+
mode: 'per-file',
|
|
427
|
+
ok: false,
|
|
428
|
+
declaredCount: 0,
|
|
429
|
+
missing: [],
|
|
430
|
+
themes: null,
|
|
431
|
+
a11y: null,
|
|
432
|
+
error: err.message,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
return validateText(text, contract, Object.assign({}, options, { label: filePath }));
|
|
436
|
+
}
|
|
437
|
+
|
|
418
438
|
// -----------------------------------------------------------
|
|
419
439
|
// Reporter
|
|
420
440
|
// -----------------------------------------------------------
|
|
@@ -665,6 +685,7 @@ module.exports = {
|
|
|
665
685
|
extractDataThemeBlocks,
|
|
666
686
|
isConsolidated,
|
|
667
687
|
validateFile,
|
|
688
|
+
validateText,
|
|
668
689
|
loadContract,
|
|
669
690
|
a11y: a11y,
|
|
670
691
|
};
|