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

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 (109) hide show
  1. package/README.md +38 -54
  2. package/dist/{babel-BUQGeOXA.d.ts → babel-Zs1Q03qt.d.ts} +2 -2
  3. package/dist/chunks/async-storage-DKK-wTD4.js.map +1 -0
  4. package/dist/chunks/build-config-Mk7waOsw.js +45 -0
  5. package/dist/chunks/build-config-Mk7waOsw.js.map +1 -0
  6. package/dist/{collector-DTahQUiV.js → chunks/collector-Ducc5l3E.js} +29 -15
  7. package/dist/chunks/collector-Ducc5l3E.js.map +1 -0
  8. package/dist/chunks/config-engine-tKK7WxJO.js +331 -0
  9. package/dist/chunks/config-engine-tKK7WxJO.js.map +1 -0
  10. package/dist/chunks/css-definitions-Crstltrf.js +1229 -0
  11. package/dist/chunks/css-definitions-Crstltrf.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-HyyLIf6O.js +543 -0
  15. package/dist/chunks/debug-HyyLIf6O.js.map +1 -0
  16. package/dist/chunks/dsl-D7kBow--.js +1771 -0
  17. package/dist/chunks/dsl-D7kBow--.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-UlZcbsEN.js +877 -0
  21. package/dist/chunks/react-runtime-UlZcbsEN.js.map +1 -0
  22. package/dist/chunks/runtime-engine-D2WOLvd6.js +3439 -0
  23. package/dist/chunks/runtime-engine-D2WOLvd6.js.map +1 -0
  24. package/dist/chunks/runtime-state-DOeH6rsG.js +138 -0
  25. package/dist/chunks/runtime-state-DOeH6rsG.js.map +1 -0
  26. package/dist/{merge-styles-oklji0KB.js → chunks/shared-utils-Bgl8FNGJ.js} +64 -24
  27. package/dist/chunks/shared-utils-Bgl8FNGJ.js.map +1 -0
  28. package/dist/chunks/style-engine-C4ZcOAPb.js +6915 -0
  29. package/dist/chunks/style-engine-C4ZcOAPb.js.map +1 -0
  30. package/dist/{css-writer-B-J87ncv.js → chunks/zero-engine-D4Rluo7u.js} +3 -3
  31. package/dist/chunks/zero-engine-D4Rluo7u.js.map +1 -0
  32. package/dist/{collector-DUaHCcTS.d.ts → collector-hl5KNH7k.d.ts} +23 -3
  33. package/dist/{config-YsxGv4tq.d.ts → config-DWL1ZYue.d.ts} +150 -55
  34. package/dist/core/index.d.ts +5 -5
  35. package/dist/core/index.js +9 -6
  36. package/dist/{index-PqN-DIpn.d.ts → index-D9N9GV24.d.ts} +129 -93
  37. package/dist/index.d.ts +5 -5
  38. package/dist/index.js +11 -922
  39. package/dist/{merge-styles-CU7JbEwg.d.ts → merge-styles-C_oMoJVd.d.ts} +2 -2
  40. package/dist/ssr/astro-client.js +1 -1
  41. package/dist/ssr/astro-middleware-extract-static.d.ts +11 -0
  42. package/dist/ssr/astro-middleware-extract-static.js +9 -0
  43. package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
  44. package/dist/ssr/astro-middleware-extract.d.ts +11 -0
  45. package/dist/ssr/astro-middleware-extract.js +9 -0
  46. package/dist/ssr/astro-middleware-extract.js.map +1 -0
  47. package/dist/ssr/astro-middleware-static.d.ts +3 -1
  48. package/dist/ssr/astro-middleware.d.ts +3 -1
  49. package/dist/ssr/astro.d.ts +45 -3
  50. package/dist/ssr/astro.js +158 -15
  51. package/dist/ssr/astro.js.map +1 -1
  52. package/dist/ssr/index.d.ts +7 -7
  53. package/dist/ssr/index.js +4 -4
  54. package/dist/ssr/index.js.map +1 -1
  55. package/dist/ssr/next-config.d.ts +66 -0
  56. package/dist/ssr/next-config.js +135 -0
  57. package/dist/ssr/next-config.js.map +1 -0
  58. package/dist/ssr/next.d.ts +9 -2
  59. package/dist/ssr/next.js +27 -11
  60. package/dist/ssr/next.js.map +1 -1
  61. package/dist/static/index.d.ts +2 -2
  62. package/dist/static/index.js +1 -1
  63. package/dist/{index-Cd45t5NM.d.ts → types-jKZNpias.d.ts} +898 -803
  64. package/dist/zero/babel.d.ts +1 -1
  65. package/dist/zero/babel.js +25 -24
  66. package/dist/zero/babel.js.map +1 -1
  67. package/dist/zero/index.d.ts +1 -1
  68. package/dist/zero/index.js +1 -1
  69. package/dist/zero/next.d.ts +1 -1
  70. package/docs/README.md +5 -3
  71. package/docs/adoption.md +3 -3
  72. package/docs/ai-agents.md +83 -78
  73. package/docs/comparison.md +12 -12
  74. package/docs/configuration.md +47 -47
  75. package/docs/debug.md +19 -7
  76. package/docs/dsl.md +3 -3
  77. package/docs/getting-started.md +10 -10
  78. package/docs/injector.md +62 -25
  79. package/docs/methodology.md +3 -3
  80. package/docs/migration-v3.md +49 -49
  81. package/docs/plugins.md +37 -33
  82. package/docs/react-api.md +2 -2
  83. package/docs/runtime-benchmarks.md +351 -0
  84. package/docs/ssr.md +219 -61
  85. package/docs/styles.md +43 -11
  86. package/docs/tasty-static.md +136 -103
  87. package/package.json +80 -11
  88. package/dist/async-storage-DKK-wTD4.js.map +0 -1
  89. package/dist/collector-DTahQUiV.js.map +0 -1
  90. package/dist/config-B5kHzuNz.js +0 -12269
  91. package/dist/config-B5kHzuNz.js.map +0 -1
  92. package/dist/context-CA8YKeMn.js +0 -24
  93. package/dist/context-CA8YKeMn.js.map +0 -1
  94. package/dist/core-Dr4u1NVD.js +0 -1566
  95. package/dist/core-Dr4u1NVD.js.map +0 -1
  96. package/dist/css-writer-B-J87ncv.js.map +0 -1
  97. package/dist/format-global-rules-DklyaXv-.js +0 -22
  98. package/dist/format-global-rules-DklyaXv-.js.map +0 -1
  99. package/dist/format-rules-DKOA-6qu.js +0 -130
  100. package/dist/format-rules-DKOA-6qu.js.map +0 -1
  101. package/dist/hydrate-OeMX99We.js +0 -37
  102. package/dist/hydrate-OeMX99We.js.map +0 -1
  103. package/dist/index.js.map +0 -1
  104. package/dist/keyframes-CV8azJf3.js +0 -493
  105. package/dist/keyframes-CV8azJf3.js.map +0 -1
  106. package/dist/merge-styles-oklji0KB.js.map +0 -1
  107. package/dist/resolve-recipes-DTG81rzl.js +0 -144
  108. package/dist/resolve-recipes-DTG81rzl.js.map +0 -1
  109. /package/dist/{async-storage-DKK-wTD4.js → chunks/async-storage-DKK-wTD4.js} +0 -0
@@ -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 |
@@ -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
@@ -407,7 +407,7 @@ On the client, CSS is injected synchronously into the DOM (idempotent via the in
407
407
 
408
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.
409
409
 
410
- 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).
411
411
 
412
412
  ### useStyles
413
413
 
@@ -676,6 +676,6 @@ See the [Functions section of the DSL reference](dsl.md#functions-function) for
676
676
  - **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
677
677
  - **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
678
678
  - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
679
- - **[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
680
680
  - **[Server-Side Rendering](ssr.md)** — SSR setup for Next.js, Astro, and generic frameworks
681
681
  - **[Debug Utilities](debug.md)** — Inspect injected CSS, cache state, and active styles at runtime