@tenphi/tasty 0.0.0-snapshot.27b6708 → 0.0.0-snapshot.28eb83c

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 (106) hide show
  1. package/README.md +38 -54
  2. package/dist/{babel-BUQGeOXA.d.ts → babel-D6lOuSwk.d.ts} +2 -2
  3. package/dist/chunks/async-storage-DKK-wTD4.js.map +1 -0
  4. package/dist/chunks/build-config-BBThSdlo.js +45 -0
  5. package/dist/chunks/build-config-BBThSdlo.js.map +1 -0
  6. package/dist/{collector-DTahQUiV.js → chunks/collector-DcViW2ZQ.js} +28 -14
  7. package/dist/chunks/collector-DcViW2ZQ.js.map +1 -0
  8. package/dist/chunks/config-engine-DQVQK4NU.js +403 -0
  9. package/dist/chunks/config-engine-DQVQK4NU.js.map +1 -0
  10. package/dist/chunks/css-definitions-BZ9vm0ci.js +1284 -0
  11. package/dist/chunks/css-definitions-BZ9vm0ci.js.map +1 -0
  12. package/dist/chunks/css-resources-Cyl_axbI.js +149 -0
  13. package/dist/chunks/css-resources-Cyl_axbI.js.map +1 -0
  14. package/dist/chunks/debug-Vyauml7U.js +583 -0
  15. package/dist/chunks/debug-Vyauml7U.js.map +1 -0
  16. package/dist/chunks/dsl-CXvoaLnj.js +2097 -0
  17. package/dist/chunks/dsl-CXvoaLnj.js.map +1 -0
  18. package/dist/chunks/hydration-CLdVKcH2.js +59 -0
  19. package/dist/chunks/hydration-CLdVKcH2.js.map +1 -0
  20. package/dist/chunks/react-runtime-so_X_yMo.js +1526 -0
  21. package/dist/chunks/react-runtime-so_X_yMo.js.map +1 -0
  22. package/dist/chunks/runtime-engine-DQ5laRou.js +3056 -0
  23. package/dist/chunks/runtime-engine-DQ5laRou.js.map +1 -0
  24. package/dist/{merge-styles-oklji0KB.js → chunks/shared-utils-OIg0N_dp.js} +42 -3
  25. package/dist/chunks/shared-utils-OIg0N_dp.js.map +1 -0
  26. package/dist/{config-B5kHzuNz.js → chunks/style-engine-CBuOt1e4.js} +2387 -7442
  27. package/dist/chunks/style-engine-CBuOt1e4.js.map +1 -0
  28. package/dist/{css-writer-B-J87ncv.js → chunks/zero-engine-BFh7gKbK.js} +3 -3
  29. package/dist/chunks/zero-engine-BFh7gKbK.js.map +1 -0
  30. package/dist/{collector-DUaHCcTS.d.ts → collector-CAmndLXe.d.ts} +23 -3
  31. package/dist/{config-YsxGv4tq.d.ts → config-BWYrv_Aa.d.ts} +141 -37
  32. package/dist/core/index.d.ts +5 -5
  33. package/dist/core/index.js +9 -6
  34. package/dist/{index-Cd45t5NM.d.ts → index-BzAC3p06.d.ts} +118 -23
  35. package/dist/{index-PqN-DIpn.d.ts → index-D4kRLj2o.d.ts} +128 -68
  36. package/dist/index.d.ts +5 -5
  37. package/dist/index.js +10 -922
  38. package/dist/{merge-styles-CU7JbEwg.d.ts → merge-styles-BFktNP8J.d.ts} +2 -2
  39. package/dist/ssr/astro-client.js +1 -1
  40. package/dist/ssr/astro-middleware-extract-static.d.ts +11 -0
  41. package/dist/ssr/astro-middleware-extract-static.js +9 -0
  42. package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
  43. package/dist/ssr/astro-middleware-extract.d.ts +11 -0
  44. package/dist/ssr/astro-middleware-extract.js +9 -0
  45. package/dist/ssr/astro-middleware-extract.js.map +1 -0
  46. package/dist/ssr/astro-middleware-static.d.ts +3 -1
  47. package/dist/ssr/astro-middleware.d.ts +3 -1
  48. package/dist/ssr/astro.d.ts +45 -3
  49. package/dist/ssr/astro.js +157 -14
  50. package/dist/ssr/astro.js.map +1 -1
  51. package/dist/ssr/index.d.ts +7 -7
  52. package/dist/ssr/index.js +4 -4
  53. package/dist/ssr/index.js.map +1 -1
  54. package/dist/ssr/next-config.d.ts +66 -0
  55. package/dist/ssr/next-config.js +135 -0
  56. package/dist/ssr/next-config.js.map +1 -0
  57. package/dist/ssr/next.d.ts +9 -2
  58. package/dist/ssr/next.js +26 -10
  59. package/dist/ssr/next.js.map +1 -1
  60. package/dist/static/index.d.ts +2 -2
  61. package/dist/static/index.js +1 -1
  62. package/dist/zero/babel.d.ts +1 -1
  63. package/dist/zero/babel.js +25 -24
  64. package/dist/zero/babel.js.map +1 -1
  65. package/dist/zero/index.d.ts +1 -1
  66. package/dist/zero/index.js +1 -1
  67. package/dist/zero/next.d.ts +1 -1
  68. package/docs/README.md +5 -3
  69. package/docs/adoption.md +3 -3
  70. package/docs/ai-agents.md +83 -78
  71. package/docs/comparison.md +12 -12
  72. package/docs/configuration.md +47 -47
  73. package/docs/debug.md +19 -7
  74. package/docs/dsl.md +3 -3
  75. package/docs/getting-started.md +10 -10
  76. package/docs/injector.md +62 -25
  77. package/docs/methodology.md +3 -3
  78. package/docs/migration-v3.md +49 -49
  79. package/docs/plugins.md +37 -33
  80. package/docs/react-api.md +2 -2
  81. package/docs/runtime-benchmarks.md +351 -0
  82. package/docs/ssr.md +219 -61
  83. package/docs/styles.md +43 -11
  84. package/docs/tasty-static.md +136 -103
  85. package/package.json +80 -11
  86. package/dist/async-storage-DKK-wTD4.js.map +0 -1
  87. package/dist/collector-DTahQUiV.js.map +0 -1
  88. package/dist/config-B5kHzuNz.js.map +0 -1
  89. package/dist/context-CA8YKeMn.js +0 -24
  90. package/dist/context-CA8YKeMn.js.map +0 -1
  91. package/dist/core-Dr4u1NVD.js +0 -1566
  92. package/dist/core-Dr4u1NVD.js.map +0 -1
  93. package/dist/css-writer-B-J87ncv.js.map +0 -1
  94. package/dist/format-global-rules-DklyaXv-.js +0 -22
  95. package/dist/format-global-rules-DklyaXv-.js.map +0 -1
  96. package/dist/format-rules-DKOA-6qu.js +0 -130
  97. package/dist/format-rules-DKOA-6qu.js.map +0 -1
  98. package/dist/hydrate-OeMX99We.js +0 -37
  99. package/dist/hydrate-OeMX99We.js.map +0 -1
  100. package/dist/index.js.map +0 -1
  101. package/dist/keyframes-CV8azJf3.js +0 -493
  102. package/dist/keyframes-CV8azJf3.js.map +0 -1
  103. package/dist/merge-styles-oklji0KB.js.map +0 -1
  104. package/dist/resolve-recipes-DTG81rzl.js +0 -144
  105. package/dist/resolve-recipes-DTG81rzl.js.map +0 -1
  106. /package/dist/{async-storage-DKK-wTD4.js → chunks/async-storage-DKK-wTD4.js} +0 -0
