css-is-awesome 1.19.0 → 1.19.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/AGENTS.md CHANGED
@@ -365,7 +365,7 @@ Either way it exposes **33 tools** across 8 families:
365
365
  into a complete theme.css — every required token the file lacks inherits from a shipped base theme (`--base`,
366
366
  default boilerplate), unmapped paths pass through verbatim and are reported, a `--dark` file or paired
367
367
  `color-light`/`color-dark` groups become `light-dark()`, and the validator + WCAG audit run before anything is
368
- written. Same function as the MCP `theme_from_tokens` tool. Run any verb with `--help`. (`cia init` remains
368
+ written. Same function as the MCP `theme_from_tokens` tool; `npx cia theme map [--json] [--path <token.path>]` prints the design-token → cia-token mapping that verb applies (same data as the MCP `get_token_map` tool), for anyone who needs to map one token name the way the converter would. Run any verb with `--help`. (`cia init` remains
369
369
  planned.)
370
370
  - **JSON token export** — Tokens Studio-format sample in `figma-tokens/tokens.json`; it round-trips through `cia theme from-tokens` (paired light/dark groups → one `light-dark()` theme).
371
371
  - **`llm.txt`** — at the repo root and served from the docs site; single-fetch
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.19.1](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.19.0...v1.19.1) (2026-09-23)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **cli:** paired light/dark modes carried only colours; export the in-process entry point ([f1fe71f](https://github.com/Jerry2d3d/css-is-awesome/commit/f1fe71f8a91a8e7bf59e0b9e830585701beb9160))
9
+
10
+
11
+ ### Features
12
+
13
+ * **site:** cross-link the posts that explain a browser-support row (B6.4) ([44f40d8](https://github.com/Jerry2d3d/css-is-awesome/commit/44f40d8bf278113a81f7f2b643ad52390fb1af25))
14
+
3
15
  # [1.19.0](https://github.com/Jerry2d3d/css-is-awesome/compare/v1.18.0...v1.19.0) (2026-09-19)
4
16
 
5
17
 
package/README.md CHANGED
@@ -417,7 +417,7 @@ Full detail: [`/docs/testing`](https://cssisawesome.com/docs/testing/).
417
417
 
418
418
  **Stable, [published on npm](https://www.npmjs.com/package/css-is-awesome)** (first published 2026-09-01). The mixin API, functions, token contract, and theme architecture are stable and under strict SemVer — breaking changes require a major bump. See [`VERSIONING.md`](./VERSIONING.md) for the policy.
419
419
 
420
- The 1.0 surface is the v0.8 mixin-first reframe — twelve mixin renames, theme system collapsed to 8 single-file theme families, six zero-JS components, intrinsic-layout vocabulary, opt-in utilities — plus the recipes book, the Tailwind/Bootstrap migration on-ramp, print/PDF support, and the 33-tool MCP server (now also available zero-install via the companion [`css-is-awesome-mcp`](https://www.npmjs.com/package/css-is-awesome-mcp) package). The npm package ships ZERO runtime JavaScript by hard rule — nothing in it is loaded by a page; its Node tooling (CLI, MCP server, validators) never reaches the browser.
420
+ The 1.0 surface is the v0.8 mixin-first reframe — twelve mixin renames, theme system collapsed to 8 single-file theme families, six zero-JS components, intrinsic-layout vocabulary, opt-in utilities — plus the recipes book, the Tailwind/Bootstrap migration on-ramp, print/PDF support, the in-browser [Playground](https://cssisawesome.com/playground/), the design-tokens on-ramp (`npx cia theme from-tokens` / `theme map`), and the 33-tool MCP server (now also available zero-install via the companion [`css-is-awesome-mcp`](https://www.npmjs.com/package/css-is-awesome-mcp) package). The npm package ships ZERO runtime JavaScript by hard rule — nothing in it is loaded by a page; its Node tooling (CLI, MCP server, validators) never reaches the browser.
421
421
 
422
422
  See [CHANGELOG.md](./CHANGELOG.md) for the full history and [MIGRATION.md](./MIGRATION.md) for the v0.7 → v0.8 and v0.8 → v1.0 upgrade paths.
423
423
 
package/dist/tokens.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- // Generated from scripts/theme-contract.json on 2026-09-19.
1
+ // Generated from scripts/theme-contract.json on 2026-09-23.
2
2
  // Do not edit by hand. Run `npm run build:token-types` to regenerate.
3
3
 
4
4
  /** Every CSS custom property cia themes are required to declare. */
package/llm.txt CHANGED
@@ -198,4 +198,4 @@ Humans first. AI's role is to compose, not to lead the pitch.
198
198
 
199
199
  ---
200
200
 
201
- *Generated 2026-05-21 for cia v0.8. Updated 2026-05-23 for the v1.0 architecture lock, 2026-08-30 for the theme single-source rework (24 themes, 127+36 contract, themeable spacing, `:root, :root[data-theme]` selector), and 2026-09-04 for the shipped recipes count (5), the mobile-nav mixin family, and the dropdown popover guard. Update when the API surface changes.*
201
+ *Generated 2026-05-21 for cia v0.8. Updated 2026-05-23 for the v1.0 architecture lock, 2026-08-30 for the theme single-source rework (24 themes, 127+36 contract, themeable spacing, `:root, :root[data-theme]` selector), 2026-09-04 for the shipped recipes count (5), the mobile-nav mixin family, and the dropdown popover guard, and 2026-09-19 for the 32-recipe book, 33-tool MCP, contract 1.2 (127 + 41 in 10 features), the playground and the design-tokens on-ramp. Update when the API surface changes.*
package/mcp/server.cjs CHANGED
@@ -34,13 +34,13 @@
34
34
  * "mcpServers": {
35
35
  * "css-is-awesome": {
36
36
  * "command": "node",
37
- * "args": ["K:/Repo/css-is-awesome/mcp/server.cjs"]
37
+ * "args": ["node_modules/css-is-awesome/mcp/server.cjs"]
38
38
  * }
39
39
  * }
40
40
  * }
41
41
  *
42
- * Aligned with the canonical sibling MCP shape: ui-ux-builder, ideas-master,
43
- * video-maker. Response envelope is `{ total, items }` for list/search;
42
+ * Aligned with the canonical sibling MCP shape used across our other servers.
43
+ * Response envelope is `{ total, items }` for list/search;
44
44
  * get_* tools return the full record.
45
45
  */
46
46
 
@@ -1402,7 +1402,8 @@ async function startServer() {
1402
1402
  '{ "--token": value } map. Format is auto-detected. Every REQUIRED contract token the file does not supply ' +
1403
1403
  'is inherited from a shipped base theme (default boilerplate) and listed in report.inherited, so the output ' +
1404
1404
  'is always contract-complete; unmapped paths are emitted verbatim and listed in report.unmapped, never ' +
1405
- 'dropped. Pass `dark` (same format) or a single file with paired color-light/color-dark groups to get ' +
1405
+ 'dropped. Pass `dark` (same format), or one file with paired top-level groups — `light`/`dark` (each a full '
1406
+ + 'token set) or `color-light`/`color-dark` (each a colour set) — to get ' +
1406
1407
  'light-dark() values. Returns { css, report, validation } — validation is the same result validate_theme ' +
1407
1408
  'gives, run on the CSS before you write it anywhere.',
1408
1409
  inputSchema: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "css-is-awesome",
3
- "version": "1.19.0",
3
+ "version": "1.19.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": {
@@ -41,6 +41,10 @@
41
41
  "./scss/main": "./scss/main.scss",
42
42
  "./scss/tokens": "./scss/tokens.scss",
43
43
  "./scss/mixins": "./scss/_mixins.scss",
44
+ "./scripts/tokens-to-theme.cjs": "./scripts/tokens-to-theme.cjs",
45
+ "./scripts/tokens-to-theme": "./scripts/tokens-to-theme.cjs",
46
+ "./scripts/theme-validator.js": "./scripts/theme-validator.js",
47
+ "./mcp/server.cjs": "./mcp/server.cjs",
44
48
  "./scss/generator": "./scss/_generator.scss",
45
49
  "./scss/utilities": "./scss/_utilities.scss",
46
50
  "./scss/animations": "./scss/_animations.scss",
@@ -48,8 +48,11 @@
48
48
  // LIGHT + DARK
49
49
  // Pass `dark` (a second tokens object, same format) and every colour token
50
50
  // that differs becomes `light-dark(light, dark)` with `color-scheme: light dark`.
51
- // A single Tokens Studio file with paired top-level groups (`color-light` +
52
- // `color-dark`, or `light` + `dark`) is split the same way automatically.
51
+ // A single file with paired top-level groups is split automatically, and the
52
+ // two conventions mean different things: `color-light`/`color-dark` name a
53
+ // COLOUR SET (children are colours), while `light`/`dark` name a MODE whose
54
+ // group holds a FULL token set — colours, spacing, fonts, component
55
+ // overrides — which merges beside any groups shared outside the pair.
53
56
  // Without a dark side the block is single-mode: `color-scheme: <mode>`
54
57
  // (`mode`, default light).
55
58
  // ============================================================================
@@ -490,6 +493,17 @@ function applyPathAliases(p) {
490
493
 
491
494
  /** Returns { token, mapped: true } or { token, mapped: false } (verbatim fallback). */
492
495
  function mapPath(tokenPath, contract) {
496
+ if (typeof tokenPath !== 'string' || !tokenPath.trim()) {
497
+ throw new Error('mapPath: first argument must be a non-empty token path, e.g. "color.text.primary"');
498
+ }
499
+ // The contract is optional: omit it and the installed one is loaded. It used
500
+ // to be required, but only the generic-rule branch touched it — so a path in
501
+ // the explicit table appeared to work and the mistake surfaced later as a
502
+ // TypeError on some other path. Fail clearly or not at all.
503
+ if (contract == null) contract = loadContract(path.join(__dirname, '..'));
504
+ else if (!contract.all || typeof contract.all.has !== 'function') {
505
+ throw new Error('mapPath: second argument must be a contract from loadContract(); omit it to load the installed contract automatically');
506
+ }
493
507
  if (TOKEN_MAP[tokenPath]) return { token: TOKEN_MAP[tokenPath], mapped: true };
494
508
  const aliased = applyPathAliases(tokenPath);
495
509
  if (TOKEN_MAP[aliased]) return { token: TOKEN_MAP[aliased], mapped: true };
@@ -498,6 +512,26 @@ function mapPath(tokenPath, contract) {
498
512
  // camelCase segments → kebab (linkHover → link-hover), one more try.
499
513
  const kebab = `--${aliased.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/\./g, '-').toLowerCase()}`;
500
514
  if (contract.all.has(kebab)) return { token: kebab, mapped: true };
515
+ // Flat per-component form: `component.btn-radius` → `--btn-radius`. The
516
+ // explicit table carries the nested shape (`component.button.radius`), and
517
+ // it is tried first; this catches exporters that flatten the group instead.
518
+ // Generic-rule first means this can never shadow a real `--component-*`.
519
+ const flat = aliased.replace(/^components?\./, '');
520
+ if (flat !== aliased) {
521
+ const body = flat.replace(/\./g, '-').toLowerCase();
522
+ // `component.btn-radius` → `--btn-radius`.
523
+ if (contract.all.has(`--${body}`)) return { token: `--${body}`, mapped: true };
524
+ // The contract names per-component overrides two ways: radius trails the
525
+ // component (`--card-radius`) but shadow, border and duration lead it
526
+ // (`--shadow-card`). A flat exporter cannot know which, so try the swap:
527
+ // `card-shadow` → `--shadow-card`, `button-hover-duration` →
528
+ // `--duration-button-hover`. Only accepted if it is a real token.
529
+ const cut = body.lastIndexOf('-');
530
+ if (cut > 0) {
531
+ const swapped = `--${body.slice(cut + 1)}-${body.slice(0, cut)}`;
532
+ if (contract.all.has(swapped)) return { token: swapped, mapped: true };
533
+ }
534
+ }
501
535
  return { token: generic, mapped: false };
502
536
  }
503
537
 
@@ -530,16 +564,35 @@ function loadBase(ciaRoot, base) {
530
564
  // ---------------------------------------------------------------------------
531
565
  // Paired-mode detection for a single Tokens Studio / DTCG file
532
566
  // ---------------------------------------------------------------------------
533
- const PAIRS = [['color-light', 'color-dark'], ['light', 'dark'], ['colors-light', 'colors-dark']];
567
+ // Two different conventions look alike and must NOT be treated alike:
568
+ //
569
+ // `color-light` / `color-dark` name a COLOUR SET. The group's children are
570
+ // colours (`color-light.brand.primary`), so they nest under `color.`.
571
+ // `light` / `dark` name a MODE. The group holds a FULL token
572
+ // set — `color.*`, `spacing.*`, `font.*`, `component.*` — so its
573
+ // children merge at the TOP level, beside any shared groups.
574
+ //
575
+ // Wrapping a mode group in `color.` (which this did for every pair until
576
+ // 2026-09-19) silently mis-routed every non-colour group: `spacing.unit`
577
+ // became `color.spacing.unit` → `--spacing-unit`, so the theme lost its
578
+ // density knob while `validation.ok` stayed true, because the required token
579
+ // was quietly inherited from the base theme instead. Reported by a consumer
580
+ // against boilerplate's canonical DTCG layout.
581
+ const PAIRS = [
582
+ { light: 'color-light', dark: 'color-dark', wrap: 'color' },
583
+ { light: 'colors-light', dark: 'colors-dark', wrap: 'color' },
584
+ { light: 'light', dark: 'dark', wrap: null },
585
+ ];
534
586
 
535
587
  function splitPairedModes(tokens) {
536
588
  if (!isPlainObject(tokens)) return null;
537
- for (const [l, d] of PAIRS) {
589
+ for (const { light: l, dark: d, wrap } of PAIRS) {
538
590
  if (isPlainObject(tokens[l]) && isPlainObject(tokens[d])) {
539
591
  const rest = {};
540
592
  for (const [k, v] of Object.entries(tokens)) if (k !== l && k !== d) rest[k] = v;
541
- const light = { ...rest, color: tokens[l] };
542
- const dark = { ...rest, color: tokens[d] };
593
+ // Mode-specific groups win over shared ones on a key collision.
594
+ const light = wrap ? { ...rest, [wrap]: tokens[l] } : { ...rest, ...tokens[l] };
595
+ const dark = wrap ? { ...rest, [wrap]: tokens[d] } : { ...rest, ...tokens[d] };
543
596
  return { light, dark, groups: [l, d] };
544
597
  }
545
598
  }