@tenphi/tasty 0.0.0-snapshot.1d29633 → 0.0.0-snapshot.1f1c00a

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.
Files changed (122) hide show
  1. package/README.md +152 -134
  2. package/dist/{babel-Cg-mJV_t.d.ts → babel-rZcEwPLM.d.ts} +2 -3
  3. package/dist/chunks/async-storage-CG5HnUT5.js +20 -0
  4. package/dist/chunks/async-storage-CG5HnUT5.js.map +1 -0
  5. package/dist/chunks/build-config-CL4eRu2z.js +42 -0
  6. package/dist/chunks/build-config-CL4eRu2z.js.map +1 -0
  7. package/dist/{collector-BMT05AmU.js → chunks/collector-D2ARGT2L.js} +21 -117
  8. package/dist/chunks/collector-D2ARGT2L.js.map +1 -0
  9. package/dist/chunks/config-engine-DEmmPPdp.js +272 -0
  10. package/dist/chunks/config-engine-DEmmPPdp.js.map +1 -0
  11. package/dist/chunks/css-definitions-DotDkIXt.js +876 -0
  12. package/dist/chunks/css-definitions-DotDkIXt.js.map +1 -0
  13. package/dist/chunks/css-resources-P7IBUUZs.js +142 -0
  14. package/dist/chunks/css-resources-P7IBUUZs.js.map +1 -0
  15. package/dist/chunks/debug-D8pg9RGI.js +502 -0
  16. package/dist/chunks/debug-D8pg9RGI.js.map +1 -0
  17. package/dist/chunks/dsl-DRhFWQzo.js +1378 -0
  18. package/dist/chunks/dsl-DRhFWQzo.js.map +1 -0
  19. package/dist/chunks/hydration-TRcz5vnk.js +41 -0
  20. package/dist/chunks/hydration-TRcz5vnk.js.map +1 -0
  21. package/dist/chunks/react-runtime-BpxlOTbO.js +562 -0
  22. package/dist/chunks/react-runtime-BpxlOTbO.js.map +1 -0
  23. package/dist/chunks/runtime-engine-CKFXUH-A.js +2416 -0
  24. package/dist/chunks/runtime-engine-CKFXUH-A.js.map +1 -0
  25. package/dist/chunks/runtime-state-BxaLhVdP.js +127 -0
  26. package/dist/chunks/runtime-state-BxaLhVdP.js.map +1 -0
  27. package/dist/{merge-styles-CkAcnd0Z.js → chunks/shared-utils-y-HjvPN4.js} +60 -55
  28. package/dist/chunks/shared-utils-y-HjvPN4.js.map +1 -0
  29. package/dist/chunks/style-engine-BBbBuSkT.js +5260 -0
  30. package/dist/chunks/style-engine-BBbBuSkT.js.map +1 -0
  31. package/dist/{css-writer-b02uBqNF.js → chunks/zero-engine-gJrKBRiw.js} +6 -121
  32. package/dist/chunks/zero-engine-gJrKBRiw.js.map +1 -0
  33. package/dist/{collector-BFuqdF2F.d.ts → collector-w4mBj5gd.d.ts} +23 -4
  34. package/dist/{config-DmFxnBxg.d.ts → config-CZWCzBr6.d.ts} +171 -72
  35. package/dist/core/index.d.ts +5 -5
  36. package/dist/core/index.js +9 -6
  37. package/dist/{index-D1nMkpUe.d.ts → index-G9JuwM9p.d.ts} +263 -247
  38. package/dist/index.d.ts +5 -5
  39. package/dist/index.js +11 -921
  40. package/dist/{merge-styles-Cx_6ySBv.d.ts → merge-styles-VXwJ0Frr.d.ts} +2 -3
  41. package/dist/ssr/astro-client.d.ts +1 -1
  42. package/dist/ssr/astro-client.js +1 -14
  43. package/dist/ssr/astro-client.js.map +1 -1
  44. package/dist/ssr/astro-middleware-extract-static.d.ts +10 -0
  45. package/dist/ssr/astro-middleware-extract-static.js +9 -0
  46. package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
  47. package/dist/ssr/astro-middleware-extract.d.ts +10 -0
  48. package/dist/ssr/astro-middleware-extract.js +9 -0
  49. package/dist/ssr/astro-middleware-extract.js.map +1 -0
  50. package/dist/ssr/astro-middleware-static.d.ts +3 -2
  51. package/dist/ssr/astro-middleware-static.js +0 -11
  52. package/dist/ssr/astro-middleware-static.js.map +1 -1
  53. package/dist/ssr/astro-middleware.d.ts +3 -2
  54. package/dist/ssr/astro-middleware.js +0 -12
  55. package/dist/ssr/astro-middleware.js.map +1 -1
  56. package/dist/ssr/astro.d.ts +40 -6
  57. package/dist/ssr/astro.js +157 -93
  58. package/dist/ssr/astro.js.map +1 -1
  59. package/dist/ssr/index.d.ts +10 -11
  60. package/dist/ssr/index.js +4 -4
  61. package/dist/ssr/index.js.map +1 -1
  62. package/dist/ssr/next-config.d.ts +64 -0
  63. package/dist/ssr/next-config.js +128 -0
  64. package/dist/ssr/next-config.js.map +1 -0
  65. package/dist/ssr/next.d.ts +9 -8
  66. package/dist/ssr/next.js +29 -43
  67. package/dist/ssr/next.js.map +1 -1
  68. package/dist/static/index.d.ts +8 -9
  69. package/dist/static/index.js +4 -22
  70. package/dist/static/index.js.map +1 -1
  71. package/dist/static/inject.d.ts +1 -2
  72. package/dist/{index-LC3O3Jj4.d.ts → types-JEY8PVYP.d.ts} +862 -811
  73. package/dist/zero/babel.d.ts +1 -1
  74. package/dist/zero/babel.js +41 -107
  75. package/dist/zero/babel.js.map +1 -1
  76. package/dist/zero/index.d.ts +6 -7
  77. package/dist/zero/index.js +1 -1
  78. package/dist/zero/next.d.ts +3 -5
  79. package/dist/zero/next.js +0 -31
  80. package/dist/zero/next.js.map +1 -1
  81. package/docs/README.md +13 -8
  82. package/docs/adoption.md +56 -31
  83. package/docs/ai-agents.md +107 -80
  84. package/docs/comparison.md +69 -40
  85. package/docs/configuration.md +179 -114
  86. package/docs/debug.md +19 -7
  87. package/docs/design-system.md +41 -22
  88. package/docs/dsl.md +310 -201
  89. package/docs/getting-started.md +64 -41
  90. package/docs/injector.md +63 -26
  91. package/docs/methodology.md +44 -38
  92. package/docs/migration-v3.md +49 -49
  93. package/docs/pipeline.md +32 -8
  94. package/docs/plugins.md +38 -34
  95. package/docs/react-api.md +67 -44
  96. package/docs/runtime-benchmarks.md +388 -0
  97. package/docs/ssr.md +219 -61
  98. package/docs/styles.md +95 -18
  99. package/docs/tasty-static.md +138 -105
  100. package/package.json +104 -33
  101. package/dist/async-storage-DKK-wTD4.js +0 -44
  102. package/dist/async-storage-DKK-wTD4.js.map +0 -1
  103. package/dist/collector-BMT05AmU.js.map +0 -1
  104. package/dist/config-DF93rL3u.js +0 -12661
  105. package/dist/config-DF93rL3u.js.map +0 -1
  106. package/dist/context-CA8YKeMn.js +0 -24
  107. package/dist/context-CA8YKeMn.js.map +0 -1
  108. package/dist/core-B5ybnMP1.js +0 -1529
  109. package/dist/core-B5ybnMP1.js.map +0 -1
  110. package/dist/css-writer-b02uBqNF.js.map +0 -1
  111. package/dist/format-global-rules-DklyaXv-.js +0 -22
  112. package/dist/format-global-rules-DklyaXv-.js.map +0 -1
  113. package/dist/format-rules-Cs4e3M9m.js +0 -130
  114. package/dist/format-rules-Cs4e3M9m.js.map +0 -1
  115. package/dist/hydrate-aaUSNJZJ.js +0 -37
  116. package/dist/hydrate-aaUSNJZJ.js.map +0 -1
  117. package/dist/index.js.map +0 -1
  118. package/dist/keyframes-BedEzEXk.js +0 -424
  119. package/dist/keyframes-BedEzEXk.js.map +0 -1
  120. package/dist/merge-styles-CkAcnd0Z.js.map +0 -1
  121. package/dist/resolve-recipes-CiyLtJG9.js +0 -144
  122. package/dist/resolve-recipes-CiyLtJG9.js.map +0 -1