@@ -259,12 +259,12 @@ Tasty is more opinionated.
259
259
 
260
260
  It behaves less like "TypeScript that outputs CSS" and more like a **state-aware style compiler**. It is designed to encode higher-level styling semantics rather than only expose CSS primitives in typed form.
261
261
 
262
- This also makes Tasty's rendering model notable:
262
+ This also makes Tasty's delivery model notable:
263
263
 
264
- - `tasty()` components are hook-free and work as React Server Components. In server-only contexts (Next.js RSC, Astro without islands), they produce static HTML + CSS with zero client JavaScript — the full feature set is available without sacrificing server rendering
265
- - `tastyStatic()` with the Babel plugin produces static class name strings via build-time extraction, with no React dependency at runtime — the output works with any JavaScript framework
264
+ - `tasty()` components are hook-free and work as React Server Components. In server-only contexts, they produce HTML + CSS with zero Tasty styling runtime in the browser while retaining the full React feature set. Astro without islands is the concrete integration; server-only Next.js RSC uses the same architecture.
265
+ - `tastyStatic()` with the Babel plugin produces static class-name objects via build-time extraction, with no React dependency — the output works with any JavaScript framework.
266
266
 
267
- Runtime features like `styleProps`, sub-element components, and dynamic variants are available in the `tasty()` path. The `tastyStatic()` path is framework-agnostic but limited to the DSL, tokens, and state logic.
267
+ React component features like `styleProps`, sub-element components, and dynamic variants are available in the `tasty()` path whether it renders on the server or in the browser. The `tastyStatic()` path is framework-agnostic but limited to the DSL, tokens, and state logic.
268
268
 
269
269
  So the tradeoff is roughly:
270
270
 
@@ -326,28 +326,28 @@ So while Stitches and Emotion are strong tools for building components, Tasty is
326
326
 
327
327
  That makes it narrower in audience, but deeper in architectural ambition.
328
328
 
