From 8ed1f4145d506beccac5497f848a479513eec1ce Mon Sep 17 00:00:00 2001 From: dreamwasp Date: Mon, 10 Aug 2026 09:19:24 -0400 Subject: [PATCH 1/2] spike(GMT-1715): engine-neutral DTCG tokens via Style Dictionary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Go/no-go criterion #3 from the reboot recommendation §5, and the only one of the four that had never been spiked. It matters because §3 recommends tokens move to an engine-neutral pipeline while every other spike has Panda generating tokens directly — the opposite of the stated goal. It works. From the real Core theme, exported to DTCG and built with Style Dictionary: palette colours 68 byte-identical to what Gamut emits today semantic aliases 32 per mode x 2 modes, as var() references token names 137 match keyof theme[scale] exactly engine-neutrality 139 tokens identical across CSS / TS / Panda preset The Panda preset is generated FROM the tokens rather than being where they live, so swapping the styling engine no longer touches the token pipeline. Aliases emit var(--color-x) rather than inlined hex, so colour mode still works by variable reassignment and stays a x1 multiplier. Findings, all in the README: - DTCG has no concept of modes. Each mode is a separate token file merged at build time, so the mode dimension lives in build config rather than in the tokens — fine while Gamut owns the build, not fine if tokens are handed to a third party expecting modes to travel with them. - spacing[0] can't round-trip: DTCG dimensions require a unit, so bare 0 returns as "0px". keyof is unaffected, so type safety holds; only the value type shifts. Recorded as an explicit accepted deviation so new ones fail. - Style Dictionary's TS formats can't express Gamut's token names (navy-800, 0, 400 aren't valid JS identifiers). name/camel would rename them, which parity forbids, so a custom format emits a quoted-key as-const object. - borders isn't expressible: a CSS shorthand embedding a var() ref to a mode-dependent semantic colour. Decomposing it is the real fix. style-dictionary pinned to 5.5.0 rather than ^5.5.1 to respect the repo's npmMinimalAgeGate of 7d. Adds spikes/* to root workspaces, which main lacks. Co-Authored-By: Claude Opus 5 --- package.json | 3 +- spikes/dtcg-tokens-poc/.gitignore | 3 + spikes/dtcg-tokens-poc/README.md | 157 ++++ spikes/dtcg-tokens-poc/build.mjs | 220 +++++ spikes/dtcg-tokens-poc/export-dtcg.mjs | 189 +++++ spikes/dtcg-tokens-poc/package.json | 17 + .../tokens/.unexpressible.json | 12 + spikes/dtcg-tokens-poc/tokens/dimension.json | 256 ++++++ spikes/dtcg-tokens-poc/tokens/palette.json | 276 ++++++ .../dtcg-tokens-poc/tokens/semantic.dark.json | 132 +++ .../tokens/semantic.light.json | 132 +++ spikes/dtcg-tokens-poc/verify.mjs | 216 +++++ yarn.lock | 784 +++++++++++++++--- 13 files changed, 2272 insertions(+), 125 deletions(-) create mode 100644 spikes/dtcg-tokens-poc/.gitignore create mode 100644 spikes/dtcg-tokens-poc/README.md create mode 100644 spikes/dtcg-tokens-poc/build.mjs create mode 100644 spikes/dtcg-tokens-poc/export-dtcg.mjs create mode 100644 spikes/dtcg-tokens-poc/package.json create mode 100644 spikes/dtcg-tokens-poc/tokens/.unexpressible.json create mode 100644 spikes/dtcg-tokens-poc/tokens/dimension.json create mode 100644 spikes/dtcg-tokens-poc/tokens/palette.json create mode 100644 spikes/dtcg-tokens-poc/tokens/semantic.dark.json create mode 100644 spikes/dtcg-tokens-poc/tokens/semantic.light.json create mode 100644 spikes/dtcg-tokens-poc/verify.mjs diff --git a/package.json b/package.json index c09a7f053d..30a4df4575 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,8 @@ }, "workspaces": { "packages": [ - "packages/*" + "packages/*", + "spikes/*" ] } } diff --git a/spikes/dtcg-tokens-poc/.gitignore b/spikes/dtcg-tokens-poc/.gitignore new file mode 100644 index 0000000000..6f3a6e4495 --- /dev/null +++ b/spikes/dtcg-tokens-poc/.gitignore @@ -0,0 +1,3 @@ +.tmp/ +dist/ +node_modules diff --git a/spikes/dtcg-tokens-poc/README.md b/spikes/dtcg-tokens-poc/README.md new file mode 100644 index 0000000000..9675451cac --- /dev/null +++ b/spikes/dtcg-tokens-poc/README.md @@ -0,0 +1,157 @@ +# dtcg-tokens-poc + +GMT-1715 spike. **Question: can Gamut's tokens live as engine-neutral W3C DTCG +JSON, with Style Dictionary emitting CSS variables and TS types at parity with +what Gamut emits today?** + +This is **go/no-go criterion #3** from `reboot-recommendation.md` §5 — the one that +had never been spiked. It matters because §3 of that doc recommends tokens move to +an _engine-neutral_ pipeline, while every other spike so far has **Panda** +generating tokens directly. That coupling is the opposite of the stated goal, so +either this works or the recommendation needs amending. + +```bash +yarn all # tokens → build → verify +``` + +Reads the **real** Core theme, bundled from `packages/gamut-styles/src/themes/core.ts`. +Nothing is retyped, so it tracks the source of truth. + +## Verdict: it works, and Panda becomes a consumer rather than the owner + +``` +1. CSS VARIABLE PARITY vs what Gamut emits today + ✓ palette colours — 68 checked + ✓ semantic aliases (light) — 32 checked + ✓ semantic aliases (dark) — 32 checked +2. VALUE ROUND-TRIP through DTCG + ✓ scale values — 37 checked (1 accepted deviation) +3. NAME PARITY (drives the generated union types) + ✓ token names match keyof theme[scale] — 137 checked +4. ENGINE-NEUTRALITY — three consumers, one source + ✓ token set identical across CSS / TS / Panda — 139 checked +``` + +**Three consumers from one DTCG source**, all verified to carry the same 139 tokens: + +| output | consumer | notes | +| ------------------------------ | ---------- | --------------------------------------------------------------- | +| `dist/tokens.{light,dark}.css` | any engine | `--color-navy-800: #1F4056`, aliases as `var(--color-navy-800)` | +| `dist/tokens.ts` | TypeScript | `as const` object + `keyof`-derived union types | +| `dist/panda-preset.mjs` | Panda | `definePreset`-shaped, values as `var(--…)` | + +That last row is the point: **Panda reads the tokens instead of owning them.** +Swapping the styling engine no longer touches the token pipeline. + +Colour-mode behaviour is preserved too — semantic aliases emit +`var(--color-navy-800)` rather than inlined hex (`outputReferences: true`), so a +`data-color-mode` flip re-themes the page by **variable reassignment**. That is what +keeps the nested-`` contract correct (see +`panda-via-gamut-option-a.md` §5) and keeps colour mode a ×1 multiplier on any +prebuilt atomic matrix. + +## The real finding: DTCG has no concept of modes + +The [DTCG format spec (2025.10)](https://www.designtokens.org/tr/drafts/format/) +defines no notion of modes, themes, or multiple values per token — **a token has +exactly one `$value`.** Gamut has 5 themes × 2 colour modes over one palette. + +So each mode is its own token **file** carrying the same token names with different +alias targets, and the build runs once per mode. That is the established workaround +and it produces correct output. But be clear about what it costs: + +> **The mode dimension lives in your build configuration, not in your tokens.** A +> consumer handed the DTCG JSON alone cannot discover that modes exist. + +For Gamut that is acceptable — Gamut owns the build. It is _not_ acceptable if the +goal is handing tokens to a third party (Figma, a partner design system) and +expecting modes to travel with them. Worth deciding which of those you're buying +before committing, because it's the difference between "tokens as data" and "tokens +as data plus a build contract". + +There is no modes or resolver module in the spec as published. + +## Four things found the hard way + +**1. `spacing[0]` cannot round-trip.** DTCG requires `dimension` values to be an +object with a numeric `value` and a `unit` of `px` or `rem`. Gamut's `spacing[0]` +is the bare number `0`, which has no unit — it comes back as `"0px"`. Harmless in +CSS, but it changes the TS value type from the number `0` to the string `'0px'`. +`keyof` is unaffected, so **token type safety is intact**; only the value type +shifts. Recorded as an explicit accepted deviation in `verify.mjs`, so any _new_ +deviation still fails the run. + +**2. Style Dictionary's TypeScript formats can't express Gamut's token names.** +`typescript/es6-declarations` and `javascript/esm` emit `export const `, and +`navy-800`, `0`, `400` are not valid JS identifiers — prettier (bundled inside +Style Dictionary) throws outright. The usual escape is the `name/camel` transform, +but `navy-800` → `navy800` **changes the token's name**, which is exactly what +parity forbids: `keyof theme.colors` has to keep yielding `'navy-800'`. A ~25-line +custom format emitting a quoted-key `as const` object solves it and preserves the +names. `json/flat` survives unchanged because JSON keys are quoted. + +**3. `borders` is not expressible as DTCG, and it's instructive.** +`borders.1` is `'1px solid var(--color-border-primary)'` — a CSS shorthand +embedding a `var()` reference to a **semantic** colour whose resolution is +mode-dependent. DTCG has a `border` composite type (`{ width, style, color }`), but +its `color` member would need one value per mode, landing straight back on the modes +gap. Left out rather than misrepresented. Decomposing it into +width/style/color tokens is the real fix and is a small breaking change for +whoever uses `border={1}`. + +**4. The `css` transform group would have silently changed values.** It bundles +opinionated conversions including px→rem. Since the entire claim here is value +parity, this spike hand-rolls three small transforms instead +(`gamut/dimension`, `gamut/scalar`, `gamut/name`) so the output is predictable. Any +future adoption should resist reaching for the convenience group. + +## A note on the dependency + +`style-dictionary` is pinned to **5.5.0**, not `^5.5.1`. The repo sets +`npmMinimalAgeGate: 7d` in `.yarnrc.yml` — a supply-chain control that refuses +packages published within the last seven days — and 5.5.1 was too recent, so yarn +reported "No candidates found". Pinning to an older release respects the gate +rather than bypassing it. Vetted before install: Apache-2.0, canonical repo +`github.com/style-dictionary/style-dictionary`, ~1.9M weekly downloads, no +install scripts (and `enableScripts: false` is set repo-wide anyway). + +This branch also adds `spikes/*` to the root `workspaces` — `main` has neither a +`spikes/` directory nor that glob; the other spike branch added both. + +## What this does NOT prove + +- **Core theme only.** The other four themes (admin, platform, lxStudio, percipio) + are not exported. They matter: `panda-via-gamut-option-a.md` §6 found Core's + palette is **not** a superset — lxStudio and percipio add their own tokens, and + emitting only Core's left 33 variables dangling and silently unstyled. The + multi-theme dimension is a second axis on top of modes, and DTCG has no more to + say about it. +- **Nothing consumes the output yet.** The Panda preset is generated and its token + set verified, but no build has been run against it. Wiring it into + `emotion-to-gamut-poc`'s `panda.config.ts` in place of that file's inline token + derivation is the obvious next step and would close the loop properly. +- **No `$description`, no `$extensions`, no Figma round-trip.** Token _metadata_ is + a large part of DTCG's value for design-tool interop (`output-formats-rfc.md`) and + is untouched here. +- **`borders` deliberately unresolved**, per finding 3. +- **No DTCG validation.** The output is not checked against a schema validator; + parity is checked against Gamut, which is the question that was asked, but it + isn't the same as being spec-conformant. + +## Sources + +- [DTCG Format Module 2025.10](https://www.designtokens.org/tr/drafts/format/) — + no modes/themes concept; `dimension` values "MUST be an object containing a + numeric `value` … and `unit` of measurement (`'px'` or `'rem'`)"; aliases via + `{group.token}` or `$ref`. +- Style Dictionary 5.5.0, Apache-2.0 — + `github.com/style-dictionary/style-dictionary`. DTCG support via `usesDtcg` + (`node_modules/style-dictionary/types/Config.d.ts:34`). Built-in format list + read from `style-dictionary/enums`. +- Gamut's theme — `packages/gamut-styles/src/themes/core.ts` (palette in + `_variables.root`, semantic aliases in `modes.{light,dark}`). +- The criterion this answers — `~/code/base camp/reboot/reboot-recommendation.md` + §5 item 3, and the coupling problem it addresses is recorded in + `state-of-research.md`. +- Repo supply-chain gate — `.yarnrc.yml` (`npmMinimalAgeGate: 7d`, + `enableScripts: false`). diff --git a/spikes/dtcg-tokens-poc/build.mjs b/spikes/dtcg-tokens-poc/build.mjs new file mode 100644 index 0000000000..fa03e22c58 --- /dev/null +++ b/spikes/dtcg-tokens-poc/build.mjs @@ -0,0 +1,220 @@ +/* DTCG JSON → CSS custom properties + TypeScript types + a Panda preset. + * + * Three consumers from one source is the whole point of "engine-neutral": if the + * Panda preset is generated FROM the tokens rather than being where the tokens + * live, then swapping the styling engine doesn't touch the token pipeline. + * + * One build per colour mode, because DTCG has no mode concept — see the long + * comment in export-dtcg.mjs. + */ +import { mkdirSync } from 'node:fs'; + +import StyleDictionary from 'style-dictionary'; + +mkdirSync('dist', { recursive: true }); + +/* ── transforms ────────────────────────────────────────────────────────────── + * Deliberately NOT using the `css` transform group. It bundles opinionated + * conversions (px→rem among them) that would silently change values, and this + * spike's entire claim is value parity with what Gamut emits today. Hand-rolled + * transforms keep the output predictable. */ + +/* DTCG dimension values are `{ value, unit }` objects, so something has to + * reassemble the CSS string. This is the round-trip `verify.mjs` checks: Gamut's + * `spacing[0]` is the bare number `0`, which becomes `{value:0,unit:'px'}` and + * comes back out as `0px` — equivalent CSS, but not the same string. */ +StyleDictionary.registerTransform({ + name: 'gamut/dimension', + type: 'value', + transitive: true, + filter: (token) => token.$type === 'dimension', + transform: (token) => { + const { value, unit } = token.$value; + return `${value}${unit}`; + }, +}); + +/* Everything else is already a plain scalar — hex, number, font stack. Pass it + * through untouched rather than let a transform group reformat it. */ +StyleDictionary.registerTransform({ + name: 'gamut/scalar', + type: 'value', + transitive: true, + filter: (token) => + ['color', 'number', 'fontWeight', 'fontFamily'].includes(token.$type), + transform: (token) => String(token.$value), +}); + +/* Gamut's variables are `--color-navy-800`, `--spacing-24`: the token path joined + * with `-`. Path segments are already lower-case, so no case munging — which + * matters, because `headerHeight` must stay camel to match + * `--elements-headerHeight`. */ +StyleDictionary.registerTransform({ + name: 'gamut/name', + type: 'name', + transform: (token) => token.path.join('-'), +}); + +/* ── the TypeScript consumer ───────────────────────────────────────────────── + * A custom format, because Style Dictionary's built-in `typescript/es6-declarations` + * and `javascript/esm` cannot express Gamut's token names. Both emit + * `export const `, and Gamut's names are not valid JS identifiers: + * `navy-800`, `0`, `400`, `spacing-24`. Prettier (bundled inside SD) throws + * outright on the generated file. + * + * The usual escape is the `name/camel` transform — but `navy-800` → `navy800` + * changes the token's NAME, and name parity is precisely what "verify parity with + * today's typed tokens" is asking about: `keyof theme.colors` has to keep + * yielding `'navy-800'`. So: an object literal with quoted keys, `as const`, and + * a union type derived from it. */ +StyleDictionary.registerFormat({ + name: 'gamut/ts-tokens', + format: ({ dictionary }) => { + const byCategory = {}; + for (const token of dictionary.allTokens) { + const [category] = token.path; + const key = token.path.slice(1).join('-'); + (byCategory[category] ??= {})[key] = token.$value ?? token.value; + } + + const body = Object.entries(byCategory) + .map(([category, entries]) => { + const pairs = Object.entries(entries) + .map(([k, v]) => ` ${JSON.stringify(k)}: ${JSON.stringify(v)},`) + .join('\n'); + return ` ${JSON.stringify(category)}: {\n${pairs}\n },`; + }) + .join('\n'); + + const unions = Object.keys(byCategory) + .map((category) => { + const type = category[0].toUpperCase() + category.slice(1); + return `export type ${type}Token = keyof typeof tokens[${JSON.stringify( + category + )}];`; + }) + .join('\n'); + + return [ + '// GENERATED from DTCG tokens by build.mjs — do not edit.', + '', + 'export const tokens = {', + body, + '} as const;', + '', + unions, + '', + ].join('\n'); + }, +}); + +/* ── the Panda consumer ────────────────────────────────────────────────────── + * Emits a `definePreset`-shaped object. Values are `var(--x)` references rather + * than raw hex, exactly as Gamut's theme does today — which is what keeps colour + * mode working by variable reassignment instead of duplicated atomic classes. */ +StyleDictionary.registerFormat({ + name: 'gamut/panda-preset', + format: ({ dictionary }) => { + const byCategory = {}; + for (const token of dictionary.allTokens) { + const [category] = token.path; + const key = token.path.slice(1).join('-'); + const bucket = (byCategory[category] ??= {}); + bucket[key] = { value: `var(--${token.name})` }; + } + return `${[ + '// GENERATED from DTCG tokens by build.mjs — do not edit.', + '// One of three consumers of the same source; see README.', + `export const gamutPreset = ${JSON.stringify( + { theme: { tokens: byCategory } }, + null, + 2 + )};`, + '', + ].join('\n')}`; + }, +}); + +/** Where the mode's variables get scoped. Matches Gamut's own selectors. */ +const SELECTOR = { + light: ':root, [data-color-mode=light]', + dark: '[data-color-mode=dark]', +}; + +const transforms = ['gamut/dimension', 'gamut/scalar', 'gamut/name']; + +const buildMode = async (mode) => { + const sd = new StyleDictionary({ + usesDtcg: true, + log: { verbosity: 'silent', warnings: 'disabled' }, + source: [ + 'tokens/palette.json', + 'tokens/dimension.json', + `tokens/semantic.${mode}.json`, + ], + platforms: { + css: { + transforms, + buildPath: 'dist/', + files: [ + { + destination: `tokens.${mode}.css`, + format: 'css/variables', + /* Emit `var(--color-navy-800)` for aliases instead of inlining the + * hex. This is what makes an attribute flip re-theme the page, and + * it's how Gamut's alias blocks already work. */ + options: { outputReferences: true, selector: SELECTOR[mode] }, + }, + ], + }, + }, + }); + await sd.buildAllPlatforms(); +}; + +/* Types and the Panda preset are mode-independent — the NAMES are identical + * across modes, only the values differ — so they're built once, from light. */ +const buildShared = async () => { + const sd = new StyleDictionary({ + usesDtcg: true, + log: { verbosity: 'silent', warnings: 'disabled' }, + source: [ + 'tokens/palette.json', + 'tokens/dimension.json', + 'tokens/semantic.light.json', + ], + platforms: { + ts: { + transforms, + buildPath: 'dist/', + files: [ + { destination: 'tokens.ts', format: 'gamut/ts-tokens' }, + // JSON keys are quoted, so this built-in survives Gamut's token names + { destination: 'flat.json', format: 'json/flat' }, + ], + }, + panda: { + transforms, + buildPath: 'dist/', + files: [ + { destination: 'panda-preset.mjs', format: 'gamut/panda-preset' }, + ], + }, + }, + }); + await sd.buildAllPlatforms(); +}; + +await buildMode('light'); +await buildMode('dark'); +await buildShared(); + +console.log('\nbuilt from tokens/ →'); +for (const f of [ + 'tokens.light.css', + 'tokens.dark.css', + 'tokens.ts', + 'flat.json', + 'panda-preset.mjs', +]) + console.log(` dist/${f}`); diff --git a/spikes/dtcg-tokens-poc/export-dtcg.mjs b/spikes/dtcg-tokens-poc/export-dtcg.mjs new file mode 100644 index 0000000000..7f0c62abb5 --- /dev/null +++ b/spikes/dtcg-tokens-poc/export-dtcg.mjs @@ -0,0 +1,189 @@ +/* Gamut's real Core theme → W3C DTCG token files. + * + * This is the half of the question that Style Dictionary can't answer for us: + * are Gamut's tokens even *expressible* in DTCG? Everything downstream (CSS + * variables, TS types, a Panda preset) is only interesting if this step is + * lossless. + * + * Reads the REAL theme, bundled from source by `yarn theme`. Nothing is retyped. + */ +import { mkdirSync, writeFileSync } from 'node:fs'; + +import { coreTheme } from './.tmp/core.mjs'; + +const OUT = 'tokens'; +mkdirSync(OUT, { recursive: true }); + +/** Things DTCG could not represent without changing their meaning. */ +const unexpressible = []; + +/* ── colours ───────────────────────────────────────────────────────────────── + * `_variables.root` holds the palette as raw hex; `modes.{light,dark}` map + * semantic aliases onto palette token NAMES. So the palette is DTCG values and + * the semantic layer is DTCG aliases — which is exactly the split DTCG is good + * at. */ +const rootVars = Object.entries(coreTheme._variables.root).filter( + ([, value]) => typeof value === 'string' || typeof value === 'number' +); + +const palette = {}; +for (const [name, value] of rootVars) { + if (!name.startsWith('--color-')) continue; + palette[name.replace('--color-', '')] = { $type: 'color', $value: value }; +} + +/* ── dimensions ────────────────────────────────────────────────────────────── + * DTCG 2025.10 requires dimension values to be an OBJECT — `{ value, unit }` + * with unit `px` or `rem` — not the CSS string Gamut stores. So every value has + * to be parsed apart and reassembled downstream, and that round-trip is the + * thing `verify.mjs` checks. */ +const DIMENSION = /^(-?[\d.]+)(px|rem)$/; + +const dimension = (raw, path) => { + // `spacing[0]` is the NUMBER 0, not a string — unitless zero has no unit + if (typeof raw === 'number') + return { $type: 'dimension', $value: { value: raw, unit: 'px' } }; + + const match = DIMENSION.exec(String(raw)); + if (!match) { + unexpressible.push({ path, value: raw, why: 'not a px/rem dimension' }); + return undefined; + } + return { + $type: 'dimension', + $value: { value: Number(match[1]), unit: match[2] }, + }; +}; + +const group = (scale, build) => { + const out = {}; + for (const [key, raw] of Object.entries(scale)) { + const token = build(raw, key); + if (token) out[key] = token; + } + return out; +}; + +const spacing = group(coreTheme.spacing, (raw, key) => + dimension(raw, `spacing.${key}`) +); +const radii = group(coreTheme.borderRadii, (raw, key) => + dimension(raw, `radii.${key}`) +); +const fontSize = group(coreTheme.fontSize, (raw, key) => + dimension(raw, `fontSize.${key}`) +); + +/* `lineHeight` is unitless (1.5) → DTCG `number`. `fontWeight` accepts a number + * or a keyword. Both map cleanly. */ +const lineHeight = group(coreTheme.lineHeight, (raw) => ({ + $type: 'number', + $value: raw, +})); +const fontWeight = group(coreTheme.fontWeight, (raw) => ({ + $type: 'fontWeight', + $value: raw, +})); + +/* DTCG `fontFamily` takes a string or an array of family names. Gamut stores a + * pre-joined CSS stack containing newlines, so it goes in as a single string — + * legal, but it means the value is opaque to any tool that wants the families + * individually. Splitting on comma would drop the exact original string, which + * the parity check would then fail on. */ +const fontFamily = group(coreTheme.fontFamily, (raw) => ({ + $type: 'fontFamily', + $value: raw, +})); + +/* ── the two things that do NOT fit ────────────────────────────────────────── + * `borders`: `'1px solid var(--color-border-primary)'`. DTCG has a `border` + * composite type — `{ width, style, color }` — but Gamut's value embeds a + * `var()` reference to a SEMANTIC colour, whose resolution is mode-dependent. + * As a DTCG composite it would need one value per mode, which lands straight + * back on the modes gap below. Left out rather than misrepresented. + * + * `elements`: `{ headerHeight: 'var(--elements-headerHeight)' }` — the theme + * object holds a reference to a variable it also defines. That indirection is a + * runtime mechanism, not a token value. The raw dimension IS exported (from + * `_variables.root`); the self-reference is not. */ +for (const [key, value] of Object.entries(coreTheme.borders)) + unexpressible.push({ + path: `borders.${key}`, + value, + why: 'CSS shorthand embedding a var() ref to a mode-dependent semantic colour; DTCG `border` is a composite and would need one value per mode', + }); + +const elements = {}; +for (const [name, value] of rootVars) { + if (!name.startsWith('--elements-')) continue; + const key = name.replace('--elements-', ''); + const token = dimension(value, `elements.${key}`); + if (token) elements[key] = token; +} + +/* ── modes ─────────────────────────────────────────────────────────────────── + * THE GAP. DTCG 2025.10 has no concept of modes, themes, or multiple values per + * token — a token has exactly one `$value`. Gamut has 5 themes × 2 colour modes + * over one palette. + * + * So each mode becomes its own token FILE holding the same token names with + * different alias targets, and the build runs once per mode. That is the + * established workaround (it is how Style Dictionary's own multi-brand examples + * work), but note what it costs: the mode dimension lives in your build + * configuration rather than in the tokens, so it does not travel with the token + * file. Any consumer reading the DTCG JSON alone cannot know modes exist. */ +const semanticFor = (mode) => + Object.fromEntries( + Object.entries(coreTheme.modes[mode]).map(([alias, target]) => [ + alias, + { $type: 'color', $value: `{color.${target}}` }, + ]) + ); + +const write = (file, data) => { + writeFileSync(`${OUT}/${file}`, `${JSON.stringify(data, null, 2)}\n`); + return file; +}; + +const written = [ + write('palette.json', { color: palette }), + write('dimension.json', { + spacing, + radii, + fontSize, + lineHeight, + fontWeight, + fontFamily, + elements, + }), + write('semantic.light.json', { color: semanticFor('light') }), + write('semantic.dark.json', { color: semanticFor('dark') }), +]; + +const count = (o) => Object.keys(o).length; + +console.log('\nDTCG export from the real Core theme'); +written.forEach((f) => console.log(` tokens/${f}`)); +console.log( + `\n palette colours ${count(palette)}` + + `\n semantic aliases ${count( + semanticFor('light') + )} per mode × 2 modes` + + `\n spacing ${count(spacing)}` + + `\n radii ${count(radii)}` + + `\n fontSize ${count(fontSize)}` + + `\n lineHeight ${count(lineHeight)}` + + `\n fontWeight ${count(fontWeight)}` + + `\n fontFamily ${count(fontFamily)}` + + `\n elements ${count(elements)}` +); + +if (unexpressible.length) { + console.log(`\n ⚠ ${unexpressible.length} values NOT expressed as DTCG:`); + for (const { path, value, why } of unexpressible) + console.log(` ${path} = ${JSON.stringify(value)}\n → ${why}`); +} +writeFileSync( + 'tokens/.unexpressible.json', + JSON.stringify(unexpressible, null, 2) +); diff --git a/spikes/dtcg-tokens-poc/package.json b/spikes/dtcg-tokens-poc/package.json new file mode 100644 index 0000000000..0b68e7c2b4 --- /dev/null +++ b/spikes/dtcg-tokens-poc/package.json @@ -0,0 +1,17 @@ +{ + "name": "dtcg-tokens-poc", + "description": "GMT-1715 spike: are Gamut's tokens expressible as engine-neutral W3C DTCG, with Style Dictionary emitting CSS vars + TS types at parity? Not published.", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "theme": "../../node_modules/.bin/esbuild ../../packages/gamut-styles/src/themes/core.ts --bundle --format=esm --platform=node --outfile=.tmp/core.mjs --log-level=error", + "tokens": "yarn theme && node export-dtcg.mjs", + "build": "node build.mjs", + "verify": "node verify.mjs", + "all": "yarn tokens && yarn build && yarn verify" + }, + "devDependencies": { + "style-dictionary": "5.5.0" + } +} diff --git a/spikes/dtcg-tokens-poc/tokens/.unexpressible.json b/spikes/dtcg-tokens-poc/tokens/.unexpressible.json new file mode 100644 index 0000000000..42d0cd26e2 --- /dev/null +++ b/spikes/dtcg-tokens-poc/tokens/.unexpressible.json @@ -0,0 +1,12 @@ +[ + { + "path": "borders.1", + "value": "1px solid var(--color-border-primary)", + "why": "CSS shorthand embedding a var() ref to a mode-dependent semantic colour; DTCG `border` is a composite and would need one value per mode" + }, + { + "path": "borders.2", + "value": "2px solid var(--color-border-primary)", + "why": "CSS shorthand embedding a var() ref to a mode-dependent semantic colour; DTCG `border` is a composite and would need one value per mode" + } +] diff --git a/spikes/dtcg-tokens-poc/tokens/dimension.json b/spikes/dtcg-tokens-poc/tokens/dimension.json new file mode 100644 index 0000000000..727cafecea --- /dev/null +++ b/spikes/dtcg-tokens-poc/tokens/dimension.json @@ -0,0 +1,256 @@ +{ + "spacing": { + "0": { + "$type": "dimension", + "$value": { + "value": 0, + "unit": "px" + } + }, + "4": { + "$type": "dimension", + "$value": { + "value": 0.25, + "unit": "rem" + } + }, + "8": { + "$type": "dimension", + "$value": { + "value": 0.5, + "unit": "rem" + } + }, + "12": { + "$type": "dimension", + "$value": { + "value": 0.75, + "unit": "rem" + } + }, + "16": { + "$type": "dimension", + "$value": { + "value": 1, + "unit": "rem" + } + }, + "24": { + "$type": "dimension", + "$value": { + "value": 1.5, + "unit": "rem" + } + }, + "32": { + "$type": "dimension", + "$value": { + "value": 2, + "unit": "rem" + } + }, + "40": { + "$type": "dimension", + "$value": { + "value": 2.5, + "unit": "rem" + } + }, + "48": { + "$type": "dimension", + "$value": { + "value": 3, + "unit": "rem" + } + }, + "64": { + "$type": "dimension", + "$value": { + "value": 4, + "unit": "rem" + } + }, + "96": { + "$type": "dimension", + "$value": { + "value": 6, + "unit": "rem" + } + } + }, + "radii": { + "none": { + "$type": "dimension", + "$value": { + "value": 0, + "unit": "px" + } + }, + "sm": { + "$type": "dimension", + "$value": { + "value": 2, + "unit": "px" + } + }, + "md": { + "$type": "dimension", + "$value": { + "value": 4, + "unit": "px" + } + }, + "lg": { + "$type": "dimension", + "$value": { + "value": 8, + "unit": "px" + } + }, + "xl": { + "$type": "dimension", + "$value": { + "value": 16, + "unit": "px" + } + }, + "full": { + "$type": "dimension", + "$value": { + "value": 999, + "unit": "px" + } + } + }, + "fontSize": { + "14": { + "$type": "dimension", + "$value": { + "value": 0.875, + "unit": "rem" + } + }, + "16": { + "$type": "dimension", + "$value": { + "value": 1, + "unit": "rem" + } + }, + "18": { + "$type": "dimension", + "$value": { + "value": 1.125, + "unit": "rem" + } + }, + "20": { + "$type": "dimension", + "$value": { + "value": 1.25, + "unit": "rem" + } + }, + "22": { + "$type": "dimension", + "$value": { + "value": 1.375, + "unit": "rem" + } + }, + "26": { + "$type": "dimension", + "$value": { + "value": 1.625, + "unit": "rem" + } + }, + "34": { + "$type": "dimension", + "$value": { + "value": 2.125, + "unit": "rem" + } + }, + "44": { + "$type": "dimension", + "$value": { + "value": 2.75, + "unit": "rem" + } + }, + "64": { + "$type": "dimension", + "$value": { + "value": 4, + "unit": "rem" + } + } + }, + "lineHeight": { + "base": { + "$type": "number", + "$value": 1.5 + }, + "spacedTitle": { + "$type": "number", + "$value": 1.3 + }, + "title": { + "$type": "number", + "$value": 1.2 + } + }, + "fontWeight": { + "400": { + "$type": "fontWeight", + "$value": 400 + }, + "700": { + "$type": "fontWeight", + "$value": 700 + }, + "base": { + "$type": "fontWeight", + "$value": 400 + }, + "title": { + "$type": "fontWeight", + "$value": 700 + } + }, + "fontFamily": { + "accent": { + "$type": "fontFamily", + "$value": "\"Suisse\", \"Apercu\", -apple-system, BlinkMacSystemFont,\n\"Segoe UI\", \"Roboto\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\",\n\"Helvetica Neue\", sans-serif" + }, + "base": { + "$type": "fontFamily", + "$value": "\"Apercu\", -apple-system, BlinkMacSystemFont, \"Segoe UI\",\n\"Roboto\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\nsans-serif" + }, + "monospace": { + "$type": "fontFamily", + "$value": "Monaco, Menlo, \"Ubuntu Mono\", \"Droid Sans Mono\", Consolas,\nmonospace" + }, + "system": { + "$type": "fontFamily", + "$value": "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Ubuntu\",\n\"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif" + } + }, + "elements": { + "headerHeight": { + "$type": "dimension", + "$value": { + "value": 4, + "unit": "rem" + } + }, + "headerZ": { + "$type": "dimension", + "$value": { + "value": 15, + "unit": "px" + } + } + } +} diff --git a/spikes/dtcg-tokens-poc/tokens/palette.json b/spikes/dtcg-tokens-poc/tokens/palette.json new file mode 100644 index 0000000000..580a23919c --- /dev/null +++ b/spikes/dtcg-tokens-poc/tokens/palette.json @@ -0,0 +1,276 @@ +{ + "color": { + "beige-100": { + "$type": "color", + "$value": "#FFF0E5" + }, + "blue-0": { + "$type": "color", + "$value": "#F5FCFF" + }, + "blue-100": { + "$type": "color", + "$value": "#D3F2FF" + }, + "blue-300": { + "$type": "color", + "$value": "#66C4FF" + }, + "blue-400": { + "$type": "color", + "$value": "#3388FF" + }, + "blue-500": { + "$type": "color", + "$value": "#1557FF" + }, + "blue-800": { + "$type": "color", + "$value": "#1D2340" + }, + "navy-100": { + "$type": "color", + "$value": "rgba(16,22,47,0.04)" + }, + "navy-200": { + "$type": "color", + "$value": "rgba(16,22,47,0.12)" + }, + "navy-300": { + "$type": "color", + "$value": "rgba(16,22,47,0.28)" + }, + "navy-400": { + "$type": "color", + "$value": "rgba(16,22,47,0.47)" + }, + "navy-500": { + "$type": "color", + "$value": "rgba(16,22,47,0.63)" + }, + "navy-600": { + "$type": "color", + "$value": "rgba(16,22,47,0.75)" + }, + "navy-700": { + "$type": "color", + "$value": "rgba(16,22,47,0.86)" + }, + "navy-800": { + "$type": "color", + "$value": "#10162F" + }, + "navy-900": { + "$type": "color", + "$value": "#0A0D1C" + }, + "green-0": { + "$type": "color", + "$value": "#F5FFE3" + }, + "green-100": { + "$type": "color", + "$value": "#EAFDC6" + }, + "green-400": { + "$type": "color", + "$value": "#AEE938" + }, + "green-700": { + "$type": "color", + "$value": "#008A27" + }, + "green-900": { + "$type": "color", + "$value": "#151C07" + }, + "yellow-0": { + "$type": "color", + "$value": "#FFFAE5" + }, + "yellow-400": { + "$type": "color", + "$value": "#CCA900" + }, + "yellow-500": { + "$type": "color", + "$value": "#FFD300" + }, + "yellow-900": { + "$type": "color", + "$value": "#211B00" + }, + "pink-0": { + "$type": "color", + "$value": "#FFF5FF" + }, + "pink-400": { + "$type": "color", + "$value": "#F966FF" + }, + "red-0": { + "$type": "color", + "$value": "#FBF1F0" + }, + "red-300": { + "$type": "color", + "$value": "#E85D7F" + }, + "red-400": { + "$type": "color", + "$value": "#DC5879" + }, + "red-500": { + "$type": "color", + "$value": "#E91C11" + }, + "red-600": { + "$type": "color", + "$value": "#BE1809" + }, + "red-900": { + "$type": "color", + "$value": "#280503" + }, + "orange-100": { + "$type": "color", + "$value": "#FFE8CC" + }, + "orange-500": { + "$type": "color", + "$value": "#FF8C00" + }, + "hyper-400": { + "$type": "color", + "$value": "#5533FF" + }, + "hyper-500": { + "$type": "color", + "$value": "#3A10E5" + }, + "gray-100": { + "$type": "color", + "$value": "#F5F5F5" + }, + "gray-200": { + "$type": "color", + "$value": "#EEEEEE" + }, + "gray-300": { + "$type": "color", + "$value": "#E0E0E0" + }, + "gray-600": { + "$type": "color", + "$value": "#9E9E9E" + }, + "gray-800": { + "$type": "color", + "$value": "#616161" + }, + "gray-900": { + "$type": "color", + "$value": "#424242" + }, + "white-100": { + "$type": "color", + "$value": "rgba(255,255,255,0.04)" + }, + "white-200": { + "$type": "color", + "$value": "rgba(255,255,255,0.09)" + }, + "white-300": { + "$type": "color", + "$value": "rgba(255,255,255,0.2)" + }, + "white-400": { + "$type": "color", + "$value": "rgba(255,255,255,0.35)" + }, + "white-500": { + "$type": "color", + "$value": "rgba(255,255,255,0.5)" + }, + "white-600": { + "$type": "color", + "$value": "rgba(255,255,255,0.65)" + }, + "white-700": { + "$type": "color", + "$value": "rgba(255,255,255,0.8)" + }, + "beige": { + "$type": "color", + "$value": "#FFF0E5" + }, + "blue": { + "$type": "color", + "$value": "#1557FF" + }, + "green": { + "$type": "color", + "$value": "#008A27" + }, + "hyper": { + "$type": "color", + "$value": "#3A10E5" + }, + "lightBlue": { + "$type": "color", + "$value": "#66C4FF" + }, + "lightGreen": { + "$type": "color", + "$value": "#AEE938" + }, + "navy": { + "$type": "color", + "$value": "#10162F" + }, + "orange": { + "$type": "color", + "$value": "#FF8C00" + }, + "paleBlue": { + "$type": "color", + "$value": "#F5FCFF" + }, + "paleGreen": { + "$type": "color", + "$value": "#F5FFE3" + }, + "palePink": { + "$type": "color", + "$value": "#FFF5FF" + }, + "paleYellow": { + "$type": "color", + "$value": "#FFFAE5" + }, + "pink": { + "$type": "color", + "$value": "#F966FF" + }, + "paleRed": { + "$type": "color", + "$value": "#DC5879" + }, + "red": { + "$type": "color", + "$value": "#E91C11" + }, + "yellow": { + "$type": "color", + "$value": "#FFD300" + }, + "black": { + "$type": "color", + "$value": "#000000" + }, + "white": { + "$type": "color", + "$value": "#ffffff" + } + } +} diff --git a/spikes/dtcg-tokens-poc/tokens/semantic.dark.json b/spikes/dtcg-tokens-poc/tokens/semantic.dark.json new file mode 100644 index 0000000000..7c07e68c7b --- /dev/null +++ b/spikes/dtcg-tokens-poc/tokens/semantic.dark.json @@ -0,0 +1,132 @@ +{ + "color": { + "text": { + "$type": "color", + "$value": "{color.white}" + }, + "text-accent": { + "$type": "color", + "$value": "{color.beige}" + }, + "text-disabled": { + "$type": "color", + "$value": "{color.white-500}" + }, + "text-secondary": { + "$type": "color", + "$value": "{color.white-600}" + }, + "feedback-error": { + "$type": "color", + "$value": "{color.red-300}" + }, + "feedback-success": { + "$type": "color", + "$value": "{color.green-400}" + }, + "feedback-warning": { + "$type": "color", + "$value": "{color.yellow-0}" + }, + "background": { + "$type": "color", + "$value": "{color.navy-800}" + }, + "background-contrast": { + "$type": "color", + "$value": "{color.black}" + }, + "background-current": { + "$type": "color", + "$value": "{color.navy-800}" + }, + "background-primary": { + "$type": "color", + "$value": "{color.navy-900}" + }, + "background-selected": { + "$type": "color", + "$value": "{color.white-100}" + }, + "background-disabled": { + "$type": "color", + "$value": "{color.white-200}" + }, + "background-hover": { + "$type": "color", + "$value": "{color.white-200}" + }, + "background-success": { + "$type": "color", + "$value": "{color.green-900}" + }, + "background-warning": { + "$type": "color", + "$value": "{color.yellow-900}" + }, + "background-error": { + "$type": "color", + "$value": "{color.red-900}" + }, + "shadow-primary": { + "$type": "color", + "$value": "{color.white}" + }, + "shadow-secondary": { + "$type": "color", + "$value": "{color.white-600}" + }, + "primary": { + "$type": "color", + "$value": "{color.yellow-500}" + }, + "primary-hover": { + "$type": "color", + "$value": "{color.yellow-400}" + }, + "primary-inverse": { + "$type": "color", + "$value": "{color.hyper-500}" + }, + "secondary": { + "$type": "color", + "$value": "{color.white}" + }, + "secondary-hover": { + "$type": "color", + "$value": "{color.white-700}" + }, + "danger": { + "$type": "color", + "$value": "{color.red-300}" + }, + "danger-hover": { + "$type": "color", + "$value": "{color.red-400}" + }, + "interface": { + "$type": "color", + "$value": "{color.yellow-500}" + }, + "interface-hover": { + "$type": "color", + "$value": "{color.yellow-400}" + }, + "border-primary": { + "$type": "color", + "$value": "{color.white}" + }, + "border-secondary": { + "$type": "color", + "$value": "{color.white-600}" + }, + "border-tertiary": { + "$type": "color", + "$value": "{color.white-300}" + }, + "border-disabled": { + "$type": "color", + "$value": "{color.white-500}" + } + } +} diff --git a/spikes/dtcg-tokens-poc/tokens/semantic.light.json b/spikes/dtcg-tokens-poc/tokens/semantic.light.json new file mode 100644 index 0000000000..98e70bd766 --- /dev/null +++ b/spikes/dtcg-tokens-poc/tokens/semantic.light.json @@ -0,0 +1,132 @@ +{ + "color": { + "text": { + "$type": "color", + "$value": "{color.navy-800}" + }, + "text-accent": { + "$type": "color", + "$value": "{color.navy-900}" + }, + "text-disabled": { + "$type": "color", + "$value": "{color.navy-500}" + }, + "text-secondary": { + "$type": "color", + "$value": "{color.navy-600}" + }, + "feedback-error": { + "$type": "color", + "$value": "{color.red-600}" + }, + "feedback-success": { + "$type": "color", + "$value": "{color.green-700}" + }, + "feedback-warning": { + "$type": "color", + "$value": "{color.yellow}" + }, + "background": { + "$type": "color", + "$value": "{color.white}" + }, + "background-contrast": { + "$type": "color", + "$value": "{color.white}" + }, + "background-current": { + "$type": "color", + "$value": "{color.white}" + }, + "background-primary": { + "$type": "color", + "$value": "{color.beige}" + }, + "background-selected": { + "$type": "color", + "$value": "{color.navy-100}" + }, + "background-disabled": { + "$type": "color", + "$value": "{color.navy-200}" + }, + "background-hover": { + "$type": "color", + "$value": "{color.navy-200}" + }, + "background-success": { + "$type": "color", + "$value": "{color.green-0}" + }, + "background-warning": { + "$type": "color", + "$value": "{color.yellow-0}" + }, + "background-error": { + "$type": "color", + "$value": "{color.red-0}" + }, + "shadow-primary": { + "$type": "color", + "$value": "{color.navy-800}" + }, + "shadow-secondary": { + "$type": "color", + "$value": "{color.navy-600}" + }, + "primary": { + "$type": "color", + "$value": "{color.hyper-500}" + }, + "primary-hover": { + "$type": "color", + "$value": "{color.hyper-400}" + }, + "primary-inverse": { + "$type": "color", + "$value": "{color.yellow-500}" + }, + "secondary": { + "$type": "color", + "$value": "{color.navy-800}" + }, + "secondary-hover": { + "$type": "color", + "$value": "{color.navy-700}" + }, + "danger": { + "$type": "color", + "$value": "{color.red-500}" + }, + "danger-hover": { + "$type": "color", + "$value": "{color.red-600}" + }, + "interface": { + "$type": "color", + "$value": "{color.hyper-500}" + }, + "interface-hover": { + "$type": "color", + "$value": "{color.hyper-400}" + }, + "border-primary": { + "$type": "color", + "$value": "{color.navy-800}" + }, + "border-secondary": { + "$type": "color", + "$value": "{color.navy-600}" + }, + "border-tertiary": { + "$type": "color", + "$value": "{color.navy-300}" + }, + "border-disabled": { + "$type": "color", + "$value": "{color.navy-500}" + } + } +} diff --git a/spikes/dtcg-tokens-poc/verify.mjs b/spikes/dtcg-tokens-poc/verify.mjs new file mode 100644 index 0000000000..e2b09e0f3f --- /dev/null +++ b/spikes/dtcg-tokens-poc/verify.mjs @@ -0,0 +1,216 @@ +/* Parity, which is the whole question. A token pipeline that emits *different* + * values than Gamut emits today is not a migration, it's a redesign. + * + * Four checks: + * 1. CSS variable parity — same names, same values, per colour mode + * 2. Value round-trip — DTCG's dimension object → CSS string, losslessly + * 3. Name/type parity — the generated unions match `keyof theme[scale]` + * 4. Engine-neutrality — all three consumers agree on the token set + */ +import { readFileSync } from 'node:fs'; + +import { coreTheme } from './.tmp/core.mjs'; + +const read = (f) => readFileSync(f, 'utf8'); + +/** `--name: value;` pairs out of a generated stylesheet. */ +const varsIn = (file) => + new Map( + [...read(file).matchAll(/^\s*(--[\w-]+):\s*([^;]+);/gm)].map((m) => [ + m[1], + m[2].trim(), + ]) + ); + +const light = varsIn('dist/tokens.light.css'); +const dark = varsIn('dist/tokens.dark.css'); +const generated = JSON.parse(read('dist/flat.json')); + +/* Deviations that are understood, unavoidable, and accepted. Listing them + * explicitly means a NEW deviation still fails the run, instead of hiding among + * known ones. */ +const EXPECTED_DEVIATIONS = new Map([ + [ + 'spacing.0: gamut 0 vs generated "0px"', + "DTCG requires `dimension` values to carry a unit, so a unitless zero can't survive the round-trip. Harmless in CSS (`0px` === `0`), but it does change the TS value type from the number 0 to the string '0px' — `keyof` is unaffected, so token type safety is intact.", + ], +]); + +let failures = 0; +const accepted = []; + +const report = (label, problems, total) => { + const unexpected = problems.filter((p) => { + if (!EXPECTED_DEVIATIONS.has(p)) return true; + accepted.push(p); + return false; + }); + + if (unexpected.length === 0) { + const note = problems.length + ? ` (${problems.length} accepted deviation${ + problems.length > 1 ? 's' : '' + })` + : ''; + console.log(` ✓ ${label} — ${total} checked${note}`); + return; + } + failures += unexpected.length; + console.log(` ✗ ${label} — ${unexpected.length} of ${total} differ`); + unexpected.slice(0, 6).forEach((p) => console.log(` ${p}`)); + if (unexpected.length > 6) + console.log(` …and ${unexpected.length - 6} more`); +}; + +/* ── 1. CSS variable parity ────────────────────────────────────────────────── + * Gamut's palette lives in `_variables.root` as `--color-x: #hex`. Those must + * come out byte-identical, because consumers' CSS references them by name. */ +console.log('\n1. CSS VARIABLE PARITY vs what Gamut emits today'); + +const gamutRoot = Object.entries(coreTheme._variables.root).filter( + ([, v]) => typeof v === 'string' || typeof v === 'number' +); + +const paletteProblems = []; +let paletteChecked = 0; +for (const [name, expected] of gamutRoot) { + if (!name.startsWith('--color-')) continue; + paletteChecked += 1; + const actual = light.get(name); + if (actual === undefined) paletteProblems.push(`${name} MISSING from output`); + else if (actual !== String(expected)) + paletteProblems.push( + `${name}: gamut '${expected}' vs generated '${actual}'` + ); +} +report('palette colours', paletteProblems, paletteChecked); + +/* Semantic aliases: Gamut's `modes[mode][alias] = paletteTokenName`, and its + * runtime emits `--color-: var(--color-)`. Same shape expected. */ +for (const [mode, vars] of [ + ['light', light], + ['dark', dark], +]) { + const problems = []; + const entries = Object.entries(coreTheme.modes[mode]); + for (const [alias, target] of entries) { + const expected = `var(--color-${target})`; + const actual = vars.get(`--color-${alias}`); + if (actual === undefined) + problems.push(`--color-${alias} MISSING from ${mode}`); + else if (actual !== expected) + problems.push(`--color-${alias}: expected '${expected}' got '${actual}'`); + } + report(`semantic aliases (${mode})`, problems, entries.length); +} + +/* ── 2. value round-trip ───────────────────────────────────────────────────── + * DTCG forces dimensions through `{ value, unit }`, so every dimension is + * decomposed and reassembled. Anything that doesn't survive shows up here. */ +console.log('\n2. VALUE ROUND-TRIP through DTCG'); + +const SCALES = [ + ['spacing', 'spacing'], + ['borderRadii', 'radii'], + ['fontSize', 'fontSize'], + ['lineHeight', 'lineHeight'], + ['fontWeight', 'fontWeight'], + ['fontFamily', 'fontFamily'], +]; + +const roundTrip = []; +let roundTripChecked = 0; +for (const [gamutScale, category] of SCALES) { + for (const [key, expected] of Object.entries(coreTheme[gamutScale])) { + roundTripChecked += 1; + const actual = generated[`${category}-${key}`]; + if (actual === undefined) { + roundTrip.push(`${category}.${key} MISSING`); + continue; + } + if (String(actual) !== String(expected)) + roundTrip.push( + `${category}.${key}: gamut ${JSON.stringify( + expected + )} vs generated ${JSON.stringify(actual)}` + ); + } +} +report('scale values', roundTrip, roundTripChecked); + +/* ── 3. name / type parity ─────────────────────────────────────────────────── + * The load-bearing one for type safety: `keyof theme.colors` must keep yielding + * `'navy-800'`, not `'navy800'`. */ +console.log('\n3. NAME PARITY (drives the generated union types)'); + +const namesFor = (category) => + new Set( + Object.keys(generated) + .filter((k) => k.startsWith(`${category}-`)) + .map((k) => k.slice(category.length + 1)) + ); + +const nameProblems = []; +let nameChecked = 0; +for (const [gamutScale, category] of [['colors', 'color'], ...SCALES]) { + const expected = new Set(Object.keys(coreTheme[gamutScale])); + const actual = namesFor(category); + nameChecked += expected.size; + + for (const name of expected) + if (!actual.has(name)) + nameProblems.push( + `${category}: missing '${name}' (in theme, not generated)` + ); + for (const name of actual) + if (!expected.has(name)) + nameProblems.push( + `${category}: extra '${name}' (generated, not in theme)` + ); +} +report('token names match keyof theme[scale]', nameProblems, nameChecked); + +/* ── 4. engine-neutrality ──────────────────────────────────────────────────── + * If the Panda preset, the CSS and the TS all carry the same token set, then + * Panda is a CONSUMER of the tokens rather than the place they live — which is + * the only thing that makes swapping the engine cheap. */ +console.log('\n4. ENGINE-NEUTRALITY — three consumers, one source'); + +const { gamutPreset } = await import('./dist/panda-preset.mjs'); +const pandaNames = new Set( + Object.entries(gamutPreset.theme.tokens).flatMap(([category, entries]) => + Object.keys(entries).map((k) => `${category}-${k}`) + ) +); +const tsNames = new Set(Object.keys(generated)); +const cssNames = new Set([...light.keys()].map((v) => v.replace(/^--/, ''))); + +const neutrality = []; +for (const name of tsNames) { + if (!pandaNames.has(name)) + neutrality.push(`${name}: in TS, missing from Panda preset`); + if (!cssNames.has(name)) neutrality.push(`${name}: in TS, missing from CSS`); +} +for (const name of pandaNames) + if (!tsNames.has(name)) + neutrality.push(`${name}: in Panda preset, missing from TS`); +report('token set identical across CSS / TS / Panda', neutrality, tsNames.size); + +/* ── what DTCG could not express ─────────────────────────────────────────────── */ +const unexpressible = JSON.parse(read('tokens/.unexpressible.json')); +console.log(`\n5. NOT EXPRESSIBLE AS DTCG — ${unexpressible.length} value(s)`); +for (const { path, value } of unexpressible) + console.log(` • ${path} = ${JSON.stringify(value)}`); + +if (accepted.length) { + console.log('\n6. ACCEPTED DEVIATIONS (understood and unavoidable)'); + for (const problem of accepted) + console.log(` • ${problem}\n → ${EXPECTED_DEVIATIONS.get(problem)}`); +} + +console.log( + failures === 0 + ? '\nAll parity checks passed.\n' + : `\n${failures} unexpected parity problem(s) — see above.\n` +); +process.exitCode = failures === 0 ? 0 : 1; diff --git a/yarn.lock b/yarn.lock index dda9145e65..75e34be65a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1589,6 +1589,45 @@ __metadata: languageName: node linkType: hard +"@bundled-es-modules/deepmerge@npm:^4.3.2": + version: 4.3.2 + resolution: "@bundled-es-modules/deepmerge@npm:4.3.2" + dependencies: + deepmerge: "npm:^4.3.1" + checksum: 10c0/e868004dc4474b68efdd83306a13402aeb5ff748542397549ff2d8f922ce51f657792e115b28bfe31245a6fe591cb7a76f944e55ef51baf621366cca6af6f5dc + languageName: node + linkType: hard + +"@bundled-es-modules/glob@npm:^13.0.6": + version: 13.0.6 + resolution: "@bundled-es-modules/glob@npm:13.0.6" + dependencies: + buffer: "npm:^6.0.3" + events: "npm:^3.3.0" + glob: "npm:^13.0.6" + path: "npm:^0.12.7" + stream: "npm:^0.0.3" + string_decoder: "npm:^1.3.0" + url: "npm:^0.11.4" + checksum: 10c0/efe6879e8b33425d50a2cf01ff1a2ccc42b76e2a1fec26ac7293fdd229931554f053edcda6eabc2ee97ad7209b885c74278427357e8e7f41c15cc56438eb7b58 + languageName: node + linkType: hard + +"@bundled-es-modules/memfs@npm:^4.17.0": + version: 4.17.0 + resolution: "@bundled-es-modules/memfs@npm:4.17.0" + dependencies: + assert: "npm:^2.1.0" + buffer: "npm:^6.0.3" + events: "npm:^3.3.0" + memfs: "npm:^4.17.0" + path: "npm:^0.12.7" + stream: "npm:^0.0.3" + util: "npm:^0.12.5" + checksum: 10c0/88b573e52afe2f14b0c06f1f4337883ccf3155b7c3f667af8ead248b84ba2743e4eee90eb283ec967a8486576509bb6d42ce1c43714253f70ee23c5e5e24bc73 + languageName: node + linkType: hard + "@codecademy/eslint-config@npm:8.0.0": version: 8.0.0 resolution: "@codecademy/eslint-config@npm:8.0.0" @@ -3172,7 +3211,16 @@ __metadata: languageName: node linkType: hard -"@jsonjoy.com/base64@npm:^1.1.1": +"@jsonjoy.com/base64@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/base64@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10c0/d9616ec1ac0ea6aa455968b1f96f2d48ce38a2b1835922a909a55147d7b8cff3d648d45e9efe6781c6926beb5f04dc41c75ce548b6b84141b14bc122893e16ee + languageName: node + linkType: hard + +"@jsonjoy.com/base64@npm:^1.1.2": version: 1.1.2 resolution: "@jsonjoy.com/base64@npm:1.1.2" peerDependencies: @@ -3181,26 +3229,226 @@ __metadata: languageName: node linkType: hard -"@jsonjoy.com/json-pack@npm:^1.0.3": - version: 1.2.0 - resolution: "@jsonjoy.com/json-pack@npm:1.2.0" +"@jsonjoy.com/buffers@npm:17.67.0, @jsonjoy.com/buffers@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/buffers@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10c0/ee46d3ea6c2dee4dd5dffd8b156745baeecfe796c7bb3f091f9fe64c402aca5e4d86ba3d736545682f919303fb15359c1f00d41ac91ea1b5d4edbbe74f540d35 + languageName: node + linkType: hard + +"@jsonjoy.com/buffers@npm:^1.0.0, @jsonjoy.com/buffers@npm:^1.2.0": + version: 1.2.1 + resolution: "@jsonjoy.com/buffers@npm:1.2.1" + peerDependencies: + tslib: 2 + checksum: 10c0/5edaf761b78b730ae0598824adb37473fef5b40a8fc100625159700eb36e00057c5129c7ad15fc0e3178e8de58a044da65728e8d7b05fd3eed58e9b9a0d02b5a + languageName: node + linkType: hard + +"@jsonjoy.com/codegen@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/codegen@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10c0/3cc529377cc315acf373dc52dbd39d56285b31ba8ca90a4447230e37e405372cc13bed7df638dc81f9071ff8f4eb8e825217987397d80182d08ded761e609a93 + languageName: node + linkType: hard + +"@jsonjoy.com/codegen@npm:^1.0.0": + version: 1.0.0 + resolution: "@jsonjoy.com/codegen@npm:1.0.0" + peerDependencies: + tslib: 2 + checksum: 10c0/54686352248440ad1484ce7db0270a5a72424fb9651b090e5f1c8e2cd8e55e6c7a3f67dfe4ed90c689cf01ed949e794764a8069f5f52510eaf0a2d0c41d324cd + languageName: node + linkType: hard + +"@jsonjoy.com/fs-core@npm:4.64.0": + version: 4.64.0 + resolution: "@jsonjoy.com/fs-core@npm:4.64.0" + dependencies: + "@jsonjoy.com/fs-node-builtins": "npm:4.64.0" + "@jsonjoy.com/fs-node-utils": "npm:4.64.0" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10c0/88c84d776ddc9114c7e2cc6df5ebac7f3c98547d31f4474257367de4765e4b440aea8f7dfc90145c394977e26566ac23303f5fabcfdfe6d0482d46cb2f675feb + languageName: node + linkType: hard + +"@jsonjoy.com/fs-fsa@npm:4.64.0": + version: 4.64.0 + resolution: "@jsonjoy.com/fs-fsa@npm:4.64.0" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.64.0" + "@jsonjoy.com/fs-node-builtins": "npm:4.64.0" + "@jsonjoy.com/fs-node-utils": "npm:4.64.0" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10c0/e97d78a9a9d793c63874e53d25e09cd6460bb2a4b357b22607625a25cfdb4f04a3bc07102dc640a4881b6f3411e613f18e9615b8aa95a60780d63adbe8e8e7bd + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-builtins@npm:4.64.0": + version: 4.64.0 + resolution: "@jsonjoy.com/fs-node-builtins@npm:4.64.0" + peerDependencies: + tslib: 2 + checksum: 10c0/9b4f6bb4c68ec51dea2fc12d70042ff0465ba4ecda32135a04578fbe335b05072eadf33b9cbaaa8e3203efcd935723a18fe3d9306eda606d3d70e306c0201b90 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-to-fsa@npm:4.64.0": + version: 4.64.0 + resolution: "@jsonjoy.com/fs-node-to-fsa@npm:4.64.0" dependencies: - "@jsonjoy.com/base64": "npm:^1.1.1" - "@jsonjoy.com/util": "npm:^1.1.2" + "@jsonjoy.com/fs-fsa": "npm:4.64.0" + "@jsonjoy.com/fs-node-builtins": "npm:4.64.0" + "@jsonjoy.com/fs-node-utils": "npm:4.64.0" + peerDependencies: + tslib: 2 + checksum: 10c0/49d859d80a2d38f33fcbb93ed225132032456eb2c5d2548105a314c2a517f5dfaf0c54fee80964e1b5acda57ca655f94bbd44efe5ad857d54e233aab71eb3cf0 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-utils@npm:4.64.0": + version: 4.64.0 + resolution: "@jsonjoy.com/fs-node-utils@npm:4.64.0" + dependencies: + "@jsonjoy.com/fs-node-builtins": "npm:4.64.0" + glob-to-regex.js: "npm:^1.0.1" + peerDependencies: + tslib: 2 + checksum: 10c0/1375cd6036c8c2d8d7264527f6eb6adeb1ced15704b97f0d73043dfc0f238bef2e8f4f3d010ca39b32bf9bb513a8e20aeb41b3c61efc1e9ac55eea386726e11a + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node@npm:4.64.0": + version: 4.64.0 + resolution: "@jsonjoy.com/fs-node@npm:4.64.0" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.64.0" + "@jsonjoy.com/fs-node-builtins": "npm:4.64.0" + "@jsonjoy.com/fs-node-utils": "npm:4.64.0" + "@jsonjoy.com/fs-print": "npm:4.64.0" + "@jsonjoy.com/fs-snapshot": "npm:4.64.0" + glob-to-regex.js: "npm:^1.0.0" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10c0/616574f539dfc24ff5cd364c6dc75022081fd652018c5133b25a3cf408368152e7c514a6956c8cd8b25997a8252254aa959784f631a320af463737244dac5350 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-print@npm:4.64.0": + version: 4.64.0 + resolution: "@jsonjoy.com/fs-print@npm:4.64.0" + dependencies: + "@jsonjoy.com/fs-node-utils": "npm:4.64.0" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10c0/0fd60d555f0dcb5a39e6ed4609e49a03edc8315eb0ca1b81022c37687a1c348ded6df3628cb9de4f6dec9c8bf9d647ad2ec9fe4f23a761936841cb6aa2489379 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-snapshot@npm:4.64.0": + version: 4.64.0 + resolution: "@jsonjoy.com/fs-snapshot@npm:4.64.0" + dependencies: + "@jsonjoy.com/buffers": "npm:^17.65.0" + "@jsonjoy.com/fs-node-utils": "npm:4.64.0" + "@jsonjoy.com/json-pack": "npm:^17.65.0" + "@jsonjoy.com/util": "npm:^17.65.0" + peerDependencies: + tslib: 2 + checksum: 10c0/caece02d0df3fe375b05112df6c1be34a3aa354a4960aeeaffaf3d6a8e840998d74f3fae85176ee4acbe37307f137c95cd035a5748903e85efb6b71bc3c19df5 + languageName: node + linkType: hard + +"@jsonjoy.com/json-pack@npm:^1.11.0": + version: 1.21.0 + resolution: "@jsonjoy.com/json-pack@npm:1.21.0" + dependencies: + "@jsonjoy.com/base64": "npm:^1.1.2" + "@jsonjoy.com/buffers": "npm:^1.2.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/json-pointer": "npm:^1.0.2" + "@jsonjoy.com/util": "npm:^1.9.0" hyperdyperid: "npm:^1.2.0" - thingies: "npm:^1.20.0" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" peerDependencies: tslib: 2 - checksum: 10c0/0744cfe2f54d896003ad240f0f069b41a152feb53b6134c5e65961126b9e5fdfc74a46f63b1dfa280e80a3d176c57e06de072bf03d749ec1982e41677a1ce5d5 + checksum: 10c0/0183eccccf2ab912389a6784ae81c1a7da48cf178902efe093fb60c457359c7c75da2803f869e0a1489f1342dfa4f8ab9b27b65adc9f44fd9646823773b71e9d languageName: node linkType: hard -"@jsonjoy.com/util@npm:^1.1.2, @jsonjoy.com/util@npm:^1.3.0": - version: 1.5.0 - resolution: "@jsonjoy.com/util@npm:1.5.0" +"@jsonjoy.com/json-pack@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/json-pack@npm:17.67.0" + dependencies: + "@jsonjoy.com/base64": "npm:17.67.0" + "@jsonjoy.com/buffers": "npm:17.67.0" + "@jsonjoy.com/codegen": "npm:17.67.0" + "@jsonjoy.com/json-pointer": "npm:17.67.0" + "@jsonjoy.com/util": "npm:17.67.0" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10c0/fee56d024c84f031ef011a85ccca071c73b8a0739506083bd3dc7a17c720a498599f285e79082a9626314324ea938f189d18d47a03341cb76286ca2e7098bf53 + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/json-pointer@npm:17.67.0" + dependencies: + "@jsonjoy.com/util": "npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10c0/763e0b1bc274390a605073b49e5bf55bdf386e784f5940d456faca958d90915b7d9a47dd9d58a08e2113f40167b0640d313897811680eb91630726920618fe7d + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:^1.0.2": + version: 1.0.2 + resolution: "@jsonjoy.com/json-pointer@npm:1.0.2" + dependencies: + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/util": "npm:^1.9.0" + peerDependencies: + tslib: 2 + checksum: 10c0/8d959c0fdd77d937d2a829270de51533bb9e3b887b3f6f02943884dc33dd79225071218c93f4bafdee6a3412fd5153264997953a86de444d85c1fff67915af54 + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:17.67.0, @jsonjoy.com/util@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/util@npm:17.67.0" + dependencies: + "@jsonjoy.com/buffers": "npm:17.67.0" + "@jsonjoy.com/codegen": "npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10c0/44be53d94c99ce74a0eff1bb111f0ff4392a1226e34637321c8bc45b569da3f9e12db8b225eef3694c44b9fd2e9b800d7baf5ea0d38e1d7767bfcbef4fbf91b0 + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:^1.9.0": + version: 1.9.0 + resolution: "@jsonjoy.com/util@npm:1.9.0" + dependencies: + "@jsonjoy.com/buffers": "npm:^1.0.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" peerDependencies: tslib: 2 - checksum: 10c0/0065ae12c4108d8aede01a479c8d2b5a39bce99e9a449d235befc753f57e8385d9c1115720529f26597840b7398d512898155423d9859fd638319fb0c827365d + checksum: 10c0/a720a6accaae71fa9e7fa06e93e382702aa5760ef2bdc3bc45c19dc2228a01cc735d36cb970c654bc5e88f1328d55d1f0d5eceef0b76bcc327a2ce863e7b0021 languageName: node linkType: hard @@ -8643,6 +8891,13 @@ __metadata: languageName: node linkType: hard +"@zip.js/zip.js@npm:^2.7.44": + version: 2.8.34 + resolution: "@zip.js/zip.js@npm:2.8.34" + checksum: 10c0/d5fba33c9408607ef1487851659dd7ca4f278c83206330024f2e97687317a111431c6381b313c881bcc321978c052af94c2c21432c8a8effe06a63b3f70442f3 + languageName: node + linkType: hard + "@zkochan/js-yaml@npm:0.0.7": version: 0.0.7 resolution: "@zkochan/js-yaml@npm:0.0.7" @@ -9166,6 +9421,19 @@ __metadata: languageName: node linkType: hard +"assert@npm:^2.1.0": + version: 2.1.0 + resolution: "assert@npm:2.1.0" + dependencies: + call-bind: "npm:^1.0.2" + is-nan: "npm:^1.3.2" + object-is: "npm:^1.1.5" + object.assign: "npm:^4.1.4" + util: "npm:^0.12.5" + checksum: 10c0/7271a5da883c256a1fa690677bf1dd9d6aa882139f2bed1cd15da4f9e7459683e1da8e32a203d6cc6767e5e0f730c77a9532a87b896b4b0af0dd535f668775f0 + languageName: node + linkType: hard + "assertion-error@npm:^2.0.1": version: 2.0.1 resolution: "assertion-error@npm:2.0.1" @@ -9196,6 +9464,20 @@ __metadata: languageName: node linkType: hard +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 + languageName: node + linkType: hard + "async@npm:^2.6.4": version: 2.6.4 resolution: "async@npm:2.6.4" @@ -9677,12 +9959,12 @@ __metadata: languageName: node linkType: hard -"brace-expansion@npm:^5.0.2, brace-expansion@npm:^5.0.5": - version: 5.0.6 - resolution: "brace-expansion@npm:5.0.6" +"brace-expansion@npm:^5.0.2, brace-expansion@npm:^5.0.8": + version: 5.0.9 + resolution: "brace-expansion@npm:5.0.9" dependencies: balanced-match: "npm:^4.0.2" - checksum: 10c0/8c919869b90f61d533b341d3340be5ee4413232ea89b8246cbc2f38eb014f1d8182785c98a006eaf6111d02dc9eeffefdc240d5ac158625b2ed084dccd4bbf9b + checksum: 10c0/3dea38884a1c3c8b1c9c44a7402a0c76fca460f70cffb3127242b0b4cbf4472019e022ade021eec44838ff19f1dac2625dfd11dd459d7e1e055b0698a8d52fec languageName: node linkType: hard @@ -9761,6 +10043,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0 + languageName: node + linkType: hard + "bundle-name@npm:^4.1.0": version: 4.1.0 resolution: "bundle-name@npm:4.1.0" @@ -9825,16 +10117,25 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7": - version: 1.0.7 - resolution: "call-bind@npm:1.0.7" +"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7, call-bind@npm:^1.0.9": + version: 1.0.9 + resolution: "call-bind@npm:1.0.9" dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - set-function-length: "npm:^1.2.1" - checksum: 10c0/a3ded2e423b8e2a265983dba81c27e125b48eefb2655e7dfab6be597088da3d47c47976c24bc51b8fd9af1061f8f87b4ab78a314f3c77784b2ae2ba535ad8b8d + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + get-intrinsic: "npm:^1.3.0" + set-function-length: "npm:^1.2.2" + checksum: 10c0/a6621f6da1444481919ce3b4983dff725691e0754d3507ae483ce56e54985f2da7d6f1df512c56dbf28660745cf1ca52553f1fc9aef5557f3ce353ef14fab714 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 languageName: node linkType: hard @@ -9958,13 +10259,20 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.2.0": +"chalk@npm:^5.2.0, chalk@npm:^5.3.0": version: 5.6.2 resolution: "chalk@npm:5.6.2" checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 languageName: node linkType: hard +"change-case@npm:^5.3.0": + version: 5.4.4 + resolution: "change-case@npm:5.4.4" + checksum: 10c0/2a9c2b9c9ad6ab2491105aaf506db1a9acaf543a18967798dcce20926c6a173aa63266cb6189f3086e3c14bf7ae1f8ea4f96ecc466fcd582310efa00372f3734 + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -10286,7 +10594,7 @@ __metadata: languageName: node linkType: hard -"colorjs.io@npm:^0.5.0": +"colorjs.io@npm:^0.5.0, colorjs.io@npm:^0.5.2": version: 0.5.2 resolution: "colorjs.io@npm:0.5.2" checksum: 10c0/2e6ea43629e325e721b92429239de3a6f42fb6d88ba6e4c2aeff0288c196d876f2f7ee82aea95bd40072d5cdc8cb87f042f4d94c134dcabf0e34a717e4caacb9 @@ -10389,6 +10697,13 @@ __metadata: languageName: node linkType: hard +"component-emitter@npm:^2.0.0": + version: 2.0.0 + resolution: "component-emitter@npm:2.0.0" + checksum: 10c0/65dfaf787ea49eb48f0ffec766bda7ec67e8dbeb3b406f08724dcae842e0aa274731fcccb9280b77d2b41693061731a9080b60d276020246a146544cd9900b83 + languageName: node + linkType: hard + "component-test-setup@npm:*, component-test-setup@npm:^0.3.1": version: 0.3.3 resolution: "component-test-setup@npm:0.3.3" @@ -11616,6 +11931,14 @@ __metadata: languageName: node linkType: hard +"dtcg-tokens-poc@workspace:spikes/dtcg-tokens-poc": + version: 0.0.0-use.local + resolution: "dtcg-tokens-poc@workspace:spikes/dtcg-tokens-poc" + dependencies: + style-dictionary: "npm:5.5.0" + languageName: unknown + linkType: soft + "dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" @@ -12625,7 +12948,7 @@ __metadata: languageName: node linkType: hard -"events@npm:^3.2.0": +"events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 @@ -13101,12 +13424,12 @@ __metadata: languageName: node linkType: hard -"for-each@npm:^0.3.3": - version: 0.3.3 - resolution: "for-each@npm:0.3.3" +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" dependencies: - is-callable: "npm:^1.1.3" - checksum: 10c0/22330d8a2db728dbf003ec9182c2d421fbcd2969b02b4f97ec288721cda63eb28f2c08585ddccd0f77cb2930af8d958005c9e72f47141dc51816127a118f39aa + is-callable: "npm:^1.2.7" + checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee languageName: node linkType: hard @@ -13499,6 +13822,13 @@ __metadata: languageName: unknown linkType: soft +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 + languageName: node + linkType: hard + "generic-names@npm:^4.0.0": version: 4.0.0 resolution: "generic-names@npm:4.0.0" @@ -13529,21 +13859,24 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.6": - version: 1.3.0 - resolution: "get-intrinsic@npm:1.3.0" +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" call-bind-apply-helpers: "npm:^1.0.2" es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" es-object-atoms: "npm:^1.1.1" function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" get-proto: "npm:^1.0.1" gopd: "npm:^1.2.0" has-symbols: "npm:^1.1.0" hasown: "npm:^2.0.2" math-intrinsics: "npm:^1.1.0" - checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a + checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d languageName: node linkType: hard @@ -13625,6 +13958,15 @@ __metadata: languageName: node linkType: hard +"glob-to-regex.js@npm:^1.0.0, glob-to-regex.js@npm:^1.0.1": + version: 1.2.0 + resolution: "glob-to-regex.js@npm:1.2.0" + peerDependencies: + tslib: 2 + checksum: 10c0/011c81ae2a4d7ac5fd617038209fd9639d54c76211cc88fe8dd85d1a0850bc683a63cf5b1eae370141fca7dd2c834dfb9684dfdd8bf7472f2c1e4ef6ab6e34f9 + languageName: node + linkType: hard + "glob-to-regexp@npm:^0.4.1": version: 0.4.1 resolution: "glob-to-regexp@npm:0.4.1" @@ -13648,14 +13990,14 @@ __metadata: languageName: node linkType: hard -"glob@npm:^13.0.0": - version: 13.0.0 - resolution: "glob@npm:13.0.0" +"glob@npm:^13.0.0, glob@npm:^13.0.6": + version: 13.0.6 + resolution: "glob@npm:13.0.6" dependencies: - minimatch: "npm:^10.1.1" - minipass: "npm:^7.1.2" - path-scurry: "npm:^2.0.0" - checksum: 10c0/8e2f5821f3f7c312dd102e23a15b80c79e0837a9872784293ba2e15ec73b3f3749a49a42a31bfcb4e52c84820a474e92331c2eebf18819d20308f5c33876630a + minimatch: "npm:^10.2.2" + minipass: "npm:^7.1.3" + path-scurry: "npm:^2.0.2" + checksum: 10c0/269c236f11a9b50357fe7a8c6aadac667e01deb5242b19c84975628f05f4438d8ee1354bb62c5d6c10f37fd59911b54d7799730633a2786660d8c69f1d18120a languageName: node linkType: hard @@ -14300,7 +14642,7 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.13": +"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb @@ -14504,13 +14846,13 @@ __metadata: languageName: node linkType: hard -"is-arguments@npm:^1.1.1": - version: 1.1.1 - resolution: "is-arguments@npm:1.1.1" +"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1": + version: 1.2.0 + resolution: "is-arguments@npm:1.2.0" dependencies: - call-bind: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/5ff1f341ee4475350adfc14b2328b38962564b7c2076be2f5bac7bd9b61779efba99b9f844a7b82ba7654adccf8e8eb19d1bb0cc6d1c1a085e498f6793d4328f + call-bound: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/6377344b31e9fcb707c6751ee89b11f132f32338e6a782ec2eac9393b0cbd32235dad93052998cda778ee058754860738341d8114910d50ada5615912bb929fc languageName: node linkType: hard @@ -14575,7 +14917,7 @@ __metadata: languageName: node linkType: hard -"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": +"is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f @@ -14673,12 +15015,16 @@ __metadata: languageName: node linkType: hard -"is-generator-function@npm:^1.0.10": - version: 1.0.10 - resolution: "is-generator-function@npm:1.0.10" +"is-generator-function@npm:^1.0.10, is-generator-function@npm:^1.0.7": + version: 1.1.2 + resolution: "is-generator-function@npm:1.1.2" dependencies: - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/df03514df01a6098945b5a0cfa1abff715807c8e72f57c49a0686ad54b3b74d394e2d8714e6f709a71eb00c9630d48e73ca1796c1ccc84ac95092c1fecc0d98b + call-bound: "npm:^1.0.4" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/83da102e89c3e3b71d67b51d47c9f9bc862bceb58f87201727e27f7fa19d1d90b0ab223644ecaee6fc6e3d2d622bb25c966fbdaf87c59158b01ce7c0fe2fa372 languageName: node linkType: hard @@ -14730,6 +15076,16 @@ __metadata: languageName: node linkType: hard +"is-nan@npm:^1.3.2": + version: 1.3.2 + resolution: "is-nan@npm:1.3.2" + dependencies: + call-bind: "npm:^1.0.0" + define-properties: "npm:^1.1.3" + checksum: 10c0/8bfb286f85763f9c2e28ea32e9127702fe980ffd15fa5d63ade3be7786559e6e21355d3625dd364c769c033c5aedf0a2ed3d4025d336abf1b9241e3d9eddc5b0 + languageName: node + linkType: hard + "is-negative-zero@npm:^2.0.3": version: 2.0.3 resolution: "is-negative-zero@npm:2.0.3" @@ -14788,6 +15144,13 @@ __metadata: languageName: node linkType: hard +"is-plain-obj@npm:^4.1.0": + version: 4.1.0 + resolution: "is-plain-obj@npm:4.1.0" + checksum: 10c0/32130d651d71d9564dc88ba7e6fda0e91a1010a3694648e9f4f47bb6080438140696d3e3e15c741411d712e47ac9edc1a8a9de1fe76f3487b0d90be06ac9975e + languageName: node + linkType: hard + "is-plain-object@npm:^5.0.0": version: 5.0.0 resolution: "is-plain-object@npm:5.0.0" @@ -14811,13 +15174,15 @@ __metadata: languageName: node linkType: hard -"is-regex@npm:^1.1.4": - version: 1.1.4 - resolution: "is-regex@npm:1.1.4" +"is-regex@npm:^1.1.4, is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" dependencies: - call-bind: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/bb72aae604a69eafd4a82a93002058c416ace8cde95873589a97fc5dac96a6c6c78a9977d487b7b95426a8f5073969124dd228f043f9f604f041f32fcc465fc1 + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04 languageName: node linkType: hard @@ -14862,12 +15227,12 @@ __metadata: languageName: node linkType: hard -"is-typed-array@npm:^1.1.13": - version: 1.1.13 - resolution: "is-typed-array@npm:1.1.13" +"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.3": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" dependencies: - which-typed-array: "npm:^1.1.14" - checksum: 10c0/fa5cb97d4a80e52c2cc8ed3778e39f175a1a2ae4ddf3adae3187d69586a1fd57cfa0b095db31f66aa90331e9e3da79184cea9c6abdcd1abc722dc3c3edd51cca + which-typed-array: "npm:^1.1.16" + checksum: 10c0/415511da3669e36e002820584e264997ffe277ff136643a3126cc949197e6ca3334d0f12d084e83b1994af2e9c8141275c741cf2b7da5a2ff62dd0cac26f76c4 languageName: node linkType: hard @@ -16999,15 +17364,27 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^4.6.0": - version: 4.17.0 - resolution: "memfs@npm:4.17.0" - dependencies: - "@jsonjoy.com/json-pack": "npm:^1.0.3" - "@jsonjoy.com/util": "npm:^1.3.0" - tree-dump: "npm:^1.0.1" +"memfs@npm:^4.17.0, memfs@npm:^4.6.0": + version: 4.64.0 + resolution: "memfs@npm:4.64.0" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.64.0" + "@jsonjoy.com/fs-fsa": "npm:4.64.0" + "@jsonjoy.com/fs-node": "npm:4.64.0" + "@jsonjoy.com/fs-node-builtins": "npm:4.64.0" + "@jsonjoy.com/fs-node-to-fsa": "npm:4.64.0" + "@jsonjoy.com/fs-node-utils": "npm:4.64.0" + "@jsonjoy.com/fs-print": "npm:4.64.0" + "@jsonjoy.com/fs-snapshot": "npm:4.64.0" + "@jsonjoy.com/json-pack": "npm:^1.11.0" + "@jsonjoy.com/util": "npm:^1.9.0" + glob-to-regex.js: "npm:^1.0.1" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.0.3" tslib: "npm:^2.0.0" - checksum: 10c0/2901f69e80e1fbefa8aafe994a253fff6f34eb176d8b80d57476311611e516a11ab4dd93f852c8739fe04f2b57d6a4ca7a1828fa0bd401ce631bcac214b3d58b + peerDependencies: + tslib: 2 + checksum: 10c0/fe4b887b1709faeed6c42567cb3310c534b1c4eebae00be19a950eb1c2cbe010e014cd7aa4255ff1afc34ec62a105a28c7add93ad4c36ee8b219e8665c9ca1f8 languageName: node linkType: hard @@ -17181,12 +17558,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.1.1": - version: 10.2.5 - resolution: "minimatch@npm:10.2.5" +"minimatch@npm:^10.2.2": + version: 10.2.6 + resolution: "minimatch@npm:10.2.6" dependencies: - brace-expansion: "npm:^5.0.5" - checksum: 10c0/6bb058bd6324104b9ec2f763476a35386d05079c1f5fe4fbf1f324a25237cd4534d6813ecd71f48208f4e635c1221899bef94c3c89f7df55698fe373aaae20fd + brace-expansion: "npm:^5.0.8" + checksum: 10c0/4559a836243b98bd4d17ea9f7edae698717c76399eea7be374f3737f33164e4907f19e9726891ddeb122f750a5a7fa80d2ac43e851d6e5984dc4ff42ec127d3a languageName: node linkType: hard @@ -17302,10 +17679,10 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb languageName: node linkType: hard @@ -17830,10 +18207,10 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.13.1": - version: 1.13.2 - resolution: "object-inspect@npm:1.13.2" - checksum: 10c0/b97835b4c91ec37b5fd71add84f21c3f1047d1d155d00c0fcd6699516c256d4fcc6ff17a1aced873197fe447f91a3964178fd2a67a1ee2120cdaf60e81a050b4 +"object-inspect@npm:^1.13.1, object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 languageName: node linkType: hard @@ -18442,13 +18819,13 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^2.0.0": - version: 2.0.1 - resolution: "path-scurry@npm:2.0.1" +"path-scurry@npm:^2.0.2": + version: 2.0.2 + resolution: "path-scurry@npm:2.0.2" dependencies: lru-cache: "npm:^11.0.0" minipass: "npm:^7.1.2" - checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 + checksum: 10c0/b35ad37cf6557a87fd057121ce2be7695380c9138d93e87ae928609da259ea0a170fac6f3ef1eb3ece8a068e8b7f2f3adf5bb2374cf4d4a57fe484954fcc9482 languageName: node linkType: hard @@ -18466,6 +18843,23 @@ __metadata: languageName: node linkType: hard +"path-unified@npm:^0.2.0": + version: 0.2.0 + resolution: "path-unified@npm:0.2.0" + checksum: 10c0/5229bbcbb093b1c76e7a8f568dd7d362bae6bd9348099968252aa17b1ffd86ef845d560a6b483bb2e6a3b2c25a5e8288707b03e41b66b2761aa1e2ba67b07d5b + languageName: node + linkType: hard + +"path@npm:^0.12.7": + version: 0.12.7 + resolution: "path@npm:0.12.7" + dependencies: + process: "npm:^0.11.1" + util: "npm:^0.10.3" + checksum: 10c0/f795ce5438a988a590c7b6dfd450ec9baa1c391a8be4c2dea48baa6e0f5b199e56cd83b8c9ebf3991b81bea58236d2c32bdafe2c17a2e70c3a2e4c69891ade59 + languageName: node + linkType: hard + "pathval@npm:^2.0.0": version: 2.0.1 resolution: "pathval@npm:2.0.1" @@ -19056,6 +19450,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^3.3.3": + version: 3.9.6 + resolution: "prettier@npm:3.9.6" + bin: + prettier: bin/prettier.cjs + checksum: 10c0/9f7ddae234035868f3daab3dc34e6de7e54622185afb017378029f0c09a9c023248ccf6f34def0b17a0538e18c47aa1b3188bab72965d1bf735919baa0747e99 + languageName: node + linkType: hard + "pretty-error@npm:^4.0.0": version: 4.0.0 resolution: "pretty-error@npm:4.0.0" @@ -19129,6 +19532,13 @@ __metadata: languageName: node linkType: hard +"process@npm:^0.11.1": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 + languageName: node + linkType: hard + "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -19204,6 +19614,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^1.4.1": + version: 1.4.1 + resolution: "punycode@npm:1.4.1" + checksum: 10c0/354b743320518aef36f77013be6e15da4db24c2b4f62c5f1eb0529a6ed02fbaf1cb52925785f6ab85a962f2b590d9cd5ad730b70da72b5f180e2556b8bd3ca08 + languageName: node + linkType: hard + "punycode@npm:^2.1.0, punycode@npm:^2.1.1, punycode@npm:^2.3.1": version: 2.3.1 resolution: "punycode@npm:2.3.1" @@ -19232,7 +19649,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.13.0, qs@npm:^6.4.0": +"qs@npm:6.13.0": version: 6.13.0 resolution: "qs@npm:6.13.0" dependencies: @@ -19241,6 +19658,16 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.12.3, qs@npm:^6.4.0": + version: 6.15.3 + resolution: "qs@npm:6.15.3" + dependencies: + es-define-property: "npm:^1.0.1" + side-channel: "npm:^1.1.1" + checksum: 10c0/8f3f6e45ece255347d57696628401cde29e9ec649fff698b53bd3150dea7cefdf33036e1bc1826b9f110bfa7cb0ec4ab9f5297eca628ce216c55af82c304e08e + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -20439,14 +20866,14 @@ __metadata: languageName: node linkType: hard -"safe-regex-test@npm:^1.0.3": - version: 1.0.3 - resolution: "safe-regex-test@npm:1.0.3" +"safe-regex-test@npm:^1.0.3, safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" dependencies: - call-bind: "npm:^1.0.6" + call-bound: "npm:^1.0.2" es-errors: "npm:^1.3.0" - is-regex: "npm:^1.1.4" - checksum: 10c0/900bf7c98dc58f08d8523b7012b468e4eb757afa624f198902c0643d7008ba777b0bdc35810ba0b758671ce887617295fb742b3f3968991b178ceca54cb07603 + is-regex: "npm:^1.2.1" + checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665 languageName: node linkType: hard @@ -20923,7 +21350,7 @@ __metadata: languageName: node linkType: hard -"set-function-length@npm:^1.2.1": +"set-function-length@npm:^1.2.2": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" dependencies: @@ -21000,15 +21427,51 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6": - version: 1.0.6 - resolution: "side-channel@npm:1.0.6" +"side-channel-list@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-list@npm:1.0.1" dependencies: - call-bind: "npm:^1.0.7" es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.4" - object-inspect: "npm:^1.13.1" - checksum: 10c0/d2afd163dc733cc0a39aa6f7e39bf0c436293510dbccbff446733daeaf295857dbccf94297092ec8c53e2503acac30f0b78830876f0485991d62a90e9cad305f + object-inspect: "npm:^1.13.4" + checksum: 10c0/d346c787fd2f9f1c2fdea14f00e8250118db0e7596d85a6cb9faa75f105d31a73a8f7a341c93d7df2a2429098c3d37a77bd3be9e88c37094b8c01807bc77c7a2 + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6, side-channel@npm:^1.1.1": + version: 1.1.1 + resolution: "side-channel@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.4" + side-channel-list: "npm:^1.0.1" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/dc0ab81d67f61bda9247d053ce93f41c3fd8ad2bdcb9cf9d8d2f8540d488f26d87a5e99ebfc07eea49ec025867b2452b705442d974b1478f0395e69f6bfb3270 languageName: node linkType: hard @@ -21483,6 +21946,15 @@ __metadata: languageName: node linkType: hard +"stream@npm:^0.0.3": + version: 0.0.3 + resolution: "stream@npm:0.0.3" + dependencies: + component-emitter: "npm:^2.0.0" + checksum: 10c0/5d262408583f3d5fed8077b33ad670320d85c6b7c0fb3ab73a9a632fbad0ee36f3c66e6feb5264cb39dbee3a619174fa886b5f69f98217666d0844f6a2f6510b + languageName: node + linkType: hard + "streamroller@npm:^3.1.5": version: 3.1.5 resolution: "streamroller@npm:3.1.5" @@ -21645,7 +22117,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1": +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -21747,6 +22219,29 @@ __metadata: languageName: node linkType: hard +"style-dictionary@npm:5.5.0": + version: 5.5.0 + resolution: "style-dictionary@npm:5.5.0" + dependencies: + "@bundled-es-modules/deepmerge": "npm:^4.3.2" + "@bundled-es-modules/glob": "npm:^13.0.6" + "@bundled-es-modules/memfs": "npm:^4.17.0" + "@zip.js/zip.js": "npm:^2.7.44" + chalk: "npm:^5.3.0" + change-case: "npm:^5.3.0" + colorjs.io: "npm:^0.5.2" + commander: "npm:^12.1.0" + is-plain-obj: "npm:^4.1.0" + json5: "npm:^2.2.2" + path-unified: "npm:^0.2.0" + prettier: "npm:^3.3.3" + tinycolor2: "npm:^1.6.0" + bin: + style-dictionary: bin/style-dictionary.js + checksum: 10c0/02896bf9ad7614ab2c7b287f1791b32ffb6696b99b1a54e827f55d07d2856fe4a33f857156d008daf4de3aa2b6b56978bb46f9e141db5fff0b7a161bc1d5e69c + languageName: node + linkType: hard + "style-loader@npm:^3.3.0": version: 3.3.4 resolution: "style-loader@npm:3.3.4" @@ -22058,12 +22553,12 @@ __metadata: languageName: node linkType: hard -"thingies@npm:^1.20.0": - version: 1.21.0 - resolution: "thingies@npm:1.21.0" +"thingies@npm:^2.5.0": + version: 2.6.1 + resolution: "thingies@npm:2.6.1" peerDependencies: tslib: ^2 - checksum: 10c0/7570ee855aecb73185a672ecf3eb1c287a6512bf5476449388433b2d4debcf78100bc8bfd439b0edd38d2bc3bfb8341de5ce85b8557dec66d0f27b962c9a8bc1 + checksum: 10c0/eaf83b2a77dc105ff724c04777a56cea40e945a73bd50cc7554694702ad3d09d1d1843ed32315723ea45490106e8a0f972d9c12dc60d3081c551b4eb4235b24f languageName: node linkType: hard @@ -22102,6 +22597,13 @@ __metadata: languageName: node linkType: hard +"tinycolor2@npm:^1.6.0": + version: 1.6.0 + resolution: "tinycolor2@npm:1.6.0" + checksum: 10c0/9aa79a36ba2c2a87cb221453465cabacd04b9e35f9694373e846fdc78b1c768110f81e581ea41440106c0f24d9a023891d0887e8075885e790ac40eb0e74a5c1 + languageName: node + linkType: hard + "tinyexec@npm:^1.0.4": version: 1.0.4 resolution: "tinyexec@npm:1.0.4" @@ -22198,12 +22700,12 @@ __metadata: languageName: node linkType: hard -"tree-dump@npm:^1.0.1": - version: 1.0.2 - resolution: "tree-dump@npm:1.0.2" +"tree-dump@npm:^1.0.3, tree-dump@npm:^1.1.0": + version: 1.1.0 + resolution: "tree-dump@npm:1.1.0" peerDependencies: tslib: 2 - checksum: 10c0/d1d180764e9c691b28332dbd74226c6b6af361dfb1e134bb11e60e17cb11c215894adee50ffc578da5dcf546006693947be8b6665eb1269b56e2f534926f1c1f + checksum: 10c0/079f0f0163b68ee2eedc65cab1de6fb121487eba9ae135c106a8bc5e4ab7906ae0b57d86016e4a7da8c0ee906da1eae8c6a1490cd6e2a5e5ccbca321e1f959ca languageName: node linkType: hard @@ -22892,6 +23394,16 @@ __metadata: languageName: node linkType: hard +"url@npm:^0.11.4": + version: 0.11.4 + resolution: "url@npm:0.11.4" + dependencies: + punycode: "npm:^1.4.1" + qs: "npm:^6.12.3" + checksum: 10c0/cc93405ae4a9b97a2aa60ca67f1cb1481c0221cb4725a7341d149be5e2f9cfda26fd432d64dbbec693d16593b68b8a46aad8e5eab21f814932134c9d8620c662 + languageName: node + linkType: hard + "use-callback-ref@npm:^1.3.3": version: 1.3.3 resolution: "use-callback-ref@npm:1.3.3" @@ -22963,6 +23475,28 @@ __metadata: languageName: node linkType: hard +"util@npm:^0.10.3": + version: 0.10.4 + resolution: "util@npm:0.10.4" + dependencies: + inherits: "npm:2.0.3" + checksum: 10c0/d29f6893e406b63b088ce9924da03201df89b31490d4d011f1c07a386ea4b3dbe907464c274023c237da470258e1805d806c7e4009a5974cd6b1d474b675852a + languageName: node + linkType: hard + +"util@npm:^0.12.5": + version: 0.12.5 + resolution: "util@npm:0.12.5" + dependencies: + inherits: "npm:^2.0.3" + is-arguments: "npm:^1.0.4" + is-generator-function: "npm:^1.0.7" + is-typed-array: "npm:^1.1.3" + which-typed-array: "npm:^1.1.2" + checksum: 10c0/c27054de2cea2229a66c09522d0fa1415fb12d861d08523a8846bf2e4cbf0079d4c3f725f09dcb87493549bcbf05f5798dce1688b53c6c17201a45759e7253f3 + languageName: node + linkType: hard + "utila@npm:~0.4": version: 0.4.0 resolution: "utila@npm:0.4.0" @@ -23440,16 +23974,18 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15": - version: 1.1.15 - resolution: "which-typed-array@npm:1.1.15" +"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.15, which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.2": + version: 1.1.22 + resolution: "which-typed-array@npm:1.1.22" dependencies: available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.7" - for-each: "npm:^0.3.3" - gopd: "npm:^1.0.1" + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" has-tostringtag: "npm:^1.0.2" - checksum: 10c0/4465d5348c044032032251be54d8988270e69c6b7154f8fcb2a47ff706fe36f7624b3a24246b8d9089435a8f4ec48c1c1025c5d6b499456b9e5eff4f48212983 + checksum: 10c0/e59db184a4e78b461fac3b05fafc1e7badbbedafbf04a967ee1de73717f1f9723a79699e7b5de71d449541cb5da8353efc01a4f8a72a152479850e54fa196c40 languageName: node linkType: hard From 94feea34d8526066e611a16a59f7b986947eedae Mon Sep 17 00:00:00 2001 From: dreamwasp Date: Mon, 10 Aug 2026 09:37:16 -0400 Subject: [PATCH 2/2] spike(GMT-1715): make the DTCG export idempotent .unexpressible.json was written without a trailing newline, so every run left the working tree dirty against the lint-formatted committed copy. Also ignore the drafted PR body. Co-Authored-By: Claude Opus 5 --- spikes/dtcg-tokens-poc/.gitignore | 1 + spikes/dtcg-tokens-poc/export-dtcg.mjs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spikes/dtcg-tokens-poc/.gitignore b/spikes/dtcg-tokens-poc/.gitignore index 6f3a6e4495..0e5d78f351 100644 --- a/spikes/dtcg-tokens-poc/.gitignore +++ b/spikes/dtcg-tokens-poc/.gitignore @@ -1,3 +1,4 @@ .tmp/ dist/ node_modules +PR_DESCRIPTION.md diff --git a/spikes/dtcg-tokens-poc/export-dtcg.mjs b/spikes/dtcg-tokens-poc/export-dtcg.mjs index 7f0c62abb5..2cb344ad79 100644 --- a/spikes/dtcg-tokens-poc/export-dtcg.mjs +++ b/spikes/dtcg-tokens-poc/export-dtcg.mjs @@ -183,7 +183,8 @@ if (unexpressible.length) { for (const { path, value, why } of unexpressible) console.log(` ${path} = ${JSON.stringify(value)}\n → ${why}`); } +// trailing newline, so re-running doesn't dirty the working tree writeFileSync( 'tokens/.unexpressible.json', - JSON.stringify(unexpressible, null, 2) + `${JSON.stringify(unexpressible, null, 2)}\n` );