@tenphi/tasty 3.4.0 → 3.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.
Files changed (83) hide show
  1. package/README.md +147 -129
  2. package/dist/astro-CzY4LCpr.js +437 -0
  3. package/dist/astro-CzY4LCpr.js.map +1 -0
  4. package/dist/{babel-BUQGeOXA.d.ts → babel-DWCsafYw.d.ts} +2 -2
  5. package/dist/{collector-DUaHCcTS.d.ts → collector-BTVXj2hS.d.ts} +21 -3
  6. package/dist/{collector-DTahQUiV.js → collector-C6TtL8HJ.js} +36 -13
  7. package/dist/collector-C6TtL8HJ.js.map +1 -0
  8. package/dist/{config-B5kHzuNz.js → config-B3gPdCqd.js} +305 -153
  9. package/dist/config-B3gPdCqd.js.map +1 -0
  10. package/dist/{config-YsxGv4tq.d.ts → config-LfIDmVHx.d.ts} +132 -32
  11. package/dist/core/index.d.ts +5 -5
  12. package/dist/core/index.js +6 -6
  13. package/dist/{core-Dr4u1NVD.js → core-Bq7w2kti.js} +215 -95
  14. package/dist/core-Bq7w2kti.js.map +1 -0
  15. package/dist/{css-writer-B-J87ncv.js → css-writer-Bgowobbx.js} +3 -3
  16. package/dist/{css-writer-B-J87ncv.js.map → css-writer-Bgowobbx.js.map} +1 -1
  17. package/dist/{format-rules-DKOA-6qu.js → format-rules-rCZ37rqY.js} +2 -2
  18. package/dist/format-rules-rCZ37rqY.js.map +1 -0
  19. package/dist/{hydrate-OeMX99We.js → hydrate-CNOmZprz.js} +3 -3
  20. package/dist/{hydrate-OeMX99We.js.map → hydrate-CNOmZprz.js.map} +1 -1
  21. package/dist/{index-PqN-DIpn.d.ts → index-Bm9ltvdH.d.ts} +38 -16
  22. package/dist/{index-Cd45t5NM.d.ts → index-BmogEzTo.d.ts} +69 -15
  23. package/dist/index.d.ts +5 -5
  24. package/dist/index.js +7 -7
  25. package/dist/index.js.map +1 -1
  26. package/dist/{keyframes-CV8azJf3.js → keyframes-DE-OE76F.js} +45 -3
  27. package/dist/keyframes-DE-OE76F.js.map +1 -0
  28. package/dist/{merge-styles-CU7JbEwg.d.ts → merge-styles-BcLl1u_d.d.ts} +2 -2
  29. package/dist/{merge-styles-oklji0KB.js → merge-styles-DuoZEsm9.js} +2 -2
  30. package/dist/{merge-styles-oklji0KB.js.map → merge-styles-DuoZEsm9.js.map} +1 -1
  31. package/dist/{resolve-recipes-DTG81rzl.js → resolve-recipes-H9NqOQuP.js} +3 -3
  32. package/dist/{resolve-recipes-DTG81rzl.js.map → resolve-recipes-H9NqOQuP.js.map} +1 -1
  33. package/dist/ssr/astro-client.js +1 -1
  34. package/dist/ssr/astro-middleware-extract-static.d.ts +11 -0
  35. package/dist/ssr/astro-middleware-extract-static.js +9 -0
  36. package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
  37. package/dist/ssr/astro-middleware-extract.d.ts +11 -0
  38. package/dist/ssr/astro-middleware-extract.js +9 -0
  39. package/dist/ssr/astro-middleware-extract.js.map +1 -0
  40. package/dist/ssr/astro-middleware-static.d.ts +3 -1
  41. package/dist/ssr/astro-middleware-static.js +1 -1
  42. package/dist/ssr/astro-middleware.d.ts +3 -1
  43. package/dist/ssr/astro-middleware.js +1 -1
  44. package/dist/ssr/astro.d.ts +45 -3
  45. package/dist/ssr/astro.js +1 -156
  46. package/dist/ssr/index.d.ts +2 -2
  47. package/dist/ssr/index.js +3 -3
  48. package/dist/ssr/index.js.map +1 -1
  49. package/dist/ssr/next.d.ts +1 -1
  50. package/dist/ssr/next.js +4 -4
  51. package/dist/static/index.d.ts +2 -2
  52. package/dist/static/index.js +1 -1
  53. package/dist/zero/babel.d.ts +1 -1
  54. package/dist/zero/babel.js +4 -4
  55. package/dist/zero/babel.js.map +1 -1
  56. package/dist/zero/index.d.ts +1 -1
  57. package/dist/zero/index.js +1 -1
  58. package/dist/zero/next.d.ts +1 -1
  59. package/docs/README.md +13 -8
  60. package/docs/adoption.md +56 -31
  61. package/docs/ai-agents.md +78 -77
  62. package/docs/comparison.md +69 -40
  63. package/docs/configuration.md +97 -87
  64. package/docs/debug.md +4 -2
  65. package/docs/design-system.md +41 -22
  66. package/docs/dsl.md +225 -195
  67. package/docs/getting-started.md +60 -39
  68. package/docs/injector.md +63 -26
  69. package/docs/methodology.md +42 -36
  70. package/docs/migration-v3.md +49 -49
  71. package/docs/plugins.md +37 -33
  72. package/docs/react-api.md +65 -44
  73. package/docs/runtime-benchmarks.md +178 -0
  74. package/docs/ssr.md +124 -61
  75. package/docs/styles.md +1 -1
  76. package/docs/tasty-static.md +137 -104
  77. package/package.json +26 -8
  78. package/dist/collector-DTahQUiV.js.map +0 -1
  79. package/dist/config-B5kHzuNz.js.map +0 -1
  80. package/dist/core-Dr4u1NVD.js.map +0 -1
  81. package/dist/format-rules-DKOA-6qu.js.map +0 -1
  82. package/dist/keyframes-CV8azJf3.js.map +0 -1
  83. package/dist/ssr/astro.js.map +0 -1