329
- There is also a fundamental architectural difference: Emotion and styled-components rely on React context and hooks internally, which means they require `'use client'` in modern React and cannot run as React Server Components. Tasty's style functions and `tasty()` components are hook-free, so they work as server components by default and produce zero client JavaScript in server-only contexts. This is not a minor compatibility detail — it means Tasty-based components stay as server components until _your_ code needs interactivity, while Emotion and styled-components force the client boundary at the styling layer.
329
+ There is also a fundamental architectural difference: Emotion and styled-components rely on React context and hooks internally, which means they require `'use client'` in modern React and cannot run as React Server Components. Tasty's style functions and `tasty()` components are hook-free, so they work as server components by default and ship no Tasty styling runtime in server-only contexts. This is not a minor compatibility detail — it means Tasty-based components stay as server components until _your_ code needs interactivity, while Emotion and styled-components force the client boundary at the styling layer.
330
330
 
331
331
  For teams evaluating runtime styling at scale, Tasty also documents its runtime benchmarks and caching model in the main [README](../README.md#performance). That matters, but it is still secondary to the core question of whether you want Tasty's deterministic selector model.
332
332
 
333
333
  ---
334
334
 
335
- ## Build-time vs runtime
335
+ ## Where styles are computed
336
336
 
337
337
  Tasty is not limited to one execution model.
338
338
 
339
- The term "runtime" in `tasty()` refers to _when_ style computation happens — during React rendering — not to where that rendering occurs. In server-only contexts (Next.js RSC without `'use client'`, Astro without `client:*` directives, SSG), `tasty()` components render on the server, produce static HTML + CSS, and ship **zero client JavaScript**. The full feature set — `styleProps`, sub-elements, variants, state maps — is available. The result is the same as what `tastyStatic()` produces, but without giving up any runtime capabilities.
339
+ Zero-runtime delivery is an outcome, not a synonym for `tastyStatic()`. `tasty()` computes styles during React rendering, but that rendering can happen only on the server. In that case it produces HTML + CSS and ships **zero Tasty styling runtime** to the browser. The full feature set — `styleProps`, sub-elements, variants, state maps — remains available. Astro's `tastyIntegration({ islands: false })` is the explicit setup for this path. Server-only Next.js RSC follows the same architecture; verify the generated output for the target deployment.
340
340
 
341
341
  Client JavaScript only enters the picture when a component needs React interactivity (state, effects, event handlers) — and that is the consuming component's decision, never Tasty's. Tasty never forces the `'use client'` boundary.
342
342
 
343
- `tastyStatic()` with the Babel plugin is for a different scenario: when you want build-time CSS extraction **without React at runtime**. The output is framework-agnostic — any JavaScript framework can consume the resulting class names and CSS. This makes Tasty usable as the compiler layer underneath a design-system implementation, even outside the React ecosystem.
343
+ `tastyStatic()` with the Babel plugin reaches zero-runtime delivery differently: CSS is extracted **during the build**, without React. The output is framework-agnostic — any JavaScript framework can consume the resulting class names and CSS. This makes Tasty usable as the compiler layer underneath a design-system implementation, even outside the React ecosystem.
344
344
 
345
345
  The tradeoff is that some capabilities — `styleProps`, sub-element components (`<Card.Title>`), dynamic variants — are tied to the `tasty()` path. The `tastyStatic()` path is best understood as extraction and compilation of the DSL, tokens, and state logic without a React dependency.
346
346
 
347
- This flexibility is one of Tasty's more unusual strengths:
347
+ This gives Tasty two zero-runtime paths:
348
348
 
349
- - `tasty()` as the default for all React setups — zero client JS in server-only contexts, full feature set, SSR integration available when client hydration is needed
350
- - `tastyStatic()` as a static compiler whose output works with any framework, including non-React ones
349
+ - server-only `tasty()` for the full React component API, with CSS generated during server or static rendering
350
+ - `tastyStatic()` as a build-time compiler whose output works with any framework, including non-React ones
351
351
 
352
352
  ---
353
353
 
@@ -443,6 +443,6 @@ Tasty is most compelling when the problem is not just "how do we write styles,"
443
443
  - [React API](react-api.md) — `tasty()` factory, component props, variants, sub-elements, style functions
444
444
  - [Style Properties](styles.md) — complete reference for all enhanced style properties
445
445
  - [Configuration](configuration.md) — tokens, recipes, custom units, style handlers, and TypeScript extensions
446
- - [Zero Runtime (tastyStatic)](tasty-static.md) — build-time static styling with Babel plugin
446
+ - [Build-Time Extraction (`tastyStatic`)](tasty-static.md) — static styling with the Babel plugin
447
447
  - [Adoption Guide](adoption.md) — where Tasty sits in the stack, incremental adoption, and what changes for product engineers
448
448
  - [Server-Side Rendering](ssr.md) — SSR setup for Next.js, Astro, and generic frameworks
@@ -47,35 +47,35 @@ These docs use `data-schema="dark"` in examples. If your app already standardize
47
47
 
48
48
  ## Options
49
49
 
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'` (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). |
79
79
 
80
80
  ---
81
81
 
@@ -150,7 +150,7 @@ All writes share one FIFO queue — component rules, global rules, raw CSS,
150
150
  Draining it in insertion order keeps the sheet byte-identical to unbatched
151
151
  output, which matters because equal-specificity rules resolve by document order.
152
152
 
153
- ### SSR, RSC and zero-runtime
153
+ ### Client rendering, server rendering, and build-time extraction
154
154
 
155
155
  **Server render — nothing to batch, safe to leave enabled.** SSR and RSC collect
156
156
  CSS as text through `ServerStyleCollector`; the runtime injector never runs
@@ -175,12 +175,12 @@ tasty components to be worth it. `'always'` needs no provider and covers every
175
175
  island, at the cost of the measurement hazard above. Either way, an island that
176
176
  only re-hydrates server-rendered classes has nothing to batch.
177
177
 
178
- **Zero-runtime (`tastyStatic`) — unaffected.** Build-time extraction never
178
+ **Build-time extraction (`tastyStatic`) — unaffected.** The Babel plugin never
179
179
  touches the injector: the babel plugin emits either a CSS file import or an
180
180
  `injectCSS()` call from `@tenphi/tasty/static/inject`, which appends text to a
181
181
  single `<style data-tasty-static>` element. `batchInjection` only defers CSSOM
182
- writes made by the runtime injector, so extracted styles are unchanged. In an app
183
- 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.
184
184
 
185
185
  ### `flushStyles()`
186
186
 
@@ -284,7 +284,7 @@ color syntax works on every `<color>`, including a `color-mix()`, a
284
284
 
285
285
  ## Name Prefix
286
286
 
287
- 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.
288
288
 
289
289
  ```jsx
290
290
  configure({
@@ -294,12 +294,12 @@ configure({
294
294
 
295
295
  The prefix is prepended verbatim to the hash, so include any separator inside the prefix string itself:
296
296
 
297
- | Setting | Class | Keyframe | Counter-style |
298
- | ----------------------------- | ------------- | -------------- | -------------- |
299
- | `'t'` (runtime default) | `t1a2b3` | `tk1a2b3` | `tc1a2b3` |
300
- | `'ts'` (zero-runtime default) | `ts1a2b3` | `tsk1a2b3` | `tsc1a2b3` |
301
- | `'mb'` | `mb1a2b3` | `mbk1a2b3` | `mbc1a2b3` |
302
- | `'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` |
303
303
 
304
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.
305
305
 
@@ -309,12 +309,12 @@ The single-letter discriminators (`k` for keyframes, `c` for counter-styles) kee
309
309
  - Validated at `configure()` time; an invalid prefix throws synchronously rather than silently producing broken hydration.
310
310
  - Locked once styles have been generated, like all other config.
311
311
 
312
- ### Coexistence with the zero-runtime build
312
+ ### Coexistence with build-time extraction
313
313
 
314
- 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:
315
315
 
316
316
  ```jsx
317
- // app config (runtime / SSR / RSC)
317
+ // app config (client / SSR / RSC)
318
318
  configure({ namePrefix: 'mb' });
319
319
 
320
320
  // tasty-zero.config.ts (Babel plugin)
@@ -352,7 +352,7 @@ configure({
352
352
  - `#name` keys become `--name-color` custom properties
353
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.
354
354
 
355
- 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.
356
356
 
357
357
  ---
358
358
 
@@ -617,7 +617,7 @@ configure({
617
617
 
618
618
  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.
619
619
 
620
- 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).
620
+ 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).
621
621
 
622
622
  ---
623
623
 
@@ -625,7 +625,7 @@ Global styles are automatically emitted in all rendering modes: runtime (client)
625
625
 
626
626
  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.
627
627
 
628
- 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.
628
+ 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.
629
629
 
630
630
  Supported types:
631
631
 
@@ -784,7 +784,7 @@ Handlers run at the very top of every component's render, before any prop is des
784
784
 
785
785
  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.
786
786
 
787
- 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.
787
+ 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.
788
788
 
789
789
  See [Plugins → Props middleware](plugins.md#props-middleware) for the full contract and a worked example.
790
790
 
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';
package/docs/dsl.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  This is the language behind Tasty’s predictable state resolution. Every property can use a state map; later branches declare higher priority, and Tasty compiles them so only one generated selector can match.
4
4
 
5
- The same value syntax, state maps, tokens, units, extension semantics, and special declarations apply to runtime `tasty()` and build-time `tastyStatic()`.
5
+ The same value syntax, state maps, tokens, units, extension semantics, and special declarations apply to React `tasty()`—whether rendered on the client or server—and build-time `tastyStatic()`.
6
6
 
7
- For the runtime React API (`tasty()`, hooks, component props), see [React API](react-api.md). For all enhanced style properties, see [Style Properties](styles.md). For global configuration, see [Configuration](configuration.md).
7
+ For the React API (`tasty()`, style functions, component props), see [React API](react-api.md). For all enhanced style properties, see [Style Properties](styles.md). For global configuration, see [Configuration](configuration.md).
8
8
 
9
9
  ---
10
10
 
@@ -1018,4 +1018,4 @@ For a complete reference of all enhanced style properties — syntax, values, mo
1018
1018
  - **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
1019
1019
  - **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
1020
1020
  - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
1021
- - **[Zero Runtime (tastyStatic)](tasty-static.md)** — Build-time static styling with Babel plugin
1021
+ - **[Build-Time Extraction (`tastyStatic`)](tasty-static.md)** — Static styling with the Babel plugin
@@ -200,19 +200,19 @@ export default [tasty.configs.strict];
200
200
 
201
201
  ## Choosing a rendering mode
202
202
 
203
- `tasty()` is the default for all React apps. All `tasty()` components and style functions are hook-free and work as React Server Components without `'use client'`. In server-only contexts, they produce zero client JavaScript with the full feature set.
203
+ `tasty()` is the default for all React apps. All `tasty()` components and style functions are hook-free and work as React Server Components without `'use client'`. Zero-runtime delivery is not tied to one API: both server-only `tasty()` and build-time `tastyStatic()` can ship no Tasty styling runtime to the browser.
204
204
 
205
- | Approach | Entry point | Best for | Trade-off |
206
- | ----------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
207
- | **Runtime (default)** | `tasty()` from `@tenphi/tasty` | All React apps — server-rendered by default, zero client JS until you need interactivity | Full feature set (styleProps, sub-elements, variants); CSS computed during rendering |
208
- | **Runtime + SSR integration** | Add `@tenphi/tasty/ssr/*` | Apps with client-side hydration (Next.js client components, Astro islands) | Adds CSS batching, deduplication, FOUC prevention, and client cache hydration |
209
- | **Zero-runtime** | `tastyStatic()` from `@tenphi/tasty/static` | Non-React frameworks or build-time extraction without React | Requires Babel plugin; no `styleProps` or runtime-only APIs |
205
+ | Approach | Authoring API | CSS is generated | Tasty runtime in the browser | Best for |
206
+ | ------------------------- | ------------------------------------------- | ----------------------------------- | ---------------------------- | ---------------------------------------------------------------- |
207
+ | **Server-only React** | `tasty()`; optional `@tenphi/tasty/ssr/*` | During server or static rendering | None | Astro without islands, server-only RSC, SSG |
208
+ | **Hydrated React** | `tasty()` plus `@tenphi/tasty/ssr/*` | During SSR, then on demand in React | Only in hydrated components | Interactive React apps, Next.js client components, Astro islands |
209
+ | **Build-time extraction** | `tastyStatic()` from `@tenphi/tasty/static` | During the build | None in file mode | Non-React frameworks or extraction before rendering |
210
210
 
211
211
  Both `tasty()` and `tastyStatic()` share the same DSL, tokens, units, and state mappings.
212
212
 
213
- - **Runtime** is the default and requires no extra setup beyond `@tenphi/tasty`. In server-only contexts (Next.js RSC, Astro without `client:*` directives, SSG), `tasty()` produces static HTML + CSS with zero client JavaScript — the same end result as `tastyStatic()` but with the full feature set. For example, `tasty()` + `tastyIntegration()` in Astro without islands gives you the complete API with zero JS shipped.
214
- - **SSR integration** is only needed when your app also has client-side rendering. Add `@tenphi/tasty/ssr/next`, `@tenphi/tasty/ssr/astro`, or the core SSR API to get CSS deduplication and cache hydration. See [Server-Side Rendering](ssr.md).
215
- - **Zero-runtime** requires the Babel plugin and additional peer dependencies. Use it when you need build-time extraction without a React runtime. See [Zero Runtime (tastyStatic)](tasty-static.md).
213
+ - **Server-only `tasty()`** keeps the full feature set (`styleProps`, sub-elements, variants) while generating CSS during server rendering. `tasty()` plus `tastyIntegration({ islands: false })` in Astro is the verified concrete setup and ships no client JavaScript. The same server-only architecture applies to Next.js RSC; verify the generated output for your deployment.
214
+ - **Hydrated `tasty()`** uses the SSR integrations to batch and deduplicate server CSS, prevent FOUC, and hydrate the client cache. The browser styling pipeline is available only where React components hydrate. See [Server-Side Rendering](ssr.md).
215
+ - **Build-time extraction** uses the Babel plugin and additional peer dependencies. Choose `tastyStatic()` when CSS must be generated before rendering or when the consumer is not React. File mode ships no styling runtime; inject mode intentionally includes a tiny CSS injector. See [Build-Time Extraction (`tastyStatic`)](tasty-static.md).
216
216
 
217
217
  ---
218
218
 
@@ -235,4 +235,4 @@ Both `tasty()` and `tastyStatic()` share the same DSL, tokens, units, and state
235
235
 
236
236
  - Styles are missing on first render: make sure the file that calls `configure()` is imported before any `tasty()` component renders.
237
237
  - Token or unit values are not what you expect: check your `configure({ tokens, units })` setup, then inspect the generated CSS variables with [Debug Utilities](debug.md).
238
- - You need build-time extraction or server-rendered CSS delivery: use [Zero Runtime (tastyStatic)](tasty-static.md) for extraction, or add [Server-Side Rendering](ssr.md) on top of runtime `tasty()` when your framework renders on the server.
238
+ - You need zero-runtime delivery: use server-only [`tasty()` with server rendering](ssr.md) to keep the full React API, or use [`tastyStatic()` build-time extraction](tasty-static.md) when styles must be compiled before rendering.
package/docs/injector.md CHANGED
@@ -9,7 +9,7 @@ A high-performance CSS-in-JS solution that powers the Tasty design system with e
9
9
  The Style Injector is the core engine behind Tasty's styling system, providing:
10
10
 
11
11
  - **Hash-based deduplication** - Identical CSS gets the same className
12
- - **Reference counting** - Automatic cleanup when components unmount (refCount = 0)
12
+ - **DOM-driven lifetime** - Styles are collected once nothing renders them
13
13
  - **CSS nesting flattening** - Handles `&`, `.Class`, `SubElement` patterns
14
14
  - **At-rule injection** - First-class `@keyframes`, `@property`, `@font-face`, `@counter-style`, and `@function` support
15
15
  - **Smart cleanup** - CSS rules batched cleanup, keyframes disposed immediately
@@ -63,10 +63,16 @@ const result = inject([{
63
63
 
64
64
  console.log(result.className); // 't-abc123'
65
65
 
66
- // Cleanup when component unmounts (refCount decremented)
66
+ // Release the pin; the class becomes collectible once nothing renders it
67
67
  result.dispose();
68
68
  ```
69
69
 
70
+ `inject()` **pins** the class it returns, and `gc()` never evicts a pinned class
71
+ — that is what `dispose()` releases. Pass `{ pin: false }` when the caller keeps
72
+ no handle and the DOM is the only record that the class is in use; `dispose()`
73
+ is then a no-op. The render path (`tasty()` / `computeStyles()`) injects this
74
+ way, because a hook-free render has no unmount signal to dispose on.
75
+
70
76
  ### `injectGlobal(rules, options?): { dispose: () => void }`
71
77
 
72
78
  Injects global styles that don't reserve tasty class names.
@@ -245,8 +251,27 @@ Dispose, ref-counted cleanup and GC therefore behave identically in every mode.
245
251
  - Most options have sensible defaults and auto-detection
246
252
  - `configure()` is optional - the injector works with defaults
247
253
  - **Configuration is locked after styles are generated** - calling `configure()` after first render will emit a warning and be ignored
248
- - `gc.touchInterval`: Number of touch events between GC cycles. Each style render counts as a touch. When the counter reaches this value, GC is scheduled via `requestIdleCallback`.
249
- - `gc.capacity`: Maximum number of unused styles (refCount = 0, not in DOM) to retain. When exceeded, the oldest are evicted first. Actively referenced styles don't count against this limit.
254
+ - `gc.touchInterval`: Number of renders between sweeps. When the counter reaches this value, a sweep is scheduled via `requestIdleCallback`; without idle callbacks nothing is collected automatically.
255
+ - `gc.grace`: How long a class is left alone after collection first notices nothing is carrying it, in milliseconds (default `10000`).
256
+
257
+ **What a sweep does.** Everything the injector holds falls into one of five bands, and only the last is ever deleted:
258
+
259
+ | Band | | Deleted |
260
+ |---|---|---|
261
+ | 1 | **Rendered** — some element carries the class right now | never |
262
+ | 2 | **Not ours** — queued for a batched write, pre-allocated, or server-rendered | never |
263
+ | 3 | **Hot** — nothing carries it, but that was noticed less than `grace` ago | never |
264
+ | 4 | **Cached** — cold, but within `capacity` when ordered by when it went cold | never |
265
+ | 5 | Everything else | on every sweep |
266
+
267
+ `gc({ force: true })` and `cleanup()` take bands 4 and 5 together, ignoring capacity. Band 3 is spared even then: an explicit cleanup is still no reason to take rules from a render that has not committed yet.
268
+
269
+ > **Why band 3 exists.** Rendering is not commit-aware: a render can resolve a class and commit it a little later, and in between nothing on the page carries it. From outside React that is indistinguishable from a class that is finished, so collection does not try to tell them apart — it leaves alone anything only just noticed to be cold. A render would have to stay pending for the whole window to lose its rules, and it gets them back on its next render.
270
+ >
271
+ > The clock starts when a sweep *notices*, not when the element actually left — nothing observes that moment, so starting it at the sighting is what gives every class the same full window however long ago it went.
272
+ >
273
+ > This is also why collection costs nothing to run: the timestamps are written by the sweep's own DOM scan, so rendering tracks nothing per class.
274
+ - `gc.capacity`: Maximum number of unused styles (not in the DOM, not pinned) to retain. When exceeded, the least recently used are evicted first. Rendered and pinned styles don't count against this limit.
250
275
 
251
276
  ---
252
277
 
@@ -329,23 +354,28 @@ const button2 = inject([{
329
354
  console.log(button1.className === button2.className); // true
330
355
  ```
331
356
 
332
- ### Reference Counting
357
+ ### Pinning
333
358
 
334
359
  ```typescript
335
- // Multiple components using the same styles
360
+ // Multiple callers using the same styles
336
361
  const comp1 = inject([commonStyle]);
337
362
  const comp2 = inject([commonStyle]);
338
363
  const comp3 = inject([commonStyle]);
339
364
 
340
- // Style is kept alive while any component uses it
341
- comp1.dispose(); // refCount: 3 → 2
342
- comp2.dispose(); // refCount: 2 → 1
343
- comp3.dispose(); // refCount: 1 → 0, eligible for bulk cleanup
365
+ // Style is pinned while any caller holds a handle
366
+ comp1.dispose(); // pins: 3 → 2
367
+ comp2.dispose(); // pins: 2 → 1
368
+ comp3.dispose(); // pins: 1 → 0, now up to the DOM and gc()
344
369
 
345
- // Rule exists but refCount = 0 means unused
346
- // Next inject() with same styles will increment refCount and reuse immediately
370
+ // Unpinned does not mean deleted: the rule stays cached and is reused instantly
371
+ // by the next inject(). gc() decides when it actually goes.
347
372
  ```
348
373
 
374
+ Pinning covers callers that hold a handle. Styles that come from rendering are
375
+ never pinned — see `{ pin: false }` under
376
+ [`inject()`](#injectrules-options-injectresult) — so what keeps them alive is
377
+ being in the DOM, and nothing else.
378
+
349
379
  ### Garbage Collection
350
380
 
351
381
  ```typescript
@@ -353,6 +383,9 @@ import { configure, gc } from '@tenphi/tasty';
353
383
 
354
384
  // Keyframes: Disposed immediately when refCount = 0 (safer for global scope)
355
385
  // CSS rules: Tracked by touch count and cleaned up via gc()
386
+ //
387
+ // A CSS rule is collectible when no element carries its class AND nobody
388
+ // pinned it with inject().
356
389
 
357
390
  configure({
358
391
  gc: {
@@ -367,15 +400,19 @@ gc();
367
400
  // Force-remove ALL unused styles (e.g. on route change or test teardown):
368
401
  gc({ force: true });
369
402
 
370
- // GC is also triggered automatically by touch count during rendering.
371
- // Every `touchInterval` touches, GC is scheduled via requestIdleCallback.
403
+ // cleanup() is the same thing:
404
+ cleanup();
405
+
406
+ // Every `touchInterval` renders, a sweep is scheduled in idle time. It scans
407
+ // the DOM for the classes actually on the page and sorts everything the
408
+ // injector holds into five bands — only the last one is deleted. See below.
372
409
 
373
410
  // Benefits:
374
411
  // - Activity-proportional: busy apps trigger GC more often
375
412
  // - DOM-safe: styles currently in the DOM are never evicted
376
413
  // - Oldest-first: least recently used styles are evicted first
377
414
  // - Keyframes: Immediate cleanup prevents global namespace pollution
378
- // - Unused styles can be instantly reactivated (just increment refCount)
415
+ // - Unused styles stay cached until evicted, so re-rendering them is a cache hit
379
416
  ```
380
417
 
381
418
  ### Shadow DOM Support
@@ -470,11 +507,11 @@ const metrics = injector.instance.getMetrics();
470
507
  console.log({
471
508
  cacheHits: metrics.hits, // Successful cache hits
472
509
  cacheMisses: metrics.misses, // New styles injected
473
- unusedHits: metrics.unusedHits, // Current unused styles (calculated on demand)
510
+ unusedHits: metrics.unusedHits, // Styles currently eligible for eviction (scans the DOM)
474
511
  bulkCleanups: metrics.bulkCleanups, // Number of bulk cleanup operations
475
512
  stylesCleanedUp: metrics.stylesCleanedUp, // Total styles removed in bulk cleanups
476
513
  totalInsertions: metrics.totalInsertions, // Lifetime insertions
477
- totalUnused: metrics.totalUnused, // Total styles marked as unused (refCount = 0)
514
+ totalUnused: metrics.totalUnused, // Times a pinned style lost its last pin
478
515
  startTime: metrics.startTime, // Metrics collection start timestamp
479
516
  cleanupHistory: metrics.cleanupHistory, // Detailed cleanup operation history
480
517
  });
@@ -521,7 +558,7 @@ metrics.cleanupHistory.forEach(cleanup => {
521
558
  const buttonBase = 'padding: 8px 16px; border-radius: 4px;';
522
559
 
523
560
  // ✅ Avoid frequent disposal and re-injection
524
- // Let the reference counting system handle cleanup
561
+ // Let the injector handle cleanup
525
562
 
526
563
  // ✅ Use bulk operations for global styles
527
564
  injectGlobal([
@@ -547,13 +584,13 @@ configure({
547
584
  // The injector automatically manages memory through:
548
585
 
549
586
  // 1. Hash-based deduplication - same CSS = same className
550
- // 2. Reference counting - styles stay alive while in use (refCount > 0)
551
- // 3. Immediate keyframes cleanup - disposed instantly when refCount = 0
552
- // 4. Touch-count GC - unused CSS rules are evicted oldest-first when over capacity
553
- // 5. DOM safety guard - styles visible in the DOM are never evicted
587
+ // 2. DOM-driven lifetime - a rendered class is never evicted
588
+ // 3. Pinning - inject() callers hold their classes until they dispose
589
+ // 4. Immediate keyframes cleanup - disposed instantly when refCount = 0
590
+ // 5. Touch-count GC - unused CSS rules are evicted oldest-first when over capacity
554
591
 
555
592
  // Manual cleanup is rarely needed but available:
556
- cleanup(); // Force immediate cleanup of all unused CSS rules (refCount = 0)
593
+ cleanup(); // Remove every rule that is neither rendered nor pinned
557
594
  destroy(); // Nuclear option: remove all stylesheets and reset
558
595
  ```
559
596
 
@@ -575,9 +612,9 @@ const StyledButton = tasty({
575
612
 
576
613
  // Internally uses the injector:
577
614
  // 1. Styles are parsed into StyleResult objects
578
- // 2. inject() is called with the parsed results
615
+ // 2. inject() is called with the parsed results, unpinned
579
616
  // 3. Component gets the returned className
580
- // 4. dispose() is called when component unmounts
617
+ // 4. gc() reclaims the class once no element carries it
581
618
  ```
582
619
 
583
620
  For most development, you'll use the [React API](./react-api.md) rather than the injector directly. The injector provides the high-performance foundation that makes Tasty's declarative styling possible.
@@ -122,10 +122,10 @@ Tasty exports predefined style prop lists that group properties by role. Use the
122
122
  | Preset | Properties | Typical use |
123
123
  | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- |
124
124
  | `FLOW_STYLES` | flow, gap, columnGap, rowGap, align, justify, placeItems, placeContent, alignItems, alignContent, justifyItems, justifyContent, gridColumns, gridRows, gridTemplate, gridAreas | Layout containers (`Space`, `Grid`) |
125
- | `POSITION_STYLES` | gridArea, gridColumn, gridRow, order, placeSelf, alignSelf, justifySelf, zIndex, margin, inset, position | Positioned elements (`Button`, `Badge`) |
126
- | `DIMENSION_STYLES` | width, height, flexBasis, flexGrow, flexShrink, flex | Sized elements |
125
+ | `POSITION_STYLES` | gridArea, gridColumn, gridRow, order, placeSelf, alignSelf, justifySelf, zIndex, physical/logical margin, inset, scroll margin/padding, position | Positioned elements (`Button`, `Badge`) |
126
+ | `DIMENSION_STYLES` | width, height, blockSize, inlineSize, flexBasis, flexGrow, flexShrink, flex | Sized elements |
127
127
  | `COLOR_STYLES` | color, fill, fade, image | Color-customizable elements |
128
- | `BLOCK_STYLES` | padding, paddingInline, paddingBlock, overflow, scrollbar, textAlign, border, radius, shadow, outline | Block-level containers |
128
+ | `BLOCK_STYLES` | padding, blockPadding, inlinePadding, overflow, scrollbar, textAlign, border, blockBorder, inlineBorder, radius, shadow, outline | Block-level containers |
129
129
  | `CONTAINER_STYLES` | All of the above combined (+ BASE_STYLES) | Fully flexible containers |
130
130
  | `OUTER_STYLES` | POSITION_STYLES + DIMENSION_STYLES + block outer (border, radius, shadow, outline) | Components whose outer shell is customizable |
131
131
  | `INNER_STYLES` | BASE_STYLES + COLOR_STYLES + block inner (padding, overflow, scrollbar) + FLOW_STYLES | Components whose inner layout is customizable |