@@ -1,6 +1,8 @@
1
1
  # Configuration
2
2
 
3
- Configure the Tasty style system before your app renders using the `configure()` function. Configuration must be done **before any styles are generated** (before first render). For a higher-level docs map, see the [Docs Hub](README.md).
3
+ Tasty works without upfront configuration. Use `configure()` when your app or design system is ready to define shared tokens, state aliases, units, recipes, presets, or extension points.
4
+
5
+ Call it **before any styles are generated** (before first render). For a guided setup, see [Getting Started](getting-started.md). For the higher-level docs map, see the [Docs Hub](README.md).
4
6
 
5
7
  ```jsx
6
8
  import { configure } from '@tenphi/tasty';
@@ -45,35 +47,35 @@ These docs use `data-schema="dark"` in examples. If your app already standardize
45
47
 
46
48
  ## Options
47
49
 
48
- | Option | Type | Default | Description |
49
- |--------|------|---------|-------------|
50
- | `nonce` | `string` | - | CSP nonce for style elements |
51
- | `maxRulesPerSheet` | `number` | `8192` | Maximum rules per injected stylesheet |
52
- | `forceTextInjection` | `boolean` | auto (`true` in test envs) | Force text-node CSS injection instead of constructable stylesheets |
53
- | `devMode` | `boolean` | auto | Enable development-mode features: performance metrics and debug info |
54
- | `states` | `Record<string, string>` | - | Global state aliases for advanced state mapping |
55
- | `parserCacheSize` | `number` | `1000` | Parser LRU cache size |
56
- | `units` | `Record<string, string \| UnitHandler>` | Built-in | Custom units (merged with built-in). See [built-in units](dsl.md#built-in-units) |
57
- | `functions` | `Record<string, FunctionDefinition \| Function>` | - | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
58
- | `handlers` | `Record<string, StyleHandlerDefinition>` | Built-in | Custom style handlers (replace built-in). See [Custom Style Handlers](#custom-style-handlers) |
59
- | `propHandlers` | `Record<string, PropHandlerDefinition>` | - | Props middleware for every component — props in, props out. See [Props Middleware](#props-middleware) |
60
- | `baseStyleProps` | `readonly string[]` | - | Style names exposed as props on **every** component. See [Base Style Props](#base-style-props) |
61
- | `tokens` | `Record<string, value \| stateMap>` | - | Design tokens injected as `:root` CSS custom properties |
62
- | `replaceTokens` | `Record<string, string \| number \| boolean>` | - | Parse-time token substitution (inline replacement). `boolean` is allowed for `#` color tokens |
63
- | `keyframes` | `Record<string, KeyframesSteps>` | - | Global keyframes for animations |
64
- | `properties` | `Record<string, PropertyDefinition>` | - | Global CSS @property definitions |
65
- | `fontFaces` | `Record<string, FontFaceInput>` | - | Global @font-face definitions |
66
- | `counterStyles` | `Record<string, CounterStyleDescriptors>` | - | Global @counter-style definitions |
67
- | `polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills for not-yet-baseline features. `functions: true` inlines `@function` calls into plain CSS at parse time |
68
- | `autoPropertyTypes` | `boolean` | `true` | Auto-infer and register `@property` types from values |
69
- | `recipes` | `Record<string, RecipeStyles>` | - | Predefined style recipes (named style bundles) |
70
- | `presets` | `Record<string, TypographyPreset>` | - | Typography presets — shorthand for `generateTypographyTokens()` |
71
- | `globalStyles` | `Record<string, Styles>` | - | Global Tasty styles keyed by CSS selector |
72
- | `plugins` | `TastyPlugin[]` | - | Plugins that bundle any of the above (processed in order; later override earlier, and direct config wins over all). See [Plugins](plugins.md) |
73
- | `gc` | `GCConfig` | - | Garbage-collection tuning for unused styles (`{ touchInterval, capacity }`) |
74
- | `batchInjection` | `boolean \| 'always'` | `false` | Defer stylesheet writes and apply them in one batch. See [Batched injection](#batched-injection) |
75
- | `colorSpace` | `'rgb' \| 'hsl' \| 'oklch'` | `'oklch'` | Color space a statically known color is emitted in |
76
- | `namePrefix` | `string` | `'t'` (runtime) / `'ts'` (zero-runtime) | Prefix prepended to every generated identifier (class, keyframe, counter-style names). Must match `^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$`. See [Name prefix](#name-prefix). |
50
+ | Option | Type | Default | Description |
51
+ | -------------------- | ------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
+ | `nonce` | `string` | - | CSP nonce for style elements |
53
+ | `maxRulesPerSheet` | `number` | `8192` | Maximum rules per injected stylesheet |
54
+ | `forceTextInjection` | `boolean` | auto (`true` in test envs) | Force text-node CSS injection instead of constructable stylesheets |
55
+ | `devMode` | `boolean` | auto | Enable development-mode features: performance metrics and debug info |
56
+ | `states` | `Record<string, string>` | - | Global state aliases for advanced state mapping |
57
+ | `parserCacheSize` | `number` | `1000` | Parser LRU cache size |
58
+ | `units` | `Record<string, string \| UnitHandler>` | Built-in | Custom units (merged with built-in). See [built-in units](dsl.md#built-in-units) |
59
+ | `functions` | `Record<string, FunctionDefinition \| Function>` | - | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
60
+ | `handlers` | `Record<string, StyleHandlerDefinition>` | Built-in | Custom style handlers (replace built-in). See [Custom Style Handlers](#custom-style-handlers) |
61
+ | `propHandlers` | `Record<string, PropHandlerDefinition>` | - | Props middleware for every component — props in, props out. See [Props Middleware](#props-middleware) |
62
+ | `baseStyleProps` | `readonly string[]` | - | Style names exposed as props on **every** component. See [Base Style Props](#base-style-props) |
63
+ | `tokens` | `Record<string, value \| stateMap>` | - | Design tokens injected as `:root` CSS custom properties |
64
+ | `replaceTokens` | `Record<string, string \| number \| boolean>` | - | Parse-time token substitution (inline replacement). `boolean` is allowed for `#` color tokens |
65
+ | `keyframes` | `Record<string, KeyframesSteps>` | - | Global keyframes for animations |
66
+ | `properties` | `Record<string, PropertyDefinition>` | - | Global CSS @property definitions |
67
+ | `fontFaces` | `Record<string, FontFaceInput>` | - | Global @font-face definitions |
68
+ | `counterStyles` | `Record<string, CounterStyleDescriptors>` | - | Global @counter-style definitions |
69
+ | `polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills for not-yet-baseline features. `functions: true` inlines `@function` calls into plain CSS at parse time |
70
+ | `autoPropertyTypes` | `boolean` | `true` | Auto-infer and register `@property` types from values |
71
+ | `recipes` | `Record<string, RecipeStyles>` | - | Predefined style recipes (named style bundles) |
72
+ | `presets` | `Record<string, TypographyPreset>` | - | Typography presets — shorthand for `generateTypographyTokens()` |
73
+ | `globalStyles` | `Record<string, Styles>` | - | Global Tasty styles keyed by CSS selector |
74
+ | `plugins` | `TastyPlugin[]` | - | Plugins that bundle any of the above (processed in order; later override earlier, and direct config wins over all). See [Plugins](plugins.md) |
75
+ | `gc` | `GCConfig` | - | Garbage-collection tuning for unused styles (`{ touchInterval, capacity }`) |
76
+ | `batchInjection` | `boolean \| 'always'` | `false` | Defer stylesheet writes and apply them in one batch. See [Batched injection](#batched-injection) |
77
+ | `colorSpace` | `'rgb' \| 'hsl' \| 'oklch'` | - | **Deprecated** — no longer has any effect. See [Color space](#color-space) |
78
+ | `namePrefix` | `string` | `'t'` (`tasty`) / `'ts'` (build-time) | Prefix prepended to every generated identifier (class, keyframe, counter-style names). Must match `^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$`. See [Name prefix](#name-prefix). |
77
79
 
78
80
  ---
79
81
 
@@ -107,10 +109,10 @@ createRoot(el).render(
107
109
  ### Why the provider is required
108
110
 
109
111
  Deferring a write past React's layout phase would let a `useLayoutEffect`
110
- measure an element whose rules are not in the sheet yet and read its *unstyled*
112
+ measure an element whose rules are not in the sheet yet and read its _unstyled_
111
113
  box — a wrong number, not a stale one, so it never self-corrects.
112
114
 
113
- `TastyBatchProvider` closes that hole. It opens a *batch window* during its
115
+ `TastyBatchProvider` closes that hole. It opens a _batch window_ during its
114
116
  render and closes it — flushing — in `useInsertionEffect`, which React runs in
115
117
  the mutation phase, before any layout effect:
116
118
 
@@ -135,11 +137,11 @@ commits it takes part in.
135
137
 
136
138
  ### Modes
137
139
 
138
- | Value | Behaviour |
139
- |-------|-----------|
140
- | `false` (default) | One `insertRule()` per component, synchronously. |
141
- | `true` | Batch inside a provider window only. Cannot affect measurement. Without the provider nothing is batched, and dev mode says so once. |
142
- | `'always'` | Batch every injection, flushing on a microtask when no window is open. Covers more commits; a `useLayoutEffect` measuring a freshly mounted element can read its unstyled box. Paint is unaffected — microtasks always drain before the browser paints. |
140
+ | Value | Behaviour |
141
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
142
+ | `false` (default) | One `insertRule()` per component, synchronously. |
143
+ | `true` | Batch inside a provider window only. Cannot affect measurement. Without the provider nothing is batched, and dev mode says so once. |
144
+ | `'always'` | Batch every injection, flushing on a microtask when no window is open. Covers more commits; a `useLayoutEffect` measuring a freshly mounted element can read its unstyled box. Paint is unaffected — microtasks always drain before the browser paints. |
143
145
 
144
146
  ### Ordering
145
147
 
@@ -148,7 +150,7 @@ All writes share one FIFO queue — component rules, global rules, raw CSS,
148
150
  Draining it in insertion order keeps the sheet byte-identical to unbatched
149
151
  output, which matters because equal-specificity rules resolve by document order.
150
152
 
151
- ### SSR, RSC and zero-runtime
153
+ ### Client rendering, server rendering, and build-time extraction
152
154
 
153
155
  **Server render — nothing to batch, safe to leave enabled.** SSR and RSC collect
154
156
  CSS as text through `ServerStyleCollector`; the runtime injector never runs
@@ -173,12 +175,12 @@ tasty components to be worth it. `'always'` needs no provider and covers every
173
175
  island, at the cost of the measurement hazard above. Either way, an island that
174
176
  only re-hydrates server-rendered classes has nothing to batch.
175
177
 
176
- **Zero-runtime (`tastyStatic`) — unaffected.** Build-time extraction never
178
+ **Build-time extraction (`tastyStatic`) — unaffected.** The Babel plugin never
177
179
  touches the injector: the babel plugin emits either a CSS file import or an
178
180
  `injectCSS()` call from `@tenphi/tasty/static/inject`, which appends text to a
179
181
  single `<style data-tasty-static>` element. `batchInjection` only defers CSSOM
180
- writes made by the runtime injector, so extracted styles are unchanged. In an app
181
- that mixes both, it still applies to the runtime half.
182
+ writes made by the browser injector, so extracted styles are unchanged. In an app
183
+ that mixes both, it still applies to the `tasty()` half.
182
184
 
183
185
  ### `flushStyles()`
184
186
 
@@ -205,45 +207,84 @@ useLayoutEffect(() => {
205
207
 
206
208
  ## Color Space
207
209
 
208
- Controls the CSS color space a statically known color is emitted in. A `#name`
209
- token declares one variable, `--name-color`, and its value is converted into the
210
- configured space when the engine can evaluate it at build time.
210
+ > **Deprecated.** `configure({ colorSpace })` no longer has any effect and will
211
+ > be removed in the next major. Setting it warns in development.
212
+
213
+ A `#name` token's value used to be rewritten into a configured color space, so
214
+ `#brand: '#ff8800'` declared `--brand-color: oklch(0.75 0.16 55)`. That existed
215
+ to serve the opacity suffix, which needed numeric channels to write an alpha
216
+ into. Opacity now uses CSS relative color syntax —
217
+ `oklch(from var(--brand-color) l c h / .5)` — which has the browser read the
218
+ channels, so there is nothing left for the setting to decide.
219
+
220
+ A color is emitted exactly as authored:
211
221
 
