@phcdevworks/spectre-tokens 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # @phcdevworks/spectre-tokens
2
2
 
3
- [![GitHub issues](https://img.shields.io/github/issues/phcdevworks/spectre-tokens)](https://github.com/phcdevworks/spectre-tokens/issues)
4
- [![GitHub pulls](https://img.shields.io/github/issues-pr/phcdevworks/spectre-tokens)](https://github.com/phcdevworks/spectre-tokens/pulls)
3
+ [![npm version](https://img.shields.io/npm/v/@phcdevworks/spectre-tokens)](https://www.npmjs.com/package/@phcdevworks/spectre-tokens)
4
+ [![CI](https://img.shields.io/github/actions/workflow/status/phcdevworks/spectre-tokens/ci.yml?branch=main&label=CI)](https://github.com/phcdevworks/spectre-tokens/actions/workflows/ci.yml)
5
5
  [![License](https://img.shields.io/github/license/phcdevworks/spectre-tokens)](LICENSE)
6
+ [![Node](https://img.shields.io/badge/node-%5E22.12.0%20%7C%7C%20%3E%3D24.0.0-brightgreen)](https://nodejs.org)
6
7
 
7
8
  `@phcdevworks/spectre-tokens` is the design-token package of the Spectre system
8
9
  for downstream Spectre packages and compatible applications.
@@ -13,19 +14,25 @@ token data while downstream UI packages define structure and adapter packages
13
14
  translate those contracts for specific frameworks and runtimes.
14
15
 
15
16
  [Contributing](CONTRIBUTING.md) | [Changelog](CHANGELOG.md) |
17
+ [Token Contract](TOKEN_CONTRACT.md) | [Roadmap](ROADMAP.md) |
16
18
  [Security Policy](SECURITY.md)
17
19
 
18
- ## Key capabilities
20
+ ## Source of truth
19
21
 
20
- - Uses `tokens/` as the source of truth for design-token data
21
- - Generates JavaScript, TypeScript, CSS, and Tailwind theme exports from shared
22
- token sources
23
- - Defines semantic token contracts for surfaces, text, components, buttons,
24
- forms, and modes
25
- - Exposes primitives and semantic roles for downstream packages and compatible
26
- applications
27
- - Keeps visual meaning centralized so downstream consumers do not redefine token
28
- contracts
22
+ `tokens/` is the source of truth. `contract.manifest.json` is the
23
+ machine-readable contract authority. Everything else is derived from them or
24
+ validated against them.
25
+
26
+ | Layer | Path | Rule |
27
+ |---|---|---|
28
+ | Source token data | `tokens/*.json` | All token value changes start here — never anywhere else |
29
+ | Contract authority | `contract.manifest.json` | Governs public namespaces and required output surfaces |
30
+ | Public entry points | `src/index.ts` · `src/types.ts` · `src/css.ts` | Contract-authority files — changes require changelog classification |
31
+ | Generated TypeScript | `src/generated/tokens.ts` | **Never edit directly** — regenerated by `npm run build` |
32
+ | Generated dist | `dist/` | **Never edit directly** — regenerated by `npm run build` |
33
+
34
+ After any source change: run `npm run build` to regenerate outputs, then
35
+ `npm run check` to validate the full contract.
29
36
 
30
37
  ## What this package owns
31
38
 
@@ -39,14 +46,30 @@ This package is the correct place to define token meaning.
39
46
 
40
47
  ## What this package does not own
41
48
 
42
- - Component structure or composition That belongs in downstream UI packages such
43
- as [`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui).
44
- - Framework-specific delivery Adapter packages translate Spectre contracts for
49
+ - Component structure or composition. That belongs in downstream UI packages
50
+ such as
51
+ [`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui).
52
+ - Framework-specific delivery. Adapter packages translate Spectre contracts for
45
53
  specific frameworks and runtimes.
46
- - Local redefinition of token meaning Downstream consumers should consume these
54
+ - Local redefinition of token meaning. Downstream consumers should consume these
47
55
  contracts rather than recreate them independently.
48
- - Example app architecture The `example/` directory documents token usage; it is
49
- not the contract source and should not become a downstream UI layer.
56
+ - Example app architecture. The `example/` directory documents token usage; it
57
+ is not the contract source and should not become a downstream UI layer.
58
+
59
+ ## When to use this package
60
+
61
+ - You are building a Spectre ecosystem package and need the visual language contract.
62
+ - You need design token values in JavaScript, TypeScript, CSS variables, or a Tailwind theme.
63
+ - You want a single source of truth for semantic roles: `surface`, `text`, `component`, `buttons`, `forms`, `modes`.
64
+ - You are consuming tokens as named values, not inventing new token meaning.
65
+
66
+ ## When not to use this package
67
+
68
+ - You need UI components or component structure — use
69
+ [`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui).
70
+ - You need framework-specific component delivery — use the appropriate adapter package.
71
+ - You want to define your own token meaning or override Spectre semantics locally —
72
+ this package is the authority; downstream consumers should consume, not redefine.
50
73
 
51
74
  ## Installation
52
75
 
@@ -93,14 +116,56 @@ export default {
93
116
  }
94
117
  ```
95
118
 
96
- ## Consumer usage
119
+ ## Semantic tokens vs raw palette tokens
120
+
121
+ Semantic tokens express UI meaning. Raw palette tokens expose the fixed color
122
+ ramp. Always prefer semantic tokens for UI surfaces, text, buttons, forms, and
123
+ mode-aware styling.
97
124
 
98
- ### Source of truth
125
+ ### Semantic namespaces (prefer for all UI work)
99
126
 
100
- - Change token data in `tokens/`.
101
- - Treat generated outputs as derived artifacts.
102
- - Treat `contract.manifest.json` as the machine-readable contract authority for
103
- public namespaces and required outputs.
127
+ | Namespace | What it expresses |
128
+ |---|---|
129
+ | `surface` | Background roles: page, card, overlay, sidebar |
130
+ | `text` | Foreground roles: default, muted, subtle, meta, on-surface, on-page |
131
+ | `component` | Role-specific tokens for icon boxes, badges, ratings, testimonials, pricing cards |
132
+ | `buttons` | Button state tokens: default, hover, active, disabled, CTA |
133
+ | `forms` | Form state tokens: default, focused, error, disabled |
134
+ | `modes` | Mode-aware overrides under `modes.default` and `modes.dark` |
135
+
136
+ ```ts
137
+ import tokens from '@phcdevworks/spectre-tokens'
138
+
139
+ // Semantic — always prefer this for UI
140
+ const card = {
141
+ background: tokens.surface.card,
142
+ color: tokens.text.onSurface.default,
143
+ }
144
+
145
+ // Mode-aware semantic
146
+ const dark = {
147
+ background: tokens.modes.dark.surface.page,
148
+ color: tokens.modes.dark.text.onPage.default,
149
+ }
150
+ ```
151
+
152
+ ### Raw palette (use sparingly)
153
+
154
+ The `colors` namespace exposes the raw palette ramp. Use it only when fixed
155
+ color access is intentional — data visualization, compatibility layers, or
156
+ tooling that inspects palette data directly.
157
+
158
+ ```ts
159
+ // Raw palette — only when fixed color access is deliberate
160
+ const chart = {
161
+ series1: tokens.colors.brand[500],
162
+ series2: tokens.colors.neutral[300],
163
+ }
164
+ ```
165
+
166
+ Do not use `colors` as a substitute for semantic tokens in normal UI surfaces.
167
+
168
+ ## Consumer usage
104
169
 
105
170
  ### JavaScript and TypeScript tokens
106
171
 
@@ -163,34 +228,6 @@ export default {
163
228
  Use `tailwindTheme` directly only when a consumer needs the generated theme
164
229
  object outside the preset shape.
165
230
 
166
- ### Semantic tokens first
167
-
168
- Prefer semantic namespaces for downstream UI work:
169
-
170
- - `surface`
171
- - `text`
172
- - `component`
173
- - `buttons`
174
- - `forms`
175
- - `modes`
176
-
177
- These namespaces are the main consumer-facing contract because they express UI
178
- meaning rather than raw color selection.
179
-
180
- ### When raw palette access is acceptable
181
-
182
- Raw palette access through `colors` is acceptable when a consumer deliberately
183
- needs fixed palette values.
184
-
185
- Typical cases:
186
-
187
- - data visualization or non-semantic decorative use
188
- - compatibility layers that need a specific raw ramp
189
- - tooling that inspects or serializes palette data directly
190
-
191
- Raw palette access should not replace semantic token usage for normal UI
192
- surfaces, text, buttons, forms, or mode-aware styling.
193
-
194
231
  ### Token model
195
232
 
196
233
  The generated token object includes these namespaces:
@@ -259,6 +296,23 @@ Guidance:
259
296
  - Do not invent local light/dark token contracts when this package already
260
297
  provides the semantic path.
261
298
 
299
+ ## Protected token families
300
+
301
+ The following semantic groups are locked. Their values must not change without
302
+ explicit approval from Bradley Potts. This applies to all contributors and all
303
+ AI agents — apparent visual improvements still require human sign-off.
304
+
305
+ | Protected group | Backed by | Guarded by |
306
+ |---|---|---|
307
+ | `success` | `colors.success` palette | `check:locked` + `check:contrast` |
308
+ | `warning` | `colors.warning` palette | `check:locked` + `check:contrast` |
309
+ | `danger` semantic roles | `colors.error` palette | `check:locked` + `check:contrast` |
310
+ | CTA / primary action / brand-action | `colors.brand` + `buttons.cta` | `check:locked` + `check:contrast` |
311
+
312
+ `check:locked` fails immediately if any protected value changes from the
313
+ recorded baseline. An intentional change requires updating the baseline as part
314
+ of an approved, classified release.
315
+
262
316
  ## Downstream boundaries
263
317
 
264
318
  Downstream packages should never redefine locally:
@@ -291,6 +345,19 @@ If a downstream package depends on specific token paths or semantic meaning:
291
345
  - read `TOKEN_CONTRACT.md` for contract rules
292
346
  - prefer documented public namespaces over undocumented internal assumptions
293
347
 
348
+ ### Change classification
349
+
350
+ Every contract-affecting change is classified in `CHANGELOG.md [Unreleased]`
351
+ with a `Contract change type:` line before release.
352
+
353
+ | Classification | When to use | Examples |
354
+ |---|---|---|
355
+ | `additive` | New tokens, new paths, new CSS variables — existing consumers unaffected | Adding a namespace, adding a token inside an existing family |
356
+ | `semantic change` | Path stays the same but meaning, intent, or visual output shifts | Adjusting the role of an existing surface or text token |
357
+ | `breaking` | Existing consumers may need code changes | Renaming a token path, removing a namespace, changing mode names |
358
+
359
+ Renames and removals are always breaking regardless of perceived scope.
360
+
294
361
  ## Package exports / API surface
295
362
 
296
363
  ### Root package
@@ -351,30 +418,71 @@ For downstream packages and compatible apps:
351
418
 
352
419
  ## Development
353
420
 
354
- Regenerate package outputs:
421
+ Install dependencies, then run the package verification flow:
355
422
 
356
423
  ```bash
357
- npm run build
424
+ npm install
425
+ npm run check
358
426
  ```
359
427
 
360
- Run the full validation and release gate:
428
+ This project expects Node.js `^22.12.0 || >=24.0.0` and npm `11.14.1`.
361
429
 
362
- ```bash
363
- npm run check
364
- ```
430
+ ### Common commands
431
+
432
+ | Command | What it does |
433
+ |---|---|
434
+ | `npm run build` | Regenerate all outputs — run after any token source change |
435
+ | `npm run check` | Full validation gate — all 14 steps must pass before commit |
436
+ | `npm run lint` | Run ESLint against all source files |
437
+ | `npm run format` | Apply Prettier formatting to all files |
438
+ | `npm run generate` | Regenerate `src/generated/tokens.ts` from token sources only |
439
+ | `npm run check:manifest` | Validate public namespaces against `contract.manifest.json` |
440
+ | `npm run check:docs` | Validate README and TOKEN_CONTRACT headings against manifest |
441
+ | `npm run check:locked` | Confirm protected color families are unchanged |
442
+ | `npm run check:contrast` | Confirm all paired tokens meet WCAG AA |
443
+ | `npm run check:dist` | Confirm `dist/` artifacts are in sync with source |
365
444
 
366
- Key source areas:
445
+ ### Key source areas
367
446
 
368
- - `tokens/` for source token data
369
- - `src/generated/` for generated token output
370
- - `src/` for package entry points, CSS generation, and types
371
- - `scripts/` for build and validation scripts
372
- - `example/` for usage examples
447
+ - `tokens/` source token data (source of truth)
448
+ - `src/` package entry points, CSS generation, and public types
449
+ - `src/generated/` auto-generated output (do not edit directly)
450
+ - `scripts/` build and validation scripts
451
+ - `example/` usage examples and smoke consumer
373
452
 
374
453
  The files in `example/` are illustrative token demos only. They help explain the
375
454
  token contract, but they are not the package contract itself and should not be
376
455
  treated as downstream UI primitives.
377
456
 
457
+ ## Troubleshooting
458
+
459
+ | Failure | Cause | Fix |
460
+ |---|---|---|
461
+ | `check:regression` fails | A token value changed vs the recorded baseline | Revert the unintended change, or update the baseline if the change was intentional |
462
+ | `check:locked` fails | A protected color family was modified | Revert unless Bradley Potts has explicitly approved the change |
463
+ | `check:contrast` fails | A text/background token pair does not meet WCAG AA | Adjust the token value or the `metadata.pair` reference in the source JSON |
464
+ | `check:dist` fails | Generated dist is out of sync | Run `npm run build` then re-run `npm run check` |
465
+ | `check:manifest` fails | A namespace exists in outputs but is not declared in `contract.manifest.json` | Add the namespace to the manifest or remove it from the source |
466
+ | `check:docs` fails | README or TOKEN_CONTRACT.md has drifted from the manifest | Update the doc to match the current contract |
467
+ | `check:classification` fails | A contract-authority file changed without a classification entry | Add `Contract change type: additive`, `semantic change`, or `breaking` to `CHANGELOG.md [Unreleased]` |
468
+
469
+ ## AI and automation boundaries
470
+
471
+ Claude Code (`claude-sonnet-4-6`) is the primary development agent for this
472
+ repository. Codex handles releases and production stabilization. Jules handles
473
+ small automated fixes and generated-output sync. GitHub Copilot provides
474
+ development support.
475
+
476
+ Claude Code does not create git commits. All Claude Code changes are prepared
477
+ and validated, then handed off to Bradley Potts for human review and commit.
478
+ Jules commits bounded automated maintenance tasks autonomously when all
479
+ validation gates pass.
480
+
481
+ **Protected from automated change:** locked color families (`success`,
482
+ `warning`, `danger`, CTA/brand-action), `contract.manifest.json`, and
483
+ `src/generated/tokens.ts`. See [AGENTS.md](AGENTS.md) for full agent
484
+ governance and boundary rules.
485
+
378
486
  ## Contributing
379
487
 
380
488
  PHCDevworks maintains this package as part of the Spectre system.
package/dist/index.cjs CHANGED
@@ -156,9 +156,9 @@ var coreTokens = {
156
156
  "focusRing": "{colors.brand.500} / 0.4"
157
157
  },
158
158
  "accent": {
159
- "bg": "{colors.accent.600}",
160
- "bgHover": "{colors.accent.700}",
161
- "bgActive": "{colors.accent.800}",
159
+ "bg": "{colors.accent.700}",
160
+ "bgHover": "{colors.accent.800}",
161
+ "bgActive": "{colors.accent.900}",
162
162
  "bgDisabled": "{colors.accent.200}",
163
163
  "text": "{colors.white}",
164
164
  "textDisabled": "{colors.neutral.400}",
@@ -206,7 +206,7 @@ var coreTokens = {
206
206
  "page": "{colors.neutral.50}",
207
207
  "card": "{colors.white}",
208
208
  "input": "{colors.white}",
209
- "overlay": "{colors.neutral.900} / 0.6",
209
+ "overlay": "{colors.black} / 0.6",
210
210
  "alternate": "{colors.neutral.100}",
211
211
  "hero": "linear-gradient(135deg, {colors.indigo.500} 0%, {colors.violet.600} 100%)"
212
212
  },
@@ -628,7 +628,7 @@ var coreTokens = {
628
628
  "page": "{colors.neutral.50}",
629
629
  "card": "{colors.white}",
630
630
  "input": "{colors.white}",
631
- "overlay": "{colors.neutral.900} / 0.6"
631
+ "overlay": "{colors.black} / 0.6"
632
632
  },
633
633
  "text": {
634
634
  "onPage": {
@@ -850,6 +850,23 @@ var resolveValue = (tokens2, value) => {
850
850
  str = str.replace(opacityRegex, (match, hex, opacity) => hexToRgba(hex, opacity));
851
851
  return str;
852
852
  };
853
+ var resolveSemanticValue = (value, tokens2) => {
854
+ if (typeof value === "string" || typeof value === "number") {
855
+ return resolveValue(tokens2, value);
856
+ }
857
+ if (value && typeof value === "object" && "value" in value) {
858
+ return resolveValue(tokens2, value.value);
859
+ }
860
+ return void 0;
861
+ };
862
+ var getPath = (source, path) => path.reduce((acc, key) => acc && typeof acc === "object" ? acc[key] : void 0, source);
863
+ var pickSemantic = (tokens2, ...candidates) => {
864
+ for (const candidate of candidates) {
865
+ const resolved = resolveSemanticValue(candidate, tokens2);
866
+ if (resolved !== void 0) return resolved;
867
+ }
868
+ return void 0;
869
+ };
853
870
  var createCssVariableMap = (tokens2, options = {}) => {
854
871
  const prefix = options.prefix ?? DEFAULT_PREFIX;
855
872
  const map = {};
@@ -998,23 +1015,6 @@ var createCssVariableMap = (tokens2, options = {}) => {
998
1015
  }
999
1016
  return map;
1000
1017
  };
1001
- var resolveSemanticValue = (value, tokens2) => {
1002
- if (typeof value === "string" || typeof value === "number") {
1003
- return resolveValue(tokens2, value);
1004
- }
1005
- if (value && typeof value === "object" && "value" in value) {
1006
- return resolveValue(tokens2, value.value);
1007
- }
1008
- return void 0;
1009
- };
1010
- var getPath = (source, path) => path.reduce((acc, key) => acc && typeof acc === "object" ? acc[key] : void 0, source);
1011
- var pickSemantic = (tokens2, ...candidates) => {
1012
- for (const candidate of candidates) {
1013
- const resolved = resolveSemanticValue(candidate, tokens2);
1014
- if (resolved !== void 0) return resolved;
1015
- }
1016
- return void 0;
1017
- };
1018
1018
  var generateCssVariables = (tokens2, options = {}) => {
1019
1019
  const selector = options.selector ?? DEFAULT_SELECTOR;
1020
1020
  const prefix = options.prefix ?? DEFAULT_PREFIX;