@@ -21,30 +21,30 @@ Most of the work is mechanical renames — see the [search-and-replace cheat she
21
21
 
22
22
  ## At a glance
23
23
 
24
- | v2.11 | v3.0 | Find it with |
25
- |---|---|---|
26
- | `'@properties': { … }` | `'@property': { … }` | `rg "'@properties'"` |
27
- | `'@fontFace': { … }` | `'@font-face': { … }` | `rg "'@fontFace'"` |
28
- | `'@counterStyle': { … }` | `'@counter-style': { … }` | `rg "'@counterStyle'"` |
29
- | `padding: '2x 4x top right'` | `padding: '2x top, 4x right'` | dev-mode console warning |
30
- | `fade: '3x 1x top bottom'` | `fade: '3x top, 1x bottom'` | dev-mode console warning |
31
- | `configure({ funcs })` | `configure({ functions })` | `rg "funcs"` |
32
- | `configure({ fontFace })` | `configure({ fontFaces })` | `rg "fontFace:"` |
33
- | `configure({ counterStyle })` | `configure({ counterStyles })` | `rg "counterStyle:"` |
34
- | `getCssText()` | `getCSSText()` | `rg "getCssText"` |
35
- | `getCssTextForNode()` | `getCSSTextForNode()` | `rg "getCssTextForNode"` |
36
- | `getCssTextForClasses()` | `getCSSTextForClasses()` | `rg "getCssTextForClasses"` |
37
- | `getGlobalFontFace()` | `getGlobalFontFaces()` | `rg "getGlobalFontFace\b"` |
38
- | `getGlobalCounterStyle()` | `getGlobalCounterStyles()` | `rg "getGlobalCounterStyle\b"` |
39
- | `getGlobalFunction()` | `getGlobalFunctions()` | `rg "getGlobalFunction\b"` |
40
- | `okhslFunc` / `okhstFunc` | `okhslFunction` / `okhstFunction` | `rg "okhs[lt]Func\b"` |
41
- | `CssOptions` | `CSSOptions` | `rg "CssOptions"` |
42
- | `ParserOptions.funcs` | `ParserOptions.functions` | `rg "funcs:"` |
43
- | `StyleParser#setFuncs()` | `StyleParser#setFunctions()` | `rg "setFuncs"` |
44
- | `ChunkInfo` (from `tastyDebug`) | `DebugChunkInfo` | `rg "ChunkInfo"` |
45
- | `@tenphi/tasty/next` | `@tenphi/tasty/zero/next` | `rg "tasty/next"` |
46
- | `getIsTestEnvironment()` | `isTestEnvironment()` | `rg "getIsTestEnvironment"` |
47
- | `hydrateTastyCache()` | `hydrateTastyClasses()` | `rg "hydrateTastyCache"` |
24
+ | v2.11 | v3.0 | Find it with |
25
+ | ------------------------------- | --------------------------------- | ------------------------------ |
26
+ | `'@properties': { … }` | `'@property': { … }` | `rg "'@properties'"` |
27
+ | `'@fontFace': { … }` | `'@font-face': { … }` | `rg "'@fontFace'"` |
28
+ | `'@counterStyle': { … }` | `'@counter-style': { … }` | `rg "'@counterStyle'"` |
29
+ | `padding: '2x 4x top right'` | `padding: '2x top, 4x right'` | dev-mode console warning |
30
+ | `fade: '3x 1x top bottom'` | `fade: '3x top, 1x bottom'` | dev-mode console warning |
31
+ | `configure({ funcs })` | `configure({ functions })` | `rg "funcs"` |
32
+ | `configure({ fontFace })` | `configure({ fontFaces })` | `rg "fontFace:"` |
33
+ | `configure({ counterStyle })` | `configure({ counterStyles })` | `rg "counterStyle:"` |
34
+ | `getCssText()` | `getCSSText()` | `rg "getCssText"` |
35
+ | `getCssTextForNode()` | `getCSSTextForNode()` | `rg "getCssTextForNode"` |
36
+ | `getCssTextForClasses()` | `getCSSTextForClasses()` | `rg "getCssTextForClasses"` |
37
+ | `getGlobalFontFace()` | `getGlobalFontFaces()` | `rg "getGlobalFontFace\b"` |
38
+ | `getGlobalCounterStyle()` | `getGlobalCounterStyles()` | `rg "getGlobalCounterStyle\b"` |
39
+ | `getGlobalFunction()` | `getGlobalFunctions()` | `rg "getGlobalFunction\b"` |
40
+ | `okhslFunc` / `okhstFunc` | `okhslFunction` / `okhstFunction` | `rg "okhs[lt]Func\b"` |
41
+ | `CssOptions` | `CSSOptions` | `rg "CssOptions"` |
42
+ | `ParserOptions.funcs` | `ParserOptions.functions` | `rg "funcs:"` |
43
+ | `StyleParser#setFuncs()` | `StyleParser#setFunctions()` | `rg "setFuncs"` |
44
+ | `ChunkInfo` (from `tastyDebug`) | `DebugChunkInfo` | `rg "ChunkInfo"` |
45
+ | `@tenphi/tasty/next` | `@tenphi/tasty/zero/next` | `rg "tasty/next"` |
46
+ | `getIsTestEnvironment()` | `isTestEnvironment()` | `rg "getIsTestEnvironment"` |
47
+ | `hydrateTastyCache()` | `hydrateTastyClasses()` | `rg "hydrateTastyCache"` |
48
48
 
49
49
  ---
50
50
 
@@ -73,7 +73,7 @@ At-rule keys now match the real CSS at-rule names Tasty already emits, so they a
73
73
 
74
74
  **This is the one change that can alter rendered CSS without a rename.** A style group that names direction modifiers now takes a **single** value, applied to every direction it names.
75
75
 
76
- The old form looked positional but never was: the parser buckets values and modifiers into separate arrays per comma group, so `'2x 4x top right'`, `'2x top 4x right'`, and `'top 2x right 4x'` were all the same input, and the pairing was decided by the order the *modifiers* happened to appear in. `'1x 2x right top'` and `'1x 2x top right'` produced different CSS, and `'1x 2x top top'` silently assigned `top` twice.
76
+ The old form looked positional but never was: the parser buckets values and modifiers into separate arrays per comma group, so `'2x 4x top right'`, `'2x top 4x right'`, and `'top 2x right 4x'` were all the same input, and the pairing was decided by the order the _modifiers_ happened to appear in. `'1x 2x right top'` and `'1x 2x top right'` produced different CSS, and `'1x 2x top top'` silently assigned `top` twice.
77
77
 
78
78
  Per-side values now come from comma-separated groups:
79
79
 
@@ -155,25 +155,25 @@ A key whose prefix doesn't match its value type is ignored with a development wa
155
155
 
156
156
  ## 5. Removed exports
157
157
 
158
- | Removed | Use instead |
159
- |---|---|
160
- | `getIsTestEnvironment()` | `isTestEnvironment()` |
161
- | `hydrateTastyCache()` | `hydrateTastyClasses()` |
162
- | `customFunc()` | `configure({ functions })` |
163
- | `getGlobalFuncs()` | `getGlobalFunctions()` for `@function` definitions; the parse-function registry is internal |
164
- | `resetGlobalFuncs()` | `resetConfig()` |
165
- | `setGlobalPredefinedTokens()` | `configure({ replaceTokens })` |
166
- | `resetGlobalPredefinedTokens()` | `resetConfig()` |
167
- | `setMiddlewareTransferCache()` / `getMiddlewareTransferCache()` | internal; no replacement needed |
168
- | `clearWriterCache()` | internal test utility |
169
- | `DEFAULT_PLUGINS` | nothing — it was never consumed, and passing it to `configure({ plugins })` was a no-op. The default `okhsl`/`okhst` functions are registered automatically. |
170
- | `registerDefaultFunctions()` | internal bootstrap helper |
171
- | `warn()` / `deprecationWarning()` | internal helpers with no callers |
172
- | `Bucket` enum | parser-internal |
173
- | `Props` type | inline `Record<string, any>` |
174
- | `UseStylesOptions` | `useStyles()` takes `Styles \| undefined` directly |
175
- | `GlobalStyledProps` | its only member was the already-dead `breakpoints` prop |
176
- | duplicate `PropertyOptions` | the single `PropertyOptions` from `injector/types` |
158
+ | Removed | Use instead |
159
+ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
160
+ | `getIsTestEnvironment()` | `isTestEnvironment()` |
161
+ | `hydrateTastyCache()` | `hydrateTastyClasses()` |
162
+ | `customFunc()` | `configure({ functions })` |
163
+ | `getGlobalFuncs()` | `getGlobalFunctions()` for `@function` definitions; the parse-function registry is internal |
164
+ | `resetGlobalFuncs()` | `resetConfig()` |
165
+ | `setGlobalPredefinedTokens()` | `configure({ replaceTokens })` |
166
+ | `resetGlobalPredefinedTokens()` | `resetConfig()` |
167
+ | `setMiddlewareTransferCache()` / `getMiddlewareTransferCache()` | internal; no replacement needed |
168
+ | `clearWriterCache()` | internal test utility |
169
+ | `DEFAULT_PLUGINS` | nothing — it was never consumed, and passing it to `configure({ plugins })` was a no-op. The default `okhsl`/`okhst` functions are registered automatically. |
170
+ | `registerDefaultFunctions()` | internal bootstrap helper |
171
+ | `warn()` / `deprecationWarning()` | internal helpers with no callers |
172
+ | `Bucket` enum | parser-internal |
173
+ | `Props` type | inline `Record<string, any>` |
174
+ | `UseStylesOptions` | `useStyles()` takes `Styles \| undefined` directly |
175
+ | `GlobalStyledProps` | its only member was the already-dead `breakpoints` prop |
176
+ | duplicate `PropertyOptions` | the single `PropertyOptions` from `injector/types` |
177
177
 
178
178
  `getGlobalFuncs()` deserves a note: it returned the **live mutable internal registry**, and writing to it bypassed the parser's cache invalidation, so the write silently never took effect. If you were mutating it, move to `configure({ functions })`.
179
179
 
@@ -192,13 +192,13 @@ A key whose prefix doesn't match its value type is ignored with a development wa
192
192
  + import { withTasty } from '@tenphi/tasty/zero/next';
193
193
  ```
194
194
 
195
- The zero-runtime Next.js wrapper already lived under the `zero` namespace internally; the top-level `/next` path collided confusingly with `@tenphi/tasty/ssr/next`, which is a different integration.
195
+ The build-time extraction Next.js wrapper already lived under the `zero` namespace internally; the top-level `/next` path collided confusingly with `@tenphi/tasty/ssr/next`, which is a different integration.
196
196
 
197
197
  ---
198
198
 
199
199
  ## 8. Behavior changes that are not renames
200
200
 
201
- **Repeated `configure()` calls now merge.** `recipes`, `keyframes`, `properties`, `fontFaces`, `counterStyles`, and CSS `@function` definitions used to replace wholesale, so a design-system `configure()` followed by an application `configure()` silently dropped the design system's values. All of them now merge, with the later call winning on a key conflict — matching `tokens` and `globalStyles`, which already merged. If you were relying on a second call to *clear* a collection, use `resetConfig()`.
201
+ **Repeated `configure()` calls now merge.** `recipes`, `keyframes`, `properties`, `fontFaces`, `counterStyles`, and CSS `@function` definitions used to replace wholesale, so a design-system `configure()` followed by an application `configure()` silently dropped the design system's values. All of them now merge, with the later call winning on a key conflict — matching `tokens` and `globalStyles`, which already merged. If you were relying on a second call to _clear_ a collection, use `resetConfig()`.
202
202
 
203
203
  `configure({ polyfills })` is now correctly rejected after the first render, like every sibling option, and shallow-merges so an unrelated later call cannot switch a polyfill off.
204
204
 
@@ -208,7 +208,7 @@ The zero-runtime Next.js wrapper already lived under the `zero` namespace intern
208
208
 
209
209
  **Replacing a built-in style handler now warns.** Built-in handlers are shared across style names, so `configure({ handlers: { fill } })` also takes over `image` and the whole `background-*` family, and `configure({ handlers: { display } })` takes down `flow`, `gap`, `hide`, `overflow`, `whiteSpace`, and `textOverflow`. This was always true; what's new is that a development-mode warning now names exactly what was displaced. If you see it, either declare the whole group and delegate the rest to `styleHandlers.*`, or pick a narrower name. See [Plugins](plugins.md#custom-style-handlers).
210
210
 
211
- **Multi-dependency custom handlers are fixed.** A handler whose dependencies mixed known and unknown style names was invoked once per chunk with only *part* of its dependencies, and could emit stale CSS. Unknown names now join their handler's chunk, so it is invoked once with all of them. No built-in style moved, so no existing class name changes.
211
+ **Multi-dependency custom handlers are fixed.** A handler whose dependencies mixed known and unknown style names was invoked once per chunk with only _part_ of its dependencies, and could emit stale CSS. Unknown names now join their handler's chunk, so it is invoked once with all of them. No built-in style moved, so no existing class name changes.
212
212
 
213
213
  **`isChecked` added to the root prop types.** It already worked at runtime; it was just missing from `BaseProps` / `AllBaseProps`.
214
214
 
@@ -220,7 +220,7 @@ The zero-runtime Next.js wrapper already lived under the `zero` namespace intern
220
220
 
221
221
  Nothing here is required to upgrade.
222
222
 
223
- - **CSS `@function` support** — reusable, parameterized functions via the `'@function'` styles key, the `useFunction` style function, or `configure({ functions })`. Works across client, SSR/RSC, and zero-runtime. `configure({ polyfills: { functions: true } })` inlines every call into plain CSS for browsers that don't ship the at-rule yet. See [Style DSL](dsl.md#functions-function).
223
+ - **CSS `@function` support** — reusable, parameterized functions via the `'@function'` styles key, the `useFunction` style function, or `configure({ functions })`. Works across client rendering, SSR/RSC, and build-time extraction. `configure({ polyfills: { functions: true } })` inlines every call into plain CSS for browsers that don't ship the at-rule yet. See [Style DSL](dsl.md#functions-function).
224
224
  - **Custom color plugins with no core special-casing** — `okhsl`/`okhst` are now ordinary one-line plugins, and a third-party color space can achieve identical integration. New `createColorFunc` and `resolveFunctionColor` helpers.
225
225
  - **`configure({ propHandlers })`** — props middleware for every component: props in, props out. The extension point for props that aren't style values.
226
226
  - **`configure({ baseStyleProps })`** — expose style properties as props on every component without listing them in each `styleProps`.
@@ -279,7 +279,7 @@ Worth stating explicitly, because it's easy to assume otherwise:
279
279
 
280
280
  - **At-rule styles-object keys are CSS-spec-faithful, not JS-conventional.** `@keyframes`, `@property`, `@font-face`, `@counter-style`, `@function`, `@starting` — kebab-case, matching what Tasty emits.
281
281
  - **The `func()` injector method stays abbreviated.** `function` is a reserved word.
282
- - `injector.fontFace()` and `injector.counterStyle()` keep their singular names — only the *config keys* and *getters* were pluralized.
282
+ - `injector.fontFace()` and `injector.counterStyle()` keep their singular names — only the _config keys_ and _getters_ were pluralized.
283
283
  - `useFontFace` and `useCounterStyle` are unchanged.
284
284
  - `configure({ properties })` and `autoPropertyTypes` are unchanged; only the styles-object `@property` key was renamed.
285
285
  - **The deprecated style aliases are retained deliberately.** `backgroundColor`, `background`, `backgroundImage`, `flex`, `grid`, and `flexDirection` still carry `@deprecated` tags and still work. They are the guardrail that discourages their use in projects that have not enabled `@tenphi/eslint-plugin-tasty` yet — read the tags as guidance, not as a removal notice.
package/docs/plugins.md CHANGED
@@ -43,23 +43,23 @@ configure({ plugins: [spacingPlugin()] });
43
43
 
44
44
  ## Choosing an extension point
45
45
 
46
- | You want to add | Use | Runs | Zero-runtime |
47
- |---|---|---|---|
48
- | New value syntax — `okhsl(…)`, `double(2x)` | `functions` (bare key) | parse time, per value | ✅ |
49
- | A reusable CSS `@function` | `functions` (`$$name` key) | injected once, globally | ✅ |
50
- | A new unit — `2gu` | `units` | parse time, per value | ✅ |
51
- | A new state alias — `@mobile` | `states` | state-key parse | ✅ |
52
- | A style property → **CSS declarations** | `handlers` | pipeline, per state snapshot | ✅ |
53
- | A style property on every component | `baseStyleProps` | prop harvest | ✅ (styles only) |
54
- | A **component prop** → anything | `propHandlers` | render, per component | ❌ (no props to run on) |
55
- | A named bundle of styles | `recipes` | before chunking | ✅ |
56
- | Typography scale tokens | `presets` | inject time | ✅ |
57
- | `:root` variables | `tokens`, `replaceTokens` | inject time | ✅ |
58
- | Global selector styles | `globalStyles` | inject time | ✅ |
46
+ | You want to add | Use | Runs | Build-time extraction |
47
+ | ------------------------------------------- | -------------------------- | ---------------------------- | ----------------------- |
48
+ | New value syntax — `okhsl(…)`, `double(2x)` | `functions` (bare key) | parse time, per value | ✅ |
49
+ | A reusable CSS `@function` | `functions` (`$$name` key) | injected once, globally | ✅ |
50
+ | A new unit — `2gu` | `units` | parse time, per value | ✅ |
51
+ | A new state alias — `@mobile` | `states` | state-key parse | ✅ |
52
+ | A style property → **CSS declarations** | `handlers` | pipeline, per state snapshot | ✅ |
53
+ | A style property on every component | `baseStyleProps` | prop harvest | ✅ (styles only) |
54
+ | A **component prop** → anything | `propHandlers` | render, per component | ❌ (no props to run on) |
55
+ | A named bundle of styles | `recipes` | before chunking | ✅ |
56
+ | Typography scale tokens | `presets` | inject time | ✅ |
57
+ | `:root` variables | `tokens`, `replaceTokens` | inject time | ✅ |
58
+ | Global selector styles | `globalStyles` | inject time | ✅ |
59
59
 
60
60
  Two distinctions do most of the work:
61
61
 
62
- - **`handlers` vs `propHandlers`.** A handler turns a *style property* into CSS declarations and knows nothing about components. A prop handler turns a *component prop* into other props — including `styles`, which then flow through the normal handlers. Reach for `propHandlers` when the input isn't a style value (an options object, a domain concept) or when it should affect more than styles.
62
+ - **`handlers` vs `propHandlers`.** A handler turns a _style property_ into CSS declarations and knows nothing about components. A prop handler turns a _component prop_ into other props — including `styles`, which then flow through the normal handlers. Reach for `propHandlers` when the input isn't a style value (an options object, a domain concept) or when it should affect more than styles.
63
63
  - **`recipes` vs `propHandlers`.** A recipe is a static named bundle. A prop handler computes its styles from a value.
64
64
 
65
65
  ---
@@ -123,12 +123,12 @@ configure({
123
123
 
124
124
  **The key is the trigger.** By default a handler runs only when a prop matching its key is present, so an absent prop costs one property check rather than a call. Override that with a tuple:
125
125
 
126
- | Definition | Runs when |
127
- |---|---|
128
- | `fn` | a prop named after the key is present |
129
- | `['glaze', fn]` | `glaze` is present |
130
- | `[['glaze', 'tint'], fn]` | either is present |
131
- | `['*', fn]` | always |
126
+ | Definition | Runs when |
127
+ | ------------------------- | ------------------------------------- |
128
+ | `fn` | a prop named after the key is present |
129
+ | `['glaze', fn]` | `glaze` is present |
130
+ | `[['glaze', 'tint'], fn]` | either is present |
131
+ | `['*', fn]` | always |
132
132
 
133
133
  **Chaining.** Handlers run in registration order — plugins first, then direct `configure()` — and each receives the previous one's output. Registering the same key twice replaces the handler in place, keeping its position.
134
134
 
@@ -136,16 +136,16 @@ configure({
136
136
 
137
137
  ### Rules
138
138
 
139
- - **Be pure. Never mutate the input.** Style values are cached by object identity, so mutating a value object in place yields a stale class name *and* stale CSS. Return fresh objects.
139
+ - **Be pure. Never mutate the input.** Style values are cached by object identity, so mutating a value object in place yields a stale class name _and_ stale CSS. Return fresh objects.
140
140
  - **Memoize the styles you build, per input value.** A reference-stable (ideally frozen) styles object lets the cache key reuse its serialization instead of recomputing it on every render. This is the single highest-leverage thing you can do for performance.
141
141
  - **Precedence is fixed** and not adjustable from a handler. Styles are merged as `factory defaults → styles → harvested style props`, and injected styles occupy the `styles` slot: they beat a component's own default styles and lose to a style prop passed at the call site. This matches how `recipe` already behaves.
142
- - **Prefer returning `styles` over bare style props.** Returning `{ fill: '#red' }` only works if `fill` is harvestable on *that* component (in its `styleProps`, or promoted via `baseStyleProps`); otherwise it leaks to the DOM.
142
+ - **Prefer returning `styles` over bare style props.** Returning `{ fill: '#red' }` only works if `fill` is harvestable on _that_ component (in its `styleProps`, or promoted via `baseStyleProps`); otherwise it leaks to the DOM.
143
143
 
144
144
  ### What it cannot do
145
145
 
146
146
  - **`ref`** — `forwardRef` separates it from props, so it is out of reach.
147
147
  - **Sub-elements** (`Card.Title`) — they have no `styles` prop and no style-prop harvest, so middleware does not run on them.
148
- - **Zero-runtime mode** — see [Rendering modes](#rendering-modes-and-caveats).
148
+ - **Build-time extraction** — see [Rendering modes](#rendering-modes-and-caveats).
149
149
 
150
150
  ---
151
151
 
@@ -161,7 +161,7 @@ configure({ baseStyleProps: ['radius', 'shadow'] });
161
161
 
162
162
  Names must be real style properties, must start with a lowercase letter, and must not collide with a prop `tasty()` consumes itself (`as`, `styles`, `variant`, `mods`, `tokens`, …); invalid entries are dropped with a development warning.
163
163
 
164
- `configure()` may run *after* your components are defined — each factory resolves its prop list lazily and refreshes when the registry changes.
164
+ `configure()` may run _after_ your components are defined — each factory resolves its prop list lazily and refreshes when the registry changes.
165
165
 
166
166
  **Costs and caveats.** Each name adds one property check per render of every component, forever, so keep the list short. The effect is app-global and cannot be scoped to a subtree — use a factory's own `styleProps` for that. Be wary of names that collide with real DOM or component props (`width`, `size`, `color`): promoting one means every component swallows it as a style, including when rendering `as={SomeThirdPartyComponent}`.
167
167
 
@@ -211,7 +211,7 @@ export default {
211
211
  };
212
212
  ```
213
213
 
214
- `propHandlers` keys are JSX props rather than style keys, so they need no entry — but any *style* name or token a handler expands into does.
214
+ `propHandlers` keys are JSX props rather than style keys, so they need no entry — but any _style_ name or token a handler expands into does.
215
215
 
216
216
  ---
217
217
 
@@ -274,7 +274,11 @@ export const glazePlugin: TastyPluginFactory = () => ({
274
274
 
275
275
  // Typed so `#glaze-bg` animates smoothly instead of snapping.
276
276
  properties: {
277
- '#glaze-bg': { syntax: '<color>', inherits: false, initialValue: 'transparent' },
277
+ '#glaze-bg': {
278
+ syntax: '<color>',
279
+ inherits: false,
280
+ initialValue: 'transparent',
281
+ },
278
282
  },
279
283
  });
280
284
  ```
@@ -302,19 +306,19 @@ declare module '@tenphi/tasty' {
302
306
  }
303
307
  ```
304
308
 
305
- The object value is unambiguous here precisely because `glaze` is a **prop**. A style *value* of the same shape would be indistinguishable from a state map (`{ tone: … }` looks exactly like `{ hovered: … }`), which is why this belongs in `propHandlers` rather than `handlers`.
309
+ The object value is unambiguous here precisely because `glaze` is a **prop**. A style _value_ of the same shape would be indistinguishable from a state map (`{ tone: … }` looks exactly like `{ hovered: … }`), which is why this belongs in `propHandlers` rather than `handlers`.
306
310
 
307
311
  ---
308
312
 
309
313
  ## Rendering modes and caveats
310
314
 
311
- | Mode | `functions` / `units` / `states` / `handlers` / `recipes` / `tokens` | `propHandlers` |
312
- |---|---|---|
313
- | Client | ✅ | ✅ |
314
- | SSR / RSC | ✅ | ✅ |
315
- | Zero-runtime (`tastyStatic`) | ✅ at build time | not applicable |
315
+ | Mode | `functions` / `units` / `states` / `handlers` / `recipes` / `tokens` | `propHandlers` |
316
+ | -------------------------- | -------------------------------------------------------------------- | -------------- |
317
+ | Client | ✅ | ✅ |
318
+ | SSR / RSC | ✅ | ✅ |
319
+ | Build-time (`tastyStatic`) | ✅ at build time | not applicable |
316
320
 
317
- **Zero-runtime.** The Babel plugin transforms `tastyStatic()` calls, which take styles objects — there are no props in that pipeline, so there is nothing for props middleware to run on. Components you render through `tasty()` are untouched by the plugin and keep the runtime injector, so `propHandlers` work there exactly as in any client app; their CSS simply comes from the injector rather than the extracted stylesheet. Everything else runs at build time, which is why handlers, functions, and units must be pure functions of their input.
321
+ **Build-time extraction.** The Babel plugin transforms `tastyStatic()` calls, which take styles objects — there are no props in that pipeline, so there is nothing for props middleware to run on. Components rendered through `tasty()` are untouched by the plugin; `propHandlers` work whether those components render on the server or in the browser. Everything in the `tastyStatic()` path runs at build time, which is why handlers, functions, and units must be pure functions of their input.
318
322
 
319
323
  **Server and client must configure identically.** Class names are derived from resolved styles, so a handler or promoted prop registered on one side and not the other produces a hydration mismatch. This is the same requirement as `namePrefix` — see [SSR → Hydration mismatch warnings](ssr.md#hydration-mismatch-warnings).
320
324
 
package/docs/react-api.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # React API
2
2
 
3
- The React-specific `tasty()` component factory, component props, and style functions. All Tasty style functions — `tasty()` components, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()`, and `useFunction()` are hook-free and compatible with React Server Components. No `'use client'` directive needed. For the shared style language (state maps, tokens, units, extending semantics), see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md). For the broader docs map, see the [Docs Hub](README.md).
3
+ Use `tasty()` to turn state-aware style definitions into normal React components, extend existing Tasty components, and expose typed styling APIs to consumers.
4
+
5
+ All Tasty style functions — `tasty()` components, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()`, and `useFunction()` — are hook-free and compatible with React Server Components. No `'use client'` directive is needed. For state maps, tokens, units, and extension semantics, see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md). For the broader docs map, see the [Docs Hub](README.md).
4
6
 
5
7
  > **Note:** This file was previously named `runtime.md`. All functionality documented here works in both server and client contexts — "runtime" referred to style computation during React rendering, not to client-side JavaScript.
6
8
 
@@ -58,10 +60,12 @@ const Link = tasty({
58
60
 
59
61
  const Span = tasty({
60
62
  as: 'span',
61
- styles: { fontWeight: 'bold' },
63
+ styles: { preset: 'strong' },
62
64
  });
63
65
 
64
- <Link href="/blog" padding="1x">Blog</Link>;
66
+ <Link href="/blog" padding="1x">
67
+ Blog
68
+ </Link>;
65
69
  ```
66
70
 
67
71
  The wrapped component only needs to forward `className` (and ideally `style`/`ref`). Tasty-specific props (`qa`, `qaVal`, `mods`, `tokens`, `styleProps`, `modProps`, `tokenProps`) are consumed by Tasty and never leak to the DOM.
@@ -84,7 +88,7 @@ const FlexibleBox = tasty({
84
88
 
85
89
  <FlexibleBox gap="2x" align="center" fill="#surface">
86
90
  Content
87
- </FlexibleBox>
91
+ </FlexibleBox>;
88
92
  ```
89
93
 
90
94
  Style props accept state maps, so responsive values work through the same API:
@@ -113,7 +117,7 @@ import { Element } from '@tenphi/tasty';
113
117
 
114
118
  <Element as="section" padding="4x" fill="#surface">
115
119
  Content
116
- </Element>
120
+ </Element>;
117
121
  ```
118
122
 
119
123
  > Note: `Element` shadows the global DOM `Element` type when imported from `@tenphi/tasty`. In files that need the DOM type, alias the import: `import { Element as TastyElement } from '@tenphi/tasty'`.
@@ -156,9 +160,9 @@ Map modifier names to type descriptors for precise TypeScript types:
156
160
  ```tsx
157
161
  const Button = tasty({
158
162
  modProps: {
159
- isLoading: Boolean, // isLoading?: boolean
160
- isSelected: Boolean, // isSelected?: boolean
161
- size: ['small', 'medium', 'large'] as const, // size?: 'small' | 'medium' | 'large'
163
+ isLoading: Boolean, // isLoading?: boolean
164
+ isSelected: Boolean, // isSelected?: boolean
165
+ size: ['small', 'medium', 'large'] as const, // size?: 'small' | 'medium' | 'large'
162
166
  },
163
167
  styles: {
164
168
  padding: { '': '2x 4x', 'size=small': '1x 2x', 'size=large': '3x 6x' },
@@ -166,18 +170,20 @@ const Button = tasty({
166
170
  },
167
171
  });
168
172
 
169
- <Button isLoading size="large">Submit</Button>
173
+ <Button isLoading size="large">
174
+ Submit
175
+ </Button>;
170
176
  // Renders: <button data-is-loading="" data-size="large">Submit</button>
171
177
  ```
172
178
 
173
179
  Available type descriptors:
174
180
 
175
- | Descriptor | TypeScript type | Example |
176
- |---|---|---|
177
- | `Boolean` | `boolean` | `isLoading: Boolean` |
178
- | `String` | `string` | `label: String` |
179
- | `Number` | `number` | `count: Number` |
180
- | `['a', 'b'] as const` | `'a' \| 'b'` | `size: ['sm', 'md', 'lg'] as const` |
181
+ | Descriptor | TypeScript type | Example |
182
+ | --------------------- | --------------- | ----------------------------------- |
183
+ | `Boolean` | `boolean` | `isLoading: Boolean` |
184
+ | `String` | `string` | `label: String` |
185
+ | `Number` | `number` | `count: Number` |
186
+ | `['a', 'b'] as const` | `'a' \| 'b'` | `size: ['sm', 'md', 'lg'] as const` |
181
187
 
182
188
  ### Merge with `mods`
183
189
 
@@ -190,11 +196,11 @@ Mod props and the `mods` object can be used together. Mod props take precedence:
190
196
 
191
197
  ### When to use `modProps` vs `mods`
192
198
 
193
- | Use case | Recommendation |
194
- |---|---|
199
+ | Use case | Recommendation |
200
+ | -------------------------------------------- | -------------------------------------------------------- |
195
201
  | Component has a fixed set of known modifiers | `modProps` — cleaner API, better TypeScript autocomplete |
196
- | Component needs arbitrary/dynamic modifiers | `mods` — open-ended `Record<string, ModValue>` |
197
- | Both fixed and dynamic | Combine: `modProps` for known keys, `mods` for ad-hoc |
202
+ | Component needs arbitrary/dynamic modifiers | `mods` — open-ended `Record<string, ModValue>` |
203
+ | Both fixed and dynamic | Combine: `modProps` for known keys, `mods` for ad-hoc |
198
204
 
199
205
  For architecture guidance on when to use modifiers vs `styleProps`, see [Methodology — modProps and mods](methodology.md#modprops-and-mods).
200
206
 
@@ -240,7 +246,7 @@ const Card = tasty({
240
246
  styles: { padding: '$card-size', fill: '#card-accent' },
241
247
  });
242
248
 
243
- <Card size="4x" color="#purple" />
249
+ <Card size="4x" color="#purple" />;
244
250
  ```
245
251
 
246
252
  ### Merge with `tokens`
@@ -259,11 +265,11 @@ const Bar = tasty({
259
265
 
260
266
  ### When to use `tokenProps` vs `tokens`
261
267
 
262
- | Use case | Recommendation |
263
- |---|---|
264
- | Component has a fixed set of known token keys | `tokenProps` — cleaner API, better TypeScript autocomplete |
265
- | Component needs arbitrary/dynamic token values | `tokens` — open-ended `Record<string, TokenValue>` |
266
- | Both fixed and dynamic | Combine: `tokenProps` for known keys, `tokens` for ad-hoc |
268
+ | Use case | Recommendation |
269
+ | ---------------------------------------------- | ---------------------------------------------------------- |
270
+ | Component has a fixed set of known token keys | `tokenProps` — cleaner API, better TypeScript autocomplete |
271
+ | Component needs arbitrary/dynamic token values | `tokens` — open-ended `Record<string, TokenValue>` |
272
+ | Both fixed and dynamic | Combine: `tokenProps` for known keys, `tokens` for ad-hoc |
267
273
 
268
274
  For architecture guidance, see [Methodology — tokenProps](methodology.md#tokenprops).
269
275
 
@@ -282,11 +288,11 @@ const Button = tasty({
282
288
  variants: {
283
289
  default: { fill: '#blue', color: '#white' },
284
290
  danger: { fill: '#red', color: '#white' },
285
- outline: { fill: 'transparent', color: '#blue', border: '1bw solid #blue' },
291
+ outline: { fill: '#clear', color: '#blue', border: '1bw solid #blue' },
286
292
  },
287
293
  });
288
294
 
289
- <Button variant="danger">Delete</Button>
295
+ <Button variant="danger">Delete</Button>;
290
296
  ```
291
297
 
292
298
  ### Extending Variants with Base State Maps
@@ -342,7 +348,7 @@ const Card = tasty({
342
348
  <Card>
343
349
  <Card.Title>Card Title</Card.Title>
344
350
  <Card.Content>Card content</Card.Content>
345
- </Card>
351
+ </Card>;
346
352
  ```
347
353
 
348
354
  Each entry in `elements` can be a tag name string or a config object:
@@ -368,7 +374,7 @@ const Card = tasty({
368
374
 
369
375
  <Card>
370
376
  <div data-element="Title">Card Title</div>
371
- </Card>
377
+ </Card>;
372
378
  ```
373
379
 
374
380
  ### Selector Affix (`$`)
@@ -401,7 +407,7 @@ On the client, CSS is injected synchronously into the DOM (idempotent via the in
401
407
 
402
408
  All style functions below are plain functions (not React hooks) and can be used in any environment: client components, SSR with a `ServerStyleCollector`, and React Server Components. They retain their `use` prefix for backward compatibility, but do not use any React hooks internally.
403
409
 
404
- In server-only contexts (Next.js RSC without `'use client'`, Astro without `client:*` directives, SSG), components that use only Tasty style functions produce zero client JavaScript. Tasty never forces the `'use client'` boundary — that decision belongs to your component when it needs React interactivity (state, effects, event handlers).
410
+ In server-only contexts, components that use only Tasty style functions ship no Tasty styling runtime. Astro without `client:*` directives produces no client JavaScript; server-only Next.js RSC follows the same Tasty architecture, while final output depends on the application deployment. Tasty never forces the `'use client'` boundary — that decision belongs to your component when it needs React interactivity (state, effects, event handlers).
405
411
 
406
412
  ### useStyles
407
413
 
@@ -480,7 +486,7 @@ function Spinner() {
480
486
  from: { transform: 'rotate(0deg)' },
481
487
  to: { transform: 'rotate(360deg)' },
482
488
  },
483
- { name: 'spin' }
489
+ { name: 'spin' },
484
490
  );
485
491
 
486
492
  return <div style={{ animation: `${spin} 1s linear infinite` }} />;
@@ -497,10 +503,14 @@ function Pulse({ scale }: { scale: number }) {
497
503
  '100%': { transform: `scale(${scale})` },
498
504
  }),
499
505
  [scale],
500
- { name: 'pulse' }
506
+ { name: 'pulse' },
501
507
  );
502
508
 
503
- return <div style={{ animation: `${pulse} 500ms ease-in-out alternate infinite` }} />;
509
+ return (
510
+ <div
511
+ style={{ animation: `${pulse} 500ms ease-in-out alternate infinite` }}
512
+ />
513
+ );
504
514
  }
505
515
  ```
506
516
 
@@ -556,8 +566,16 @@ For multiple weights/styles, pass an array:
556
566
 
557
567
  ```tsx
558
568
  useFontFace('Brand Sans', [
559
- { src: 'url("/fonts/brand-regular.woff2") format("woff2")', fontWeight: 400, fontDisplay: 'swap' },
560
- { src: 'url("/fonts/brand-bold.woff2") format("woff2")', fontWeight: 700, fontDisplay: 'swap' },
569
+ {
570
+ src: 'url("/fonts/brand-regular.woff2") format("woff2")',
571
+ fontWeight: 400,
572
+ fontDisplay: 'swap',
573
+ },
574
+ {
575
+ src: 'url("/fonts/brand-bold.woff2") format("woff2")',
576
+ fontWeight: 700,
577
+ fontDisplay: 'swap',
578
+ },
561
579
  ]);
562
580
  ```
563
581
 
@@ -575,11 +593,14 @@ Inject a `@counter-style` rule and get back the counter style name. Permanent
575
593
  import { useCounterStyle } from '@tenphi/tasty';
576
594
 
577
595
  function EmojiList() {
578
- const styleName = useCounterStyle({
579
- system: 'cyclic',
580
- symbols: '"👍"',
581
- suffix: '" "',
582
- }, { name: 'thumbs' });
596
+ const styleName = useCounterStyle(
597
+ {
598
+ system: 'cyclic',
599
+ symbols: '"👍"',
600
+ suffix: '" "',
601
+ },
602
+ { name: 'thumbs' },
603
+ );
583
604
 
584
605
  return (
585
606
  <ol style={{ listStyleType: styleName }}>
@@ -606,7 +627,7 @@ Register a CSS `@function` (custom function). Permanent — no cleanup on unmoun
606
627
  ```tsx
607
628
  import { tasty, useFunction } from '@tenphi/tasty';
608
629
 
609
- const Box = tasty({ styles: { marginTop: '$$negative(10px)' } });
630
+ const Box = tasty({ styles: { margin: '$$negative(10px) top' } });
610
631
 
611
632
  function Layout() {
612
633
  useFunction('$$negative', { args: ['$value'], result: '(-1 * $value)' });
@@ -621,8 +642,8 @@ Inside a `tasty()` component you call functions with the same `$$name(...)` suga
621
642
  ```tsx
622
643
  const Box = tasty({
623
644
  styles: {
624
- '@function': { '$$negative': { args: ['$value'], result: '(-1 * $value)' } },
625
- marginTop: '$$negative(10px)',
645
+ '@function': { $$negative: { args: ['$value'], result: '(-1 * $value)' } },
646
+ margin: '$$negative(10px) top',
626
647
  },
627
648
  });
628
649
  ```
@@ -655,6 +676,6 @@ See the [Functions section of the DSL reference](dsl.md#functions-function) for
655
676
  - **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
656
677
  - **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
657
678
  - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
658
- - **[Zero Runtime (tastyStatic)](tasty-static.md)** — Build-time static styling with Babel plugin
679
+ - **[Build-Time Extraction (`tastyStatic`)](tasty-static.md)** — Static styling with the Babel plugin
659
680
  - **[Server-Side Rendering](ssr.md)** — SSR setup for Next.js, Astro, and generic frameworks
660
681
  - **[Debug Utilities](debug.md)** — Inspect injected CSS, cache state, and active styles at runtime