212
222
  ```jsx
213
- configure({
214
- colorSpace: 'oklch', // default
215
- });
223
+ configure({ tokens: { '#brand': '#ff8800' } });
224
+ // → --brand-color: #ff8800
225
+ ```
226
+
227
+ That holds for every form: a hex literal, a native color function, a bare CSS
228
+ color name, a `color-mix()`, a `light-dark()`, a fallback chain. A `#token`
229
+ reference still resolves to its `var()` chain, and a plugin color function such
230
+ as `okhsl()` is still resolved by the parser to the color it denotes.
231
+
232
+ To address a token's channels, use relative color syntax against the token:
233
+
234
+ ```css
235
+ background: oklch(from var(--brand-color) calc(l * 1.2) c h);
236
+ ```
237
+
238
+ This works on any `<color>`, including the ones no build-time conversion could
239
+ have evaluated — and it is what the [opacity suffix](dsl.md#color-tokens--opacity)
240
+ itself uses. Its space is always `oklch`, which is unbounded, so a wide-gamut
241
+ color survives a round trip a narrower space would clamp.
242
+
243
+ ### Migrating off `colorSpace`
244
+
245
+ Drop the option. If you relied on the uniform output format, nothing in Tasty
246
+ needs it — author your tokens in the space you want them emitted in, since the
247
+ value now passes through untouched:
248
+
249
+ ```jsx
250
+ // before: any input, normalized to the configured space on the way out
251
+ configure({ colorSpace: 'oklch', tokens: { '#brand': '#ff8800' } });
252
+
253
+ // after: author it in the space you want
254
+ configure({ tokens: { '#brand': 'oklch(0.75 0.16 55)' } });
216
255
  ```
217
256
 
218
- | Color Space | `#brand: '#ff8800'` emits |
219
- |---|---|
220
- | `rgb` | `--brand-color: rgb(255 136 0)` |
221
- | `hsl` | `--brand-color: hsl(32 100% 50%)` |
222
- | `oklch` | `--brand-color: oklch(0.75 0.16 55)` |
257
+ ### Migrating off the channel companions
223
258
 
224
- The `oklch` color space is the default because it is perceptually uniform and
225
- wide-gamut, so a channel read off it — to shift a lightness, animate a hue,
226
- build a derived color — behaves the way the eye expects and is not clamped to
227
- sRGB.
259
+ A `#name` token used to declare a second variable beside `--name-color` holding
260
+ its channels decomposed, suffixed with the configured space —
261
+ `--brand-color-oklch: 0.75 0.16 55` — and a `color` style emitted
262
+ `--current-color-{space}` beside `--current-color`. Both are gone, for the same
263
+ reason: they existed so an opacity suffix had channels to write an alpha into.
228
264
 
229
- A color the engine cannot evaluate at build time — a `color-mix()`, a
230
- `light-dark()`, a `color()` in a space it has no conversion for, anything reached
231
- through `var()` — is emitted as authored and left for the browser to resolve.
265
+ These were never part of the public API, but they were visible in the emitted
266
+ CSS, so hand-authored CSS may reference one. Address the token itself instead:
232
267
 
233
- The setting does not affect the [opacity suffix](dsl.md#color-tokens--opacity),
234
- which always writes `oklch(from <color> l c h / <alpha>)`: alpha application is
235
- space-independent, and `oklch` is unbounded, so a wide-gamut color survives a
236
- round trip a narrower space would clamp.
268
+ ```css
269
+ /* before */
270
+ color: oklch(var(--brand-color-oklch) / 0.5);
271
+ background: oklch(var(--brand-color-oklch));
272
+
273
+ /* after */
274
+ color: oklch(from var(--brand-color) l c h / 0.5);
275
+ background: var(--brand-color);
276
+ ```
237
277
 
238
- To address a token's channels yourself, use relative color syntax against the
239
- token — `oklch(from var(--brand-color) calc(l * 1.2) c h)` — which works on any
240
- color, including the ones the engine cannot decompose.
278
+ The replacement is strictly more capable — the companion could only be
279
+ decomposed for a color the engine could evaluate at build time, while relative
280
+ color syntax works on every `<color>`, including a `color-mix()`, a
281
+ `light-dark()`, and a variable Tasty never defined.
241
282
 
242
283
  ---
243
284
 
244
285
  ## Name Prefix
245
286
 
246
- Every identifier Tasty generates — class names, keyframe names, counter-style names — starts with a configurable prefix. The runtime, SSR, and RSC paths default to `'t'`; the zero-runtime build path (`tastyStatic` via the Babel plugin) defaults to `'ts'` so static-extracted classes can never collide with runtime classes when both are loaded on the same page.
287
+ Every identifier Tasty generates — class names, keyframe names, counter-style names — starts with a configurable prefix. The `tasty()` client, SSR, and RSC paths default to `'t'`; the build-time extraction path (`tastyStatic` via the Babel plugin) defaults to `'ts'` so extracted classes can never collide with `tasty()` classes when both are loaded on the same page.
247
288
 
248
289
  ```jsx
249
290
  configure({
@@ -253,12 +294,12 @@ configure({
253
294
 
254
295
  The prefix is prepended verbatim to the hash, so include any separator inside the prefix string itself:
255
296
 
256
- | Setting | Class | Keyframe | Counter-style |
257
- |---|---|---|---|
258
- | `'t'` (runtime default) | `t1a2b3` | `tk1a2b3` | `tc1a2b3` |
259
- | `'ts'` (zero-runtime default) | `ts1a2b3` | `tsk1a2b3` | `tsc1a2b3` |
260
- | `'mb'` | `mb1a2b3` | `mbk1a2b3` | `mbc1a2b3` |
261
- | `'myapp-'` | `myapp-1a2b3` | `myapp-k1a2b3` | `myapp-c1a2b3` |
297
+ | Setting | Class | Keyframe | Counter-style |
298
+ | --------------------------- | ------------- | -------------- | -------------- |
299
+ | `'t'` (`tasty` default) | `t1a2b3` | `tk1a2b3` | `tc1a2b3` |
300
+ | `'ts'` (build-time default) | `ts1a2b3` | `tsk1a2b3` | `tsc1a2b3` |
301
+ | `'mb'` | `mb1a2b3` | `mbk1a2b3` | `mbc1a2b3` |
302
+ | `'myapp-'` | `myapp-1a2b3` | `myapp-k1a2b3` | `myapp-c1a2b3` |
262
303
 
263
304
  The single-letter discriminators (`k` for keyframes, `c` for counter-styles) keep the three kinds visually distinct in devtools — they are not required for correctness because CSS keeps these in separate namespaces.
264
305
 
@@ -268,12 +309,12 @@ The single-letter discriminators (`k` for keyframes, `c` for counter-styles) kee
268
309
  - Validated at `configure()` time; an invalid prefix throws synchronously rather than silently producing broken hydration.
269
310
  - Locked once styles have been generated, like all other config.
270
311
 
271
- ### Coexistence with the zero-runtime build
312
+ ### Coexistence with build-time extraction
272
313
 
273
- The runtime and zero-runtime builds **must use different prefixes** when both are loaded on the same page. Defaults already guarantee this; if you customize one, customize the other accordingly:
314
+ The `tasty()` and `tastyStatic()` paths **must use different prefixes** when both are loaded on the same page. Defaults already guarantee this; if you customize one, customize the other accordingly:
274
315
 
275
316
  ```jsx
276
- // app config (runtime / SSR / RSC)
317
+ // app config (client / SSR / RSC)
277
318
  configure({ namePrefix: 'mb' });
278
319
 
279
320
  // tasty-zero.config.ts (Babel plugin)
@@ -293,8 +334,8 @@ Tokens support state maps for responsive or theme-aware values:
293
334
  ```jsx
294
335
  configure({
295
336
  tokens: {
296
- '$gap': '4px',
297
- '$radius': '6px',
337
+ $gap: '4px',
338
+ $radius: '6px',
298
339
  '#primary': {
299
340
  '': '#purple',
300
341
  '@dark': '#light-purple',
@@ -311,7 +352,7 @@ configure({
311
352
  - `#name` keys become `--name-color` custom properties
312
353
  - Names keep their case, since CSS custom properties are case-sensitive: `$myVar` is `--myVar` and is referenced as `$myVar`. A leading capital is not supported and folds — `$Foo` and `#Purple` become `--foo` and `--purple-color` — so start names lowercase. Kebab-case (`$my-var`) remains the convention.
313
354
 
314
- Tokens are automatically emitted in all rendering modes: runtime (client), SSR, and zero-runtime (Babel plugin).
355
+ Tokens are automatically emitted in all delivery modes: client rendering, server rendering, and build-time extraction with the Babel plugin.
315
356
 
316
357
  ---
317
358
 
@@ -365,7 +406,7 @@ configure({
365
406
  });
366
407
  ```
367
408
 
368
- Now any component can use `fontFamily: '"Brand Sans", sans-serif'` and the browser will already have the `@font-face` rules in the stylesheet.
409
+ Now any component can use `font: '"Brand Sans", sans-serif'` and the browser will already have the `@font-face` rules in the stylesheet.
369
410
 
370
411
  See [Font Face (`@font-face`)](dsl.md#font-face-font-face) for inline usage inside component styles and the full list of supported descriptors.
371
412
 
@@ -422,7 +463,7 @@ configure({
422
463
  });
423
464
  ```
424
465
 
425
- Components then invoke parse functions as `double(...)` and CSS functions with the `$$name(...)` sugar, e.g. `marginTop: '$$negative(10px)'`.
466
+ Components then invoke parse functions as `double(...)` and CSS functions with the `$$name(...)` sugar, e.g. `margin: '$$negative(10px) top'`.
426
467
 
427
468
  > A key whose prefix doesn't match its value type (an object under a bare key, or a function under a `$$` key) is **ignored with a dev-mode warning**.
428
469
 
@@ -430,7 +471,7 @@ See [Functions (`@function`)](dsl.md#functions-function) for inline usage inside
430
471
 
431
472
  ### Custom color functions
432
473
 
433
- A parse function whose output is an already-supported color (`rgb`, `hsl`, `#…`, `oklch`, …) is treated as a **color function**: it works everywhere a color is accepted — style values, `#token.alpha` opacity injection, conversion into the configured color space, and `parseColor` — with no extra registration. This is the same mechanism the built-in `okhsl`/`okhst` plugins use; they are ordinary plugins registered by default.
474
+ A parse function whose output is an already-supported color (`rgb`, `hsl`, `#…`, `oklch`, …) is treated as a **color function**: it works everywhere a color is accepted — style values, `#token.alpha` opacity injection, and `parseColor` — with no extra registration. This is the same mechanism the built-in `okhsl`/`okhst` plugins use; they are ordinary plugins registered by default.
434
475
 
435
476
  ```ts
436
477
  import { configure, createColorFunc } from '@tenphi/tasty';
@@ -472,7 +513,7 @@ configure({
472
513
  },
473
514
  });
474
515
 
475
- // Now `marginTop: '$$negative(10px)'` renders `margin-top: calc(-1 * 10px)`
516
+ // Now `margin: '$$negative(10px) top'` renders `margin: calc(-1 * 10px) 0 0 0`
476
517
  // — no native @function rule is emitted.
477
518
  ```
478
519
 
@@ -508,7 +549,23 @@ configure({
508
549
  });
509
550
  ```
510
551
 
511
- Recipe values are flat tasty styles (no sub-element keys). They may contain base styles, tokens, local states, `@keyframes`, and `@property`. Recipes cannot reference other recipes.
552
+ Recipe values are flat tasty styles (no sub-element keys). They may contain base styles, tokens, local states, `@keyframes`, and `@property`.
553
+
554
+ **Recipes cannot reference other recipes.** A `recipe` key inside a configured recipe's value is not recursively resolved:
555
+
556
+ ```jsx
557
+ // Unsupported: applying 'elevatedCard' does not expand 'card elevated'.
558
+ configure({
559
+ recipes: {
560
+ elevatedCard: {
561
+ recipe: 'card elevated',
562
+ padding: '6x',
563
+ },
564
+ },
565
+ });
566
+ ```
567
+
568
+ Instead, compose the names where you apply the styles: `styles: { recipe: 'card elevated', padding: '6x' }`. Recipe references are resolved on the component root and independently inside each sub-element's styles (for example, `styles: { Panel: { recipe: 'card elevated' } }`). This sub-element support does not allow one recipe definition to inherit another. Tasty warns in development when a recipe definition contains a `recipe` key.
512
569
 
513
570
  For how to apply, compose, and override recipes in components, see [Recipes](dsl.md#recipes) in the Style DSL reference.
514
571
 
@@ -565,7 +622,7 @@ configure({
565
622
  color: '#text',
566
623
  preset: 't2',
567
624
  margin: 0,
568
- fontFamily: 'system-ui, sans-serif',
625
+ font: 'system-ui, sans-serif',
569
626
  },
570
627
  html: {
571
628
  overflow: 'hidden',
@@ -576,7 +633,7 @@ configure({
576
633
 
577
634
  Each key is a CSS selector, and each value is a Tasty `Styles` object supporting the full style syntax including style properties, tokens, state maps, and selector-based sub-styling (e.g. `$: '> .app'` for elements outside React scope). Global styles are injected alongside `:root` tokens when the first style is rendered.
578
635
 
579
- Global styles are automatically emitted in all rendering modes: runtime (client), SSR, and zero-runtime (Babel plugin). Plugins can also provide `globalStyles`; they are merged per selector with config global styles (config wins on conflict).
636
+ Global styles are automatically emitted in all delivery modes: client rendering, server rendering, and build-time extraction with the Babel plugin. Plugins can also provide `globalStyles`; they are merged per selector with config global styles (config wins on conflict).
580
637
 
581
638
  ---
582
639
 
@@ -584,18 +641,18 @@ Global styles are automatically emitted in all rendering modes: runtime (client)
584
641
 
585
642
  CSS cannot transition or animate custom properties unless their type is declared via [`@property`](https://developer.mozilla.org/en-US/docs/Web/CSS/@property). Tasty handles this automatically — when a custom property is assigned a concrete value (e.g. `'$scale': 1`, `'$gap': '10px'`, `'#accent': 'purple'`), the type is inferred and a `@property` rule is registered.
586
643
 
587
- This works across all declaration contexts: component styles, `@keyframes`, global config, and the zero-runtime Babel plugin. It also resolves `var()` chains — if `$a` references `var(--b)`, the type propagates once `--b` is resolved.
644
+ This works across all declaration contexts: component styles, `@keyframes`, global config, and build-time extraction with the Babel plugin. It also resolves `var()` chains — if `$a` references `var(--b)`, the type propagates once `--b` is resolved.
588
645
 
589
646
  Supported types:
590
647
 
591
- | Detection | Inferred syntax |
592
- |-----------|-----------------|
593
- | `1`, `0.5`, `-3` (bare numbers) | `<number>` |
594
- | `10px`, `2rem`, `100vw` (length units) | `<length>` |
595
- | `50%` | `<percentage>` |
596
- | `45deg`, `0.5turn` (angle units) | `<angle>` |
597
- | `300ms`, `1s` (time units) | `<time>` |
598
- | `#name` tokens (by naming convention) | `<color>` |
648
+ | Detection | Inferred syntax |
649
+ | -------------------------------------- | --------------- |
650
+ | `1`, `0.5`, `-3` (bare numbers) | `<number>` |
651
+ | `10px`, `2rem`, `100vw` (length units) | `<length>` |
652
+ | `50%` | `<percentage>` |
653
+ | `45deg`, `0.5turn` (angle units) | `<angle>` |
654
+ | `300ms`, `1s` (time units) | `<time>` |
655
+ | `#name` tokens (by naming convention) | `<color>` |
599
656
 
600
657
  Auto-inferred properties use `inherits: true` (the CSS default). Use explicit `@property` when you need different settings:
601
658
 
@@ -627,11 +684,11 @@ configure({ autoPropertyTypes: false });
627
684
 
628
685
  Override or extend the built-in style property handlers. A handler definition can take three forms:
629
686
 
630
- | Form | Syntax | Description |
631
- |------|--------|-------------|
632
- | Function only | `handler` | Triggered by its key name; receives only that property |
633
- | Single dep | `['styleName', handler]` | Triggered by the specified style property |
634
- | Multi dep | `[['dep1', 'dep2', ...], handler]` | Triggered by any of the listed properties; receives all of them |
687
+ | Form | Syntax | Description |
688
+ | ------------- | ---------------------------------- | --------------------------------------------------------------- |
689
+ | Function only | `handler` | Triggered by its key name; receives only that property |
690
+ | Single dep | `['styleName', handler]` | Triggered by the specified style property |
691
+ | Multi dep | `[['dep1', 'dep2', ...], handler]` | Triggered by any of the listed properties; receives all of them |
635
692
 
636
693
  The multi-dep form is useful when output depends on several style properties together (e.g., `gap` needs to know `display` and `flow` to decide the CSS strategy). Use `defineHandler` for it and the dependency types are inferred from the dependency list, so a typo in the destructure is a type error instead of a silent `undefined`.
637
694
 
@@ -653,11 +710,19 @@ configure({
653
710
  // them all through, or the ones you leave out stop working (see below).
654
711
  fill: defineHandler(
655
712
  [
656
- 'fill', 'backgroundColor', 'image', 'backgroundImage',
657
- 'backgroundPosition', 'backgroundSize', 'backgroundRepeat',
713
+ 'fill',
714
+ 'backgroundColor',
715
+ 'image',
716
+ 'backgroundImage',
717
+ 'backgroundPosition',
718
+ 'backgroundSize',
719
+ 'backgroundRepeat',
658
720
  ],
659
721
  (props) => {
660
- if (typeof props.fill === 'string' && props.fill.startsWith('gradient:')) {
722
+ if (
723
+ typeof props.fill === 'string' &&
724
+ props.fill.startsWith('gradient:')
725
+ ) {
661
726
  return { background: props.fill.slice(9) };
662
727
  }
663
728
  return styleHandlers.fill(props);
@@ -691,18 +756,18 @@ Built-in handlers are shared across several style names. Registering a handler f
691
756
 
692
757
  The shared groups worth knowing:
693
758
 
694
- | Registering a handler for | Also takes over |
695
- |---|---|
696
- | `fill` | `backgroundColor`, `image`, `backgroundImage`, `backgroundPosition`, `backgroundSize`, `backgroundRepeat` |
697
- | `display` | `hide`, `overflow`, `whiteSpace`, `textOverflow`, `flow`, `gap` |
698
- | `preset` | `font`, `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`, `letterSpacing`, `textTransform` |
699
- | `padding` / `margin` / `inset` | their `*Top`/`*Right`/`*Bottom`/`*Left`/`*Block`/`*Inline` longhands |
759
+ | Registering a handler for | Also takes over |
760
+ | ------------------------------ | --------------------------------------------------------------------------------------------------------- |
761
+ | `fill` | `backgroundColor`, `image`, `backgroundImage`, `backgroundPosition`, `backgroundSize`, `backgroundRepeat` |
762
+ | `display` | `hide`, `overflow`, `whiteSpace`, `textOverflow`, `flow`, `gap` |
763
+ | `preset` | `font`, `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`, `letterSpacing`, `textTransform` |
764
+ | `padding` / `margin` / `inset` | their `*Top`/`*Right`/`*Bottom`/`*Left`/`*Block`/`*Inline` longhands |
700
765
 
701
766
  Either declare the whole group and delegate the rest to `styleHandlers.*`, or pick a name that isn't shared.
702
767
 
703
768
  ### Chunk membership
704
769
 
705
- Tasty renders and caches CSS in independent chunks, and a chunk's cache key covers only its own style values. All of a handler's dependencies must therefore live in one chunk. Custom style names are pulled into their handler's chunk automatically at registration; a handler whose dependencies span two *built-in* chunks (say `fill` and `padding`) warns, because it would be invoked once per chunk with a subset of its inputs.
770
+ Tasty renders and caches CSS in independent chunks, and a chunk's cache key covers only its own style values. All of a handler's dependencies must therefore live in one chunk. Custom style names are pulled into their handler's chunk automatically at registration; a handler whose dependencies span two _built-in_ chunks (say `fill` and `padding`) warns, because it would be invoked once per chunk with a subset of its inputs.
706
771
 
707
772
  `configure({ handlers })` must run before the first render, like every other config option.
708
773
 
@@ -710,7 +775,7 @@ Tasty renders and caches CSS in independent chunks, and a chunk's cache key cove
710
775
 
711
776
  ## Props Middleware
712
777
 
713
- `propHandlers` are middleware over a component's props — props in, props out. Where a style *handler* turns a style property into CSS declarations, a prop handler turns a **component prop** into other props, including `styles`. It is the extension point for props whose value isn't a style value.
778
+ `propHandlers` are middleware over a component's props — props in, props out. Where a style _handler_ turns a style property into CSS declarations, a prop handler turns a **component prop** into other props, including `styles`. It is the extension point for props whose value isn't a style value.
714
779
 
715
780
  ```jsx
716
781
  import { configure, mergeStyles } from '@tenphi/tasty';
@@ -726,7 +791,7 @@ configure({
726
791
  },
727
792
  });
728
793
 
729
- <Element glaze="purple" />
794
+ <Element glaze="purple" />;
730
795
  ```
731
796
 
732
797
  The map key is the handler's name and, by default, the prop that triggers it, so an absent prop costs one property check rather than a call. A tuple overrides that: `['glaze', fn]`, `[['glaze', 'tint'], fn]`, or `['*', fn]` for unconditional.
@@ -735,7 +800,7 @@ Handlers run at the very top of every component's render, before any prop is des
735
800
 
736
801
  Handlers must be **pure** and must not mutate their input, and should memoize the styles they build per input value: style values are cached by object identity, so mutating one in place yields stale CSS, and a reference-stable object avoids re-serializing on every render.
737
802
 
738
- Injected styles occupy the `styles` slot, so they beat a component's own default styles and lose to a style prop at the call site. Prop handlers do not apply to sub-elements or to zero-runtime `tastyStatic()`, which has no props.
803
+ Injected styles occupy the `styles` slot, so they beat a component's own default styles and lose to a style prop at the call site. Prop handlers do not apply to sub-elements or to build-time `tastyStatic()`, which has no props.
739
804
 
740
805
  See [Plugins → Props middleware](plugins.md#props-middleware) for the full contract and a worked example.
741
806
 
@@ -748,7 +813,7 @@ See [Plugins → Props middleware](plugins.md#props-middleware) for the full con
748
813
  ```jsx
749
814
  configure({ baseStyleProps: ['radius', 'shadow'] });
750
815
 
751
- <Card radius="1r" shadow />
816
+ <Card radius="1r" shadow />;
752
817
  ```
753
818
 
754
819
  `configure()` may run after your components are defined — each factory resolves its prop list lazily.
package/docs/debug.md CHANGED
@@ -6,9 +6,7 @@ Runtime CSS inspection and diagnostics for the Tasty styling system. Inspect inj
6
6
 
7
7
  ## Overview
8
8
 
9
- `tastyDebug` is a diagnostic object that exposes Tasty's runtime CSS state. It is designed for development use but can be manually installed in production for debugging.
10
-
11
- In development mode (`isDevEnv()` returns `true`), `tastyDebug` is automatically installed on `window.tastyDebug`. In production, install it manually when needed.
9
+ `tastyDebug` is a diagnostic object that exposes Tasty's runtime CSS state. It is designed for development use and can be installed on `window` explicitly when needed.
12
10
 
13
11
  All methods **log to the console by default**. Pass `{ raw: true }` to suppress logging and only return data.
14
12
 
@@ -19,7 +17,6 @@ All methods **log to the console by default**. Pass `{ raw: true }` to suppress
19
17
  ## Quick Start
20
18
 
21
19
  ```typescript
22
- // Auto-installed in dev mode. Otherwise:
23
20
  import { tastyDebug } from '@tenphi/tasty';
24
21
  tastyDebug.install();
25
22
 
@@ -54,6 +51,19 @@ interface DebugOptions {
54
51
 
55
52
  When `raw` is `false` (the default), results are logged to the console **and** returned. When `raw` is `true`, results are returned silently.
56
53
 
54
+ ### Environments Without a DOM
55
+
56
+ `tastyDebug` reads the document, so on a server — SSR, a Node REPL, a test
57
+ runner in the `node` environment — there is nothing for it to read. Every
58
+ method returns its empty result (`''`, an empty `InspectResult`, a summary of
59
+ zeroes, `metrics: null`) instead of throwing, and explains itself once with a
60
+ console warning. `{ raw: true }` suppresses that warning along with the rest of
61
+ the logging.
62
+
63
+ To see the styles a server render produced, read the `ServerStyleCollector`
64
+ instead — `collector.getCSS()` and `collector.getRenderedClassNames()`. See
65
+ [SSR](./ssr.md).
66
+
57
67
  ---
58
68
 
59
69
  ## API Reference
@@ -68,7 +78,7 @@ Retrieves CSS text for a given target. Logs the result with rule count and size.
68
78
  |---|---|
69
79
  | `'all'` | All tasty CSS (component + global + raw) |
70
80
  | `'active'` | CSS for classes currently in the DOM |
71
- | `'unused'` | CSS with refCount = 0 (cached but not used) |
81
+ | `'unused'` | CSS for injected classes no element carries (cached but not rendered) |
72
82
  | `'global'` | Only global CSS (from `injectGlobal`) |
73
83
  | `'page'` | All CSS on the page (including non-tasty) |
74
84
  | `'t3a5f'` | CSS for a specific tasty class (class names are `t` + base36 hash) |
@@ -241,7 +251,9 @@ tastyDebug.cache();
241
251
 
242
252
  ### `cleanup(opts?): void`
243
253
 
244
- Forces immediate cleanup of all unused styles (those with `refCount = 0`).
254
+ Removes every injected style that is neither carried by an element nor pinned by
255
+ an outstanding `inject()` handle — the same set `summary()` reports as unused.
256
+ Equivalent to `gc({ force: true })`.
245
257
 
246
258
  ```typescript
247
259
  tastyDebug.cleanup();
@@ -262,7 +274,7 @@ tastyDebug.help();
262
274
 
263
275
  ### `install(): void`
264
276
 
265
- Attaches `tastyDebug` to `window.tastyDebug`. Called automatically in development mode.
277
+ Attaches `tastyDebug` to `window.tastyDebug`.
266
278
 
267
279
  ```typescript
268
280
  import { tastyDebug } from '@tenphi/tasty';