@tenphi/tasty 0.0.0-snapshot.db103fd → 0.0.0-snapshot.dc62ea3

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 (365) hide show
  1. package/README.md +196 -140
  2. package/dist/astro-CeYENy2x.js +301 -0
  3. package/dist/astro-CeYENy2x.js.map +1 -0
  4. package/dist/{ssr/async-storage.js → async-storage-DKK-wTD4.js} +16 -6
  5. package/dist/async-storage-DKK-wTD4.js.map +1 -0
  6. package/dist/babel-DWCsafYw.d.ts +83 -0
  7. package/dist/collector-B3OsM252.js +327 -0
  8. package/dist/collector-B3OsM252.js.map +1 -0
  9. package/dist/collector-BTVXj2hS.d.ts +163 -0
  10. package/dist/config-B3gPdCqd.js +12421 -0
  11. package/dist/config-B3gPdCqd.js.map +1 -0
  12. package/dist/config-LfIDmVHx.d.ts +1363 -0
  13. package/dist/context-CA8YKeMn.js +24 -0
  14. package/dist/context-CA8YKeMn.js.map +1 -0
  15. package/dist/core/index.d.ts +5 -33
  16. package/dist/core/index.js +6 -26
  17. package/dist/core-DGm0CFHP.js +1732 -0
  18. package/dist/core-DGm0CFHP.js.map +1 -0
  19. package/dist/css-resources-Cyl_axbI.js +149 -0
  20. package/dist/css-resources-Cyl_axbI.js.map +1 -0
  21. package/dist/{zero/extractor.js → css-writer-Bgowobbx.js} +148 -24
  22. package/dist/css-writer-Bgowobbx.js.map +1 -0
  23. package/dist/{ssr/format-global-rules.js → format-global-rules-DklyaXv-.js} +2 -2
  24. package/dist/format-global-rules-DklyaXv-.js.map +1 -0
  25. package/dist/{ssr/format-rules.js → format-rules-XRw9u7d4.js} +33 -2
  26. package/dist/format-rules-XRw9u7d4.js.map +1 -0
  27. package/dist/hydrate-CNOmZprz.js +37 -0
  28. package/dist/hydrate-CNOmZprz.js.map +1 -0
  29. package/dist/index-Bm9ltvdH.d.ts +1933 -0
  30. package/dist/index-BmogEzTo.d.ts +1575 -0
  31. package/dist/index.d.ts +5 -50
  32. package/dist/index.js +922 -35
  33. package/dist/index.js.map +1 -0
  34. package/dist/keyframes-DE-OE76F.js +535 -0
  35. package/dist/keyframes-DE-OE76F.js.map +1 -0
  36. package/dist/{utils/merge-styles.d.ts → merge-styles-BcLl1u_d.d.ts} +3 -3
  37. package/dist/{utils/merge-styles.js → merge-styles-DuoZEsm9.js} +3 -4
  38. package/dist/merge-styles-DuoZEsm9.js.map +1 -0
  39. package/dist/{utils/resolve-recipes.js → resolve-recipes-H9NqOQuP.js} +4 -6
  40. package/dist/resolve-recipes-H9NqOQuP.js.map +1 -0
  41. package/dist/ssr/astro-client.js +19 -0
  42. package/dist/ssr/astro-client.js.map +1 -0
  43. package/dist/ssr/astro-middleware-extract-static.d.ts +11 -0
  44. package/dist/ssr/astro-middleware-extract-static.js +9 -0
  45. package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
  46. package/dist/ssr/astro-middleware-extract.d.ts +11 -0
  47. package/dist/ssr/astro-middleware-extract.js +9 -0
  48. package/dist/ssr/astro-middleware-extract.js.map +1 -0
  49. package/dist/ssr/astro-middleware-static.d.ts +18 -0
  50. package/dist/ssr/astro-middleware-static.js +18 -0
  51. package/dist/ssr/astro-middleware-static.js.map +1 -0
  52. package/dist/ssr/astro-middleware.d.ts +19 -0
  53. package/dist/ssr/astro-middleware.js +19 -0
  54. package/dist/ssr/astro-middleware.js.map +1 -0
  55. package/dist/ssr/astro.d.ts +117 -11
  56. package/dist/ssr/astro.js +2 -64
  57. package/dist/ssr/index.d.ts +37 -5
  58. package/dist/ssr/index.js +6 -7
  59. package/dist/ssr/index.js.map +1 -1
  60. package/dist/ssr/next-config.d.ts +66 -0
  61. package/dist/ssr/next-config.js +115 -0
  62. package/dist/ssr/next-config.js.map +1 -0
  63. package/dist/ssr/next.d.ts +14 -7
  64. package/dist/ssr/next.js +38 -16
  65. package/dist/ssr/next.js.map +1 -1
  66. package/dist/ssr-collector-ref-COs_ioWl.js +29 -0
  67. package/dist/ssr-collector-ref-COs_ioWl.js.map +1 -0
  68. package/dist/static/index.d.ts +91 -5
  69. package/dist/static/index.js +49 -3
  70. package/dist/static/index.js.map +1 -0
  71. package/dist/static/inject.js.map +1 -1
  72. package/dist/zero/babel.d.ts +2 -182
  73. package/dist/zero/babel.js +80 -40
  74. package/dist/zero/babel.js.map +1 -1
  75. package/dist/zero/index.d.ts +81 -3
  76. package/dist/zero/index.js +2 -3
  77. package/dist/zero/next.d.ts +1 -1
  78. package/dist/zero/next.js +1 -1
  79. package/dist/zero/next.js.map +1 -1
  80. package/docs/README.md +20 -12
  81. package/docs/adoption.md +59 -32
  82. package/docs/ai-agents.md +224 -0
  83. package/docs/comparison.md +76 -48
  84. package/docs/configuration.md +566 -69
  85. package/docs/debug.md +35 -18
  86. package/docs/design-system.md +63 -32
  87. package/docs/dsl.md +527 -179
  88. package/docs/getting-started.md +62 -41
  89. package/docs/injector.md +157 -51
  90. package/docs/methodology.md +116 -35
  91. package/docs/migration-v3.md +285 -0
  92. package/docs/pipeline.md +741 -0
  93. package/docs/plugins.md +353 -0
  94. package/docs/react-api.md +681 -0
  95. package/docs/runtime-benchmarks.md +351 -0
  96. package/docs/ssr.md +356 -130
  97. package/docs/styles.md +79 -16
  98. package/docs/tasty-static.md +149 -87
  99. package/package.json +78 -23
  100. package/tasty.config.ts +6 -0
  101. package/dist/_virtual/_rolldown/runtime.js +0 -7
  102. package/dist/chunks/cacheKey.js +0 -77
  103. package/dist/chunks/cacheKey.js.map +0 -1
  104. package/dist/chunks/definitions.d.ts +0 -37
  105. package/dist/chunks/definitions.js +0 -258
  106. package/dist/chunks/definitions.js.map +0 -1
  107. package/dist/chunks/index.d.ts +0 -1
  108. package/dist/chunks/renderChunk.d.ts +0 -1
  109. package/dist/chunks/renderChunk.js +0 -59
  110. package/dist/chunks/renderChunk.js.map +0 -1
  111. package/dist/config.d.ts +0 -366
  112. package/dist/config.js +0 -503
  113. package/dist/config.js.map +0 -1
  114. package/dist/counter-style/index.js +0 -51
  115. package/dist/counter-style/index.js.map +0 -1
  116. package/dist/debug.d.ts +0 -89
  117. package/dist/debug.js +0 -453
  118. package/dist/debug.js.map +0 -1
  119. package/dist/font-face/index.js +0 -63
  120. package/dist/font-face/index.js.map +0 -1
  121. package/dist/hooks/index.d.ts +0 -7
  122. package/dist/hooks/resolve-ssr-collector.js +0 -14
  123. package/dist/hooks/resolve-ssr-collector.js.map +0 -1
  124. package/dist/hooks/useCounterStyle.d.ts +0 -50
  125. package/dist/hooks/useCounterStyle.js +0 -46
  126. package/dist/hooks/useCounterStyle.js.map +0 -1
  127. package/dist/hooks/useFontFace.d.ts +0 -43
  128. package/dist/hooks/useFontFace.js +0 -70
  129. package/dist/hooks/useFontFace.js.map +0 -1
  130. package/dist/hooks/useGlobalStyles.d.ts +0 -30
  131. package/dist/hooks/useGlobalStyles.js +0 -78
  132. package/dist/hooks/useGlobalStyles.js.map +0 -1
  133. package/dist/hooks/useKeyframes.d.ts +0 -56
  134. package/dist/hooks/useKeyframes.js +0 -64
  135. package/dist/hooks/useKeyframes.js.map +0 -1
  136. package/dist/hooks/useProperty.d.ts +0 -79
  137. package/dist/hooks/useProperty.js +0 -109
  138. package/dist/hooks/useProperty.js.map +0 -1
  139. package/dist/hooks/useRawCSS.d.ts +0 -53
  140. package/dist/hooks/useRawCSS.js +0 -35
  141. package/dist/hooks/useRawCSS.js.map +0 -1
  142. package/dist/hooks/useStyles.d.ts +0 -45
  143. package/dist/hooks/useStyles.js +0 -237
  144. package/dist/hooks/useStyles.js.map +0 -1
  145. package/dist/injector/index.d.ts +0 -183
  146. package/dist/injector/index.js +0 -179
  147. package/dist/injector/index.js.map +0 -1
  148. package/dist/injector/injector.d.ts +0 -166
  149. package/dist/injector/injector.js +0 -464
  150. package/dist/injector/injector.js.map +0 -1
  151. package/dist/injector/sheet-manager.d.ts +0 -136
  152. package/dist/injector/sheet-manager.js +0 -733
  153. package/dist/injector/sheet-manager.js.map +0 -1
  154. package/dist/injector/types.d.ts +0 -204
  155. package/dist/keyframes/index.js +0 -206
  156. package/dist/keyframes/index.js.map +0 -1
  157. package/dist/parser/classify.js +0 -319
  158. package/dist/parser/classify.js.map +0 -1
  159. package/dist/parser/const.js +0 -49
  160. package/dist/parser/const.js.map +0 -1
  161. package/dist/parser/lru.js +0 -109
  162. package/dist/parser/lru.js.map +0 -1
  163. package/dist/parser/parser.d.ts +0 -25
  164. package/dist/parser/parser.js +0 -115
  165. package/dist/parser/parser.js.map +0 -1
  166. package/dist/parser/tokenizer.js +0 -69
  167. package/dist/parser/tokenizer.js.map +0 -1
  168. package/dist/parser/types.d.ts +0 -51
  169. package/dist/parser/types.js +0 -46
  170. package/dist/parser/types.js.map +0 -1
  171. package/dist/pipeline/conditions.d.ts +0 -134
  172. package/dist/pipeline/conditions.js +0 -406
  173. package/dist/pipeline/conditions.js.map +0 -1
  174. package/dist/pipeline/exclusive.js +0 -230
  175. package/dist/pipeline/exclusive.js.map +0 -1
  176. package/dist/pipeline/index.d.ts +0 -55
  177. package/dist/pipeline/index.js +0 -708
  178. package/dist/pipeline/index.js.map +0 -1
  179. package/dist/pipeline/materialize.js +0 -1103
  180. package/dist/pipeline/materialize.js.map +0 -1
  181. package/dist/pipeline/parseStateKey.d.ts +0 -15
  182. package/dist/pipeline/parseStateKey.js +0 -446
  183. package/dist/pipeline/parseStateKey.js.map +0 -1
  184. package/dist/pipeline/simplify.js +0 -515
  185. package/dist/pipeline/simplify.js.map +0 -1
  186. package/dist/pipeline/warnings.js +0 -18
  187. package/dist/pipeline/warnings.js.map +0 -1
  188. package/dist/plugins/index.d.ts +0 -2
  189. package/dist/plugins/okhsl-plugin.d.ts +0 -35
  190. package/dist/plugins/okhsl-plugin.js +0 -97
  191. package/dist/plugins/okhsl-plugin.js.map +0 -1
  192. package/dist/plugins/types.d.ts +0 -76
  193. package/dist/properties/index.js +0 -222
  194. package/dist/properties/index.js.map +0 -1
  195. package/dist/properties/property-type-resolver.d.ts +0 -24
  196. package/dist/properties/property-type-resolver.js +0 -90
  197. package/dist/properties/property-type-resolver.js.map +0 -1
  198. package/dist/ssr/astro.js.map +0 -1
  199. package/dist/ssr/async-storage.d.ts +0 -17
  200. package/dist/ssr/async-storage.js.map +0 -1
  201. package/dist/ssr/collect-auto-properties.js +0 -39
  202. package/dist/ssr/collect-auto-properties.js.map +0 -1
  203. package/dist/ssr/collector.d.ts +0 -102
  204. package/dist/ssr/collector.js +0 -226
  205. package/dist/ssr/collector.js.map +0 -1
  206. package/dist/ssr/context.d.ts +0 -8
  207. package/dist/ssr/context.js +0 -13
  208. package/dist/ssr/context.js.map +0 -1
  209. package/dist/ssr/format-global-rules.js.map +0 -1
  210. package/dist/ssr/format-keyframes.js +0 -69
  211. package/dist/ssr/format-keyframes.js.map +0 -1
  212. package/dist/ssr/format-property.js +0 -49
  213. package/dist/ssr/format-property.js.map +0 -1
  214. package/dist/ssr/format-rules.js.map +0 -1
  215. package/dist/ssr/hydrate.d.ts +0 -22
  216. package/dist/ssr/hydrate.js +0 -49
  217. package/dist/ssr/hydrate.js.map +0 -1
  218. package/dist/ssr/ssr-collector-ref.js +0 -12
  219. package/dist/ssr/ssr-collector-ref.js.map +0 -1
  220. package/dist/states/index.d.ts +0 -49
  221. package/dist/states/index.js +0 -170
  222. package/dist/states/index.js.map +0 -1
  223. package/dist/static/tastyStatic.d.ts +0 -46
  224. package/dist/static/tastyStatic.js +0 -30
  225. package/dist/static/tastyStatic.js.map +0 -1
  226. package/dist/static/types.d.ts +0 -49
  227. package/dist/static/types.js +0 -24
  228. package/dist/static/types.js.map +0 -1
  229. package/dist/styles/align.d.ts +0 -15
  230. package/dist/styles/align.js +0 -14
  231. package/dist/styles/align.js.map +0 -1
  232. package/dist/styles/border.d.ts +0 -25
  233. package/dist/styles/border.js +0 -113
  234. package/dist/styles/border.js.map +0 -1
  235. package/dist/styles/color.d.ts +0 -14
  236. package/dist/styles/color.js +0 -26
  237. package/dist/styles/color.js.map +0 -1
  238. package/dist/styles/createStyle.js +0 -79
  239. package/dist/styles/createStyle.js.map +0 -1
  240. package/dist/styles/dimension.js +0 -96
  241. package/dist/styles/dimension.js.map +0 -1
  242. package/dist/styles/display.d.ts +0 -37
  243. package/dist/styles/display.js +0 -66
  244. package/dist/styles/display.js.map +0 -1
  245. package/dist/styles/fade.d.ts +0 -15
  246. package/dist/styles/fade.js +0 -57
  247. package/dist/styles/fade.js.map +0 -1
  248. package/dist/styles/fill.d.ts +0 -42
  249. package/dist/styles/fill.js +0 -51
  250. package/dist/styles/fill.js.map +0 -1
  251. package/dist/styles/flow.d.ts +0 -16
  252. package/dist/styles/flow.js +0 -12
  253. package/dist/styles/flow.js.map +0 -1
  254. package/dist/styles/gap.d.ts +0 -31
  255. package/dist/styles/gap.js +0 -36
  256. package/dist/styles/gap.js.map +0 -1
  257. package/dist/styles/height.d.ts +0 -17
  258. package/dist/styles/height.js +0 -19
  259. package/dist/styles/height.js.map +0 -1
  260. package/dist/styles/index.d.ts +0 -1
  261. package/dist/styles/index.js +0 -8
  262. package/dist/styles/index.js.map +0 -1
  263. package/dist/styles/inset.d.ts +0 -52
  264. package/dist/styles/inset.js +0 -149
  265. package/dist/styles/inset.js.map +0 -1
  266. package/dist/styles/justify.d.ts +0 -15
  267. package/dist/styles/justify.js +0 -14
  268. package/dist/styles/justify.js.map +0 -1
  269. package/dist/styles/list.d.ts +0 -16
  270. package/dist/styles/list.js +0 -98
  271. package/dist/styles/list.js.map +0 -1
  272. package/dist/styles/margin.d.ts +0 -24
  273. package/dist/styles/margin.js +0 -103
  274. package/dist/styles/margin.js.map +0 -1
  275. package/dist/styles/outline.d.ts +0 -29
  276. package/dist/styles/outline.js +0 -64
  277. package/dist/styles/outline.js.map +0 -1
  278. package/dist/styles/padding.d.ts +0 -24
  279. package/dist/styles/padding.js +0 -103
  280. package/dist/styles/padding.js.map +0 -1
  281. package/dist/styles/predefined.d.ts +0 -71
  282. package/dist/styles/predefined.js +0 -237
  283. package/dist/styles/predefined.js.map +0 -1
  284. package/dist/styles/preset.d.ts +0 -52
  285. package/dist/styles/preset.js +0 -126
  286. package/dist/styles/preset.js.map +0 -1
  287. package/dist/styles/radius.d.ts +0 -12
  288. package/dist/styles/radius.js +0 -71
  289. package/dist/styles/radius.js.map +0 -1
  290. package/dist/styles/scrollbar.d.ts +0 -25
  291. package/dist/styles/scrollbar.js +0 -46
  292. package/dist/styles/scrollbar.js.map +0 -1
  293. package/dist/styles/shadow.d.ts +0 -14
  294. package/dist/styles/shadow.js +0 -23
  295. package/dist/styles/shadow.js.map +0 -1
  296. package/dist/styles/transition.d.ts +0 -14
  297. package/dist/styles/transition.js +0 -157
  298. package/dist/styles/transition.js.map +0 -1
  299. package/dist/styles/types.d.ts +0 -549
  300. package/dist/styles/width.d.ts +0 -17
  301. package/dist/styles/width.js +0 -19
  302. package/dist/styles/width.js.map +0 -1
  303. package/dist/tasty.d.ts +0 -119
  304. package/dist/tasty.js +0 -231
  305. package/dist/tasty.js.map +0 -1
  306. package/dist/types.d.ts +0 -184
  307. package/dist/utils/cache-wrapper.js +0 -21
  308. package/dist/utils/cache-wrapper.js.map +0 -1
  309. package/dist/utils/case-converter.js +0 -8
  310. package/dist/utils/case-converter.js.map +0 -1
  311. package/dist/utils/color-math.d.ts +0 -46
  312. package/dist/utils/color-math.js +0 -749
  313. package/dist/utils/color-math.js.map +0 -1
  314. package/dist/utils/color-space.d.ts +0 -5
  315. package/dist/utils/color-space.js +0 -228
  316. package/dist/utils/color-space.js.map +0 -1
  317. package/dist/utils/colors.d.ts +0 -5
  318. package/dist/utils/colors.js +0 -10
  319. package/dist/utils/colors.js.map +0 -1
  320. package/dist/utils/css-types.d.ts +0 -7
  321. package/dist/utils/dotize.d.ts +0 -26
  322. package/dist/utils/dotize.js +0 -122
  323. package/dist/utils/dotize.js.map +0 -1
  324. package/dist/utils/filter-base-props.d.ts +0 -15
  325. package/dist/utils/filter-base-props.js +0 -45
  326. package/dist/utils/filter-base-props.js.map +0 -1
  327. package/dist/utils/get-display-name.d.ts +0 -7
  328. package/dist/utils/get-display-name.js +0 -10
  329. package/dist/utils/get-display-name.js.map +0 -1
  330. package/dist/utils/has-keys.js +0 -13
  331. package/dist/utils/has-keys.js.map +0 -1
  332. package/dist/utils/is-dev-env.js +0 -19
  333. package/dist/utils/is-dev-env.js.map +0 -1
  334. package/dist/utils/is-valid-element-type.js +0 -15
  335. package/dist/utils/is-valid-element-type.js.map +0 -1
  336. package/dist/utils/merge-styles.js.map +0 -1
  337. package/dist/utils/mod-attrs.d.ts +0 -6
  338. package/dist/utils/mod-attrs.js +0 -20
  339. package/dist/utils/mod-attrs.js.map +0 -1
  340. package/dist/utils/process-tokens.d.ts +0 -21
  341. package/dist/utils/process-tokens.js +0 -90
  342. package/dist/utils/process-tokens.js.map +0 -1
  343. package/dist/utils/resolve-recipes.d.ts +0 -17
  344. package/dist/utils/resolve-recipes.js.map +0 -1
  345. package/dist/utils/selector-transform.js +0 -32
  346. package/dist/utils/selector-transform.js.map +0 -1
  347. package/dist/utils/string.js +0 -8
  348. package/dist/utils/string.js.map +0 -1
  349. package/dist/utils/styles.d.ts +0 -99
  350. package/dist/utils/styles.js +0 -220
  351. package/dist/utils/styles.js.map +0 -1
  352. package/dist/utils/typography.d.ts +0 -47
  353. package/dist/utils/typography.js +0 -51
  354. package/dist/utils/typography.js.map +0 -1
  355. package/dist/utils/warnings.d.ts +0 -16
  356. package/dist/utils/warnings.js +0 -16
  357. package/dist/utils/warnings.js.map +0 -1
  358. package/dist/zero/css-writer.d.ts +0 -45
  359. package/dist/zero/css-writer.js +0 -73
  360. package/dist/zero/css-writer.js.map +0 -1
  361. package/dist/zero/extractor.d.ts +0 -24
  362. package/dist/zero/extractor.js.map +0 -1
  363. package/docs/PIPELINE.md +0 -519
  364. package/docs/runtime.md +0 -485
  365. /package/dist/{chunks/cacheKey.d.ts → ssr/astro-client.d.ts} +0 -0
package/docs/ssr.md CHANGED
@@ -1,16 +1,22 @@
1
1
  # Server-Side Rendering (SSR)
2
2
 
3
- Tasty supports server-side rendering with zero-cost client hydration. This does **not** introduce a separate styling engine: SSR uses the same runtime `tasty()` pipeline you already use on the client, then adds server-side CSS collection and client-side cache hydration. Your existing `tasty()` components work unchanged, and SSR remains opt-in with no per-component modifications. For the broader docs map, see the [Docs Hub](README.md).
3
+ Tasty supports server-side rendering with zero-cost client hydration. This does **not** introduce a separate styling engine: `tasty()` uses the same rendering pipeline on the server and in the browser, while the SSR integrations add server-side CSS collection and client-side cache hydration. Your existing `tasty()` components work unchanged, and SSR remains opt-in with no per-component modifications. For the broader docs map, see the [Docs Hub](README.md).
4
+
5
+ ## Zero-runtime terminology
6
+
7
+ Zero-runtime delivery is an outcome, not an alias for `tastyStatic()`. When `tasty()` components render only on the server, their CSS is delivered with the HTML and no Tasty styling runtime is shipped to the browser. Astro's `tastyIntegration({ islands: false })` is the explicit integration for this setup. Server-only Next.js React Server Components follow the same architecture, although you should verify the generated output for your deployment.
8
+
9
+ `tastyStatic()` reaches the same client-side outcome by extracting CSS during the build instead of during React rendering. Use it when extraction must happen before rendering or when the consumer is not React; see [Build-Time Extraction](tasty-static.md).
4
10
 
5
11
  ---
6
12
 
7
13
  ## Requirements
8
14
 
9
- | Dependency | Version | Required for |
10
- |---|---|---|
11
- | `react` | >= 18 | All SSR entry points (matches the current peer dependency of `@tenphi/tasty`) |
12
- | `next` | >= 13 | Next.js integration (`@tenphi/tasty/ssr/next`) — App Router with `useServerInsertedHTML` |
13
- | Node.js | >= 20 | Generic / streaming SSR (`@tenphi/tasty/ssr`) — uses `node:async_hooks` for `AsyncLocalStorage` |
15
+ | Dependency | Version | Required for |
16
+ | ---------- | ------- | ----------------------------------------------------------------------------------------------- |
17
+ | `react` | >= 18 | All SSR entry points (matches the current peer dependency of `@tenphi/tasty`) |
18
+ | `next` | >= 13 | Next.js integration (`@tenphi/tasty/ssr/next`) — App Router with `useServerInsertedHTML` |
19
+ | Node.js | >= 20 | Generic / streaming SSR (`@tenphi/tasty/ssr`) — uses `node:async_hooks` for `AsyncLocalStorage` |
14
20
 
15
21
  The Astro integration (`@tenphi/tasty/ssr/astro`) has no additional dependencies beyond `react`.
16
22
 
@@ -18,18 +24,18 @@ The Astro integration (`@tenphi/tasty/ssr/astro`) has no additional dependencies
18
24
 
19
25
  ## How It Works
20
26
 
21
- When the environment can execute runtime React code during server rendering, the same `tasty()` and `useStyles()` calls can run there too. In Next.js, generic React SSR, and Astro islands, Tasty simply changes where that runtime-generated CSS goes: `useStyles()` detects a `ServerStyleCollector` and collects CSS into it instead of trying to access the DOM. The collector accumulates all styles, serializes them as `<style>` tags and a cache state script in the HTML. On the client, `hydrateTastyCache()` pre-populates the injector cache so that `useStyles()` skips the rendering pipeline entirely during hydration.
27
+ `tasty()` components are hook-free and use `computeStyles()` internally — a synchronous, framework-agnostic function. On the server, `computeStyles()` discovers a `ServerStyleCollector` via a registered getter (module-level for Next.js, `globalThis` for Astro/generic frameworks using `AsyncLocalStorage`) and collects CSS into it instead of trying to access the DOM. On the client, CSS is injected synchronously into the DOM during render; the injector's content-based cache makes this idempotent. The collector accumulates all styles and serializes them as `<style>` tags plus a class-list script in the HTML. On the client, `hydrateTastyClasses()` pre-populates the injector's rules map with the rendered class names so that `computeStyles()` skips the rendering pipeline entirely during hydration.
22
28
 
23
29
  ```
24
30
  Server Client
25
31
  ────── ──────
26
- tasty() renders hydrateTastyCache() pre-populates cache
27
- └─ useStyles() └─ cacheKey → className map ready
32
+ tasty() renders hydrateTastyClasses() reads window.__TASTY__
33
+ └─ computeStyles() └─ marks rendered class names as already-in-DOM
28
34
  └─ collector.collect()
29
35
  tasty() renders
30
- After render: └─ useStyles()
31
- <style data-tasty-ssr> └─ cache hit → skip pipeline
32
- <script data-tasty-cache> └─ no CSS re-injection
36
+ After render: └─ computeStyles()
37
+ <style data-tasty-ssr> └─ class name known → skip pipeline
38
+ <script> (pushes to __TASTY__) └─ no CSS re-injection
33
39
  ```
34
40
 
35
41
  ---
@@ -80,17 +86,109 @@ export default function RootLayout({
80
86
 
81
87
  That's it. All `tasty()` components inside the tree automatically get SSR support. No per-component changes needed.
82
88
 
89
+ ### Optional shared globals stylesheet
90
+
91
+ By default, configured global CSS is included in the streamed Tasty style tag
92
+ for every route. `withTastyNext()` can move that stable CSS into one
93
+ content-hashed stylesheet shared by all routes while leaving component and hook
94
+ styles in the normal streaming path.
95
+
96
+ Keep the Tasty config in a side-effect-free module:
97
+
98
+ ```ts
99
+ // app/tasty.config.ts
100
+ import type { TastyConfig } from '@tenphi/tasty';
101
+
102
+ const config: TastyConfig = {
103
+ tokens: { $gap: '8px', '#brand': 'rebeccapurple' },
104
+ globalStyles: { body: { margin: '0', color: '#brand' } },
105
+ fontFaces: {
106
+ Brand: { src: 'url("/fonts/brand.woff2") format("woff2")' },
107
+ },
108
+ };
109
+
110
+ export default config;
111
+ ```
112
+
113
+ Use it from the Next config:
114
+
115
+ ```ts
116
+ // next.config.ts
117
+ import { withTastyNext } from '@tenphi/tasty/ssr/next-config';
118
+ import config from './app/tasty.config';
119
+
120
+ export default withTastyNext({
121
+ config,
122
+ })({
123
+ // your Next.js config
124
+ });
125
+ ```
126
+
127
+ The runtime must receive the same config. Import and configure it before the
128
+ registry renders:
129
+
130
+ ```tsx
131
+ // app/tasty-registry.tsx
132
+ 'use client';
133
+
134
+ import { configure } from '@tenphi/tasty';
135
+ import { TastyRegistry } from '@tenphi/tasty/ssr/next';
136
+ import config from './tasty.config';
137
+
138
+ configure(config);
139
+
140
+ export default function TastyStyleRegistry({ children }) {
141
+ return <TastyRegistry>{children}</TastyRegistry>;
142
+ }
143
+ ```
144
+
145
+ The generated file contains eager configuration artifacts: built-in and custom
146
+ `@property` rules, tokens and presets, `@font-face`, `@counter-style`, native
147
+ CSS `@function` definitions, and `globalStyles`. Route-dependent component
148
+ rules and calls to `useGlobalStyles`, `useRawCSS`, `useKeyframes`,
149
+ `useProperty`, `useFontFace`, `useCounterStyle`, and `useFunction` remain
150
+ route-specific. Configured keyframes also remain lazy and are streamed only
151
+ when a route references them.
152
+
153
+ The default output directory is `public/_tasty`. The wrapper adds the generated
154
+ URL to `TastyRegistry`, respects `basePath`, preserves existing `env` and
155
+ `headers` config, and serves the content-hashed file with an immutable one-year
156
+ cache header on Next server deployments. Static exports keep the content-hashed
157
+ URL and leave cache headers to the hosting provider. Older hashes are not
158
+ deleted automatically, so rolling deployments cannot break pages from the
159
+ previous build; clean the generated directory as part of a clean deployment if
160
+ needed. Page-relative CSS resources such as
161
+ `url(../fonts/brand.woff2)` are rejected because moving them would change their
162
+ meaning; use root-relative, absolute, or data URLs.
163
+
164
+ `withTastyNext()` options:
165
+
166
+ | Option | Type | Default | Description |
167
+ | ------------ | ------------- | ------------------------- | ---------------------------------------------------------------------- |
168
+ | `config` | `TastyConfig` | — | Config object; takes precedence over `configFile` |
169
+ | `configFile` | `string` | — | Project-relative config module path; requires the optional `jiti` peer |
170
+ | `rootDir` | `string` | current directory | Next app root when the build runs from a monorepo root |
171
+ | `outputDir` | `string` | `public/_tasty` | Filesystem output directory |
172
+ | `publicPath` | `string` | inferred from `outputDir` | Root-relative URL; required when output is outside `public` |
173
+ | `enabled` | `boolean` | `true` | Disable generation without changing wrapper composition |
174
+
83
175
  ### How it works
84
176
 
85
- - `TastyRegistry` is a `'use client'` component, but Next.js still server-renders it on initial page load.
86
- - During SSR, `useStyles()` finds the collector via React context and pushes CSS rules to it.
87
- - `TastyRegistry` uses `useServerInsertedHTML` to flush collected CSS into the HTML stream as `<style data-tasty-ssr>` tags. This is fully streaming-compatible -- styles are injected alongside each Suspense boundary as it resolves.
88
- - A companion `<script>` tag transfers the `cacheKey → className` mapping to the client.
89
- - When the module loads on the client, `hydrateTastyCache()` runs automatically and pre-populates the injector cache. During hydration, `useStyles()` hits the cache and skips the entire pipeline.
177
+ - `TastyRegistry` is a `'use client'` component, but Next.js still server-renders it on initial page load. The `'use client'` boundary is required solely to access `useServerInsertedHTML` — **not** because `tasty()` components need the client.
178
+ - During SSR, `TastyRegistry` creates a `ServerStyleCollector` and registers it via a module-level getter (not `globalThis` — this avoids leaking between Next.js's separate RSC and SSR module graphs). It also wraps children in a React context provider so that hooks inside the SSR tree can discover the collector. All style functions — `tasty()` components, `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, and `useCounterStyle()` — discover the collector through the module-level getter or context provider.
179
+ - `TastyRegistry` uses `useServerInsertedHTML` to flush collected CSS into the HTML stream as `<style data-tasty-ssr>` tags. This is fully streaming-compatible — styles are injected alongside each Suspense boundary as it resolves.
180
+ - A companion inline `<script>` tag pushes the rendered class names into `window.__TASTY__` for each flush. This streaming-friendly approach accumulates the class list incrementally as Suspense boundaries resolve.
181
+ - When the `@tenphi/tasty/ssr/next` module loads on the client, `hydrateTastyClasses()` runs automatically from `window.__TASTY__` and pre-populates the injector's rules map. During hydration, `computeStyles()` sees the class name is already registered and skips the entire pipeline.
182
+
183
+ ### Using Tasty in Server Components
184
+
185
+ All Tasty style functions are hook-free and do not require `'use client'`. They can be used directly in React Server Components:
90
186
 
91
- ### Using tasty() in Server Components
187
+ - `tasty()` components — dynamic `styleProps` like `<Grid flow="column">` work normally
188
+ - `useStyles()`, `useGlobalStyles()`, `useRawCSS()` — inject styles by class or selector
189
+ - `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()` — inject ancillary CSS rules
92
190
 
93
- `tasty()` components use React hooks internally, so they require `'use client'`. However, this does **not** prevent them from being used in Server Component pages. In Next.js, `'use client'` components are still server-rendered on initial load. Dynamic `styleProps` like `<Grid flow="column">` work normally when a `tasty()` component is imported into a Server Component page.
191
+ During SSR, all functions discover the collector via the same global getter registered by `TastyRegistry` — no React context or client boundary needed. In RSC mode without a collector (e.g., Astro zero-setup), CSS is accumulated in a per-request cache and flushed into an inline `<style>` tag by the next `tasty()` component in the tree. Ensure at least one `tasty()` component is present in every RSC render tree — standalone style functions alone cannot emit their CSS without a `tasty()` component to trigger the flush.
94
192
 
95
193
  ### Options
96
194
 
@@ -116,18 +214,19 @@ The nonce is automatically applied to all `<style>` and `<script>` tags injected
116
214
 
117
215
  ## Astro
118
216
 
119
- ### 1. Add the middleware
217
+ Tasty offers several levels of Astro integration. Choose the one that matches your needs:
120
218
 
121
- Create or update your Astro middleware:
122
-
123
- ```ts
124
- // src/middleware.ts
125
- import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
219
+ | Setup | Config needed | Deduplication | Hooks work | Client JS |
220
+ | ---------------------------------------------------------------- | ------------- | ------------------------- | ---------------------- | -------------- |
221
+ | Zero setup | None | Per render tree | Yes (within each tree) | None |
222
+ | `tastyIntegration({ islands: false })` | One line | Cross-tree | Yes | None |
223
+ | `tastyIntegration()` | One line | Cross-tree | Yes | Auto-hydration |
224
+ | `tastyIntegration({ css: { mode: 'extract' } })` | One line | Cross-tree and cross-page | Yes | Auto-hydration |
225
+ | `tastyIntegration({ islands: false, css: { mode: 'extract' } })` | One line | Cross-tree and cross-page | Yes | None |
126
226
 
127
- export const onRequest = tastyMiddleware();
128
- ```
227
+ ### Zero setup (static pages)
129
228
 
130
- ### 2. Use tasty() components as normal
229
+ `tasty()` components work in Astro with **no configuration**. Each component emits its own inline `<style>` tag during server rendering via the RSC inline path. Just import and use:
131
230
 
132
231
  ```tsx
133
232
  // src/components/Card.tsx
@@ -153,82 +252,203 @@ import Card from '../components/Card.tsx';
153
252
 
154
253
  <html>
155
254
  <body>
156
- <Card>Static card — styles collected by middleware</Card>
157
- <Card client:load>Island card — styles hydrated on client</Card>
255
+ <Card>Styled with zero setup</Card>
158
256
  </body>
159
257
  </html>
160
258
  ```
161
259
 
162
- ### How it works
260
+ **Trade-offs**: Styles are deduplicated within each React render tree, but Astro renders separate component trees independently, so shared CSS (tokens, `@property` rules) may appear more than once. All style functions (`useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`) work in zero-setup mode — their CSS is accumulated in the RSC cache and flushed by the next `tasty()` component in the tree.
163
261
 
164
- Astro's `@astrojs/react` renderer calls `renderToString()` for each React component without wrapping the tree in a provider. The middleware uses `AsyncLocalStorage` to make the collector available to all `useStyles()` calls within the request.
262
+ Best for quick prototyping, small static sites, or trying Tasty out in Astro.
165
263
 
166
- - **Static components** (no `client:*`): Styles are collected during `renderToString` and injected into `</head>`. No JavaScript is shipped for these components.
167
- - **Islands** (`client:load`, `client:visible`, etc.): Styles are collected during SSR the same way. On the client, importing `@tenphi/tasty/ssr/astro` auto-hydrates the cache from `<script data-tasty-cache>`. The island's `useStyles()` calls hit the cache during hydration.
264
+ ### Astro Integration (recommended)
168
265
 
169
- ### Client-side hydration for islands
266
+ For production use, add `tastyIntegration()` to your Astro config. This registers middleware automatically and, by default, injects client-side hydration for islands.
170
267
 
171
- The `@tenphi/tasty/ssr/astro` module auto-hydrates when imported on the client. To ensure the cache is warm before any island renders, import it in a shared entry point or in each island component:
268
+ #### With islands (default)
172
269
 
173
- ```tsx
174
- // src/components/MyIsland.tsx
175
- import '@tenphi/tasty/ssr/astro'; // auto-hydrates cache on import
176
- import { tasty } from '@tenphi/tasty';
270
+ ```ts
271
+ // astro.config.mjs
272
+ import { defineConfig } from 'astro/config';
273
+ import react from '@astrojs/react';
274
+ import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
177
275
 
178
- const MyIsland = tasty({
179
- styles: { padding: '2x', fill: '#blue' },
276
+ export default defineConfig({
277
+ integrations: [react(), tastyIntegration()],
180
278
  });
279
+ ```
181
280
 
182
- export default MyIsland;
281
+ This gives you:
282
+
283
+ - A `ServerStyleCollector` per request via `AsyncLocalStorage`, deduplicating CSS across all React trees on the page
284
+ - A single consolidated `<style data-tasty-ssr>` injected into `</head>`
285
+ - A `<script>` tag pushing the rendered class names into `window.__TASTY__` for client hydration
286
+ - Auto-injected client hydration script (via `injectScript('before-hydration')`) so islands skip the style pipeline during hydration -- no need to import anything manually in each island component
287
+
288
+ All style functions (`useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`) work on the server.
289
+
290
+ ```astro
291
+ ---
292
+ // src/pages/index.astro
293
+ import Card from '../components/Card.tsx';
294
+ import Interactive from '../components/Interactive.tsx';
295
+ ---
296
+
297
+ <html>
298
+ <body>
299
+ <Card>Static -- styles in <style data-tasty-ssr></Card>
300
+ <Interactive client:load>Island -- cache hydrated automatically</Interactive>
301
+ </body>
302
+ </html>
183
303
  ```
184
304
 
185
- ### Options
305
+ #### Static only (no client JS)
306
+
307
+ If your site has no `client:*` islands, skip the hydration script and cache transfer:
308
+
309
+ ```ts
310
+ // astro.config.mjs
311
+ import { defineConfig } from 'astro/config';
312
+ import react from '@astrojs/react';
313
+ import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
314
+
315
+ export default defineConfig({
316
+ integrations: [react(), tastyIntegration({ islands: false })],
317
+ });
318
+ ```
319
+
320
+ This gives the same middleware deduplication and hook support, but ships zero client-side JavaScript. No class-list `<script>` is emitted.
321
+
322
+ #### Build-wide CSS extraction
323
+
324
+ Static Astro builds can move Tasty CSS into content-hashed, browser-cacheable
325
+ shared and page assets:
326
+
327
+ ```ts
328
+ export default defineConfig({
329
+ integrations: [
330
+ react(),
331
+ tastyIntegration({
332
+ islands: false,
333
+ css: {
334
+ mode: 'extract',
335
+ },
336
+ }),
337
+ ],
338
+ });
339
+ ```
340
+
341
+ `css.mode` defaults to `'inline'`, so existing projects keep their current
342
+ output. Extraction requires Astro 5 or newer and only applies to prerendered
343
+ production pages. Development, preview-time SSR, and on-demand routes continue
344
+ to receive the normal inline `<style data-tasty-ssr>` output.
345
+
346
+ Extraction writes every artifact emitted by all styled pages to a shared
347
+ stylesheet. Each page's strict set difference is written to a separate page
348
+ stylesheet. The shared link comes first and the page link follows, so shared
349
+ styles form the base cascade and page-only styles can override them. A fully
350
+ shared page omits the empty page stylesheet. If generated pages have no common
351
+ artifacts, each page receives only its page stylesheet.
352
+
353
+ The shared-base/page-override order is the extraction-mode cascade contract.
354
+ It does not preserve an inline artifact order where a page-only rule originally
355
+ appeared before a shared rule. Use shared styles for defaults and page-only
356
+ styles for overrides.
357
+
358
+ Extracted CSS preserves resource URLs verbatim. Relative URLs in an inline
359
+ style resolve from the page, but in an extracted stylesheet they resolve from
360
+ the asset directory. Use absolute URLs or data URLs when extraction is enabled.
361
+ Root-relative URLs such as `url(/fonts/brand.woff2)` are also safe while the
362
+ stylesheet stays on the page's origin. The build fails with a clear error if an
363
+ artifact contains a page-relative or fragment-only URL, including URL strings
364
+ in `image-set()`, `image()`, `src()`, and `@import`.
365
+
366
+ Assets are written under Astro's configured `build.assets` directory (for
367
+ example, `/_astro/tasty.shared.a1b2c3.css` and
368
+ `/_astro/tasty.page.d4e5f6.css`). Links include the configured Astro `base`, so
369
+ nested routes do not need relative-path handling. Content hashes and output are
370
+ deterministic for identical builds. If `build.assetsPrefix` is configured,
371
+ Tasty uses its CSS-specific prefix (or `fallback`) just like Astro-generated
372
+ stylesheets. When that prefix points to a different origin, root-relative
373
+ resources would resolve against the asset origin rather than the page's origin,
374
+ so the build rejects them as well. Tasty compares the prefix with Astro's `site`
375
+ when it is configured; without `site`, an absolute or protocol-relative prefix
376
+ is treated conservatively as cross-origin. Use a fully absolute resource URL in
377
+ that configuration.
378
+
379
+ ### Manual middleware (advanced)
380
+
381
+ If you need to compose Tasty's middleware with other middleware (e.g., via `sequence()`), use `tastyMiddleware()` directly:
186
382
 
187
383
  ```ts
188
- // Skip cache state transfer
384
+ // src/middleware.ts
385
+ import { sequence } from 'astro:middleware';
386
+ import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
387
+
388
+ export const onRequest = sequence(tastyMiddleware(), myOtherMiddleware);
389
+ ```
390
+
391
+ For island hydration with manual middleware, import the client module in a shared entry point or in each island:
392
+
393
+ ```tsx
394
+ import '@tenphi/tasty/ssr/astro-client';
395
+ ```
396
+
397
+ #### Options
398
+
399
+ ```ts
400
+ // Skip cache state transfer (static-only, no islands)
189
401
  export const onRequest = tastyMiddleware({ transferCache: false });
190
402
  ```
191
403
 
404
+ ### How it works
405
+
406
+ Astro's `@astrojs/react` renderer calls `renderToString()` for each React component without wrapping the tree in a provider. The middleware creates a `ServerStyleCollector` and binds it via `AsyncLocalStorage`. All `computeStyles()` calls within the request discover this collector automatically.
407
+
408
+ - **Static components** (no `client:*`): Styles are collected during `renderToString` and injected into `</head>` as a single `<style>` tag. No JavaScript is shipped.
409
+ - **Islands** (`client:load`, `client:visible`, etc.): Styles are collected during SSR the same way. On the client, the hydration script (auto-injected by `tastyIntegration()` or manually via `@tenphi/tasty/ssr/astro-client`) reads the class list from `window.__TASTY__` and pre-populates the injector's rules map. The island's `computeStyles()` calls see the class names as already registered and skip the pipeline during hydration.
410
+ - The middleware reads the full response body, then injects the collected CSS into `</head>` before sending the final HTML.
411
+ - In extraction mode, prerendered responses also carry temporary structured
412
+ artifact metadata. `astro:build:done` uses those collector-provided
413
+ boundaries to write shared and page assets and rewrite generated HTML, then
414
+ removes the metadata. Artifact boundaries are never inferred by splitting or
415
+ reparsing the generated CSS.
416
+
192
417
  ### CSP nonce
193
418
 
194
- Same as Next.js -- call `configure({ nonce: '...' })` before any rendering happens. The middleware reads the nonce and applies it to injected tags.
419
+ Call `configure({ nonce: '...' })` before any rendering happens. The middleware reads the nonce and applies it to injected `<style>` and `<script>` tags. In extraction mode, the external stylesheet links retain the nonce.
195
420
 
196
421
  ---
197
422
 
198
423
  ## Generic Framework Integration
199
424
 
200
- Any React-based framework can integrate using the core SSR API:
425
+ Any React-based framework can integrate using `runWithCollector`, which binds a `ServerStyleCollector` to the current async context via `AsyncLocalStorage`. All style function calls within the render automatically discover the collector.
201
426
 
202
427
  ```tsx
203
428
  import {
204
429
  ServerStyleCollector,
205
- TastySSRContext,
206
- hydrateTastyCache,
430
+ createServerStyleCollector,
431
+ runWithCollector,
432
+ hydrateTastyClasses,
207
433
  } from '@tenphi/tasty/ssr';
208
434
  import { renderToString } from 'react-dom/server';
209
435
  import { hydrateRoot } from 'react-dom/client';
210
436
 
211
437
  // ── Server ──────────────────────────────────────────────
212
438
 
213
- const collector = new ServerStyleCollector();
439
+ const collector = createServerStyleCollector();
214
440
 
215
- const html = renderToString(
216
- <TastySSRContext.Provider value={collector}>
217
- <App />
218
- </TastySSRContext.Provider>
219
- );
441
+ const html = await runWithCollector(collector, () => renderToString(<App />));
220
442
 
221
443
  const css = collector.getCSS();
222
- const cacheState = collector.getCacheState();
444
+ const classNames = collector.getRenderedClassNames();
223
445
 
224
446
  // Embed in your HTML template:
225
447
  const fullHtml = `
226
448
  <html>
227
449
  <head>
228
450
  <style data-tasty-ssr>${css}</style>
229
- <script data-tasty-cache type="application/json">
230
- ${JSON.stringify(cacheState)}
231
- </script>
451
+ <script>(window.__TASTY__=window.__TASTY__||[]).push(${JSON.stringify(classNames)})</script>
232
452
  </head>
233
453
  <body>
234
454
  <div id="root">${html}</div>
@@ -239,7 +459,7 @@ const fullHtml = `
239
459
  // ── Client ──────────────────────────────────────────────
240
460
 
241
461
  // Before hydration:
242
- hydrateTastyCache(); // reads from <script data-tasty-cache>
462
+ hydrateTastyClasses(); // reads from window.__TASTY__
243
463
 
244
464
  hydrateRoot(document.getElementById('root'), <App />);
245
465
  ```
@@ -249,13 +469,10 @@ hydrateRoot(document.getElementById('root'), <App />);
249
469
  For streaming with `renderToPipeableStream`, use `flushCSS()` instead of `getCSS()`:
250
470
 
251
471
  ```tsx
252
- const collector = new ServerStyleCollector();
472
+ const collector = createServerStyleCollector();
253
473
 
254
- const stream = renderToPipeableStream(
255
- <TastySSRContext.Provider value={collector}>
256
- <App />
257
- </TastySSRContext.Provider>,
258
- {
474
+ const stream = await runWithCollector(collector, () =>
475
+ renderToPipeableStream(<App />, {
259
476
  onShellReady() {
260
477
  // Flush styles collected so far
261
478
  const css = collector.flushCSS();
@@ -263,94 +480,97 @@ const stream = renderToPipeableStream(
263
480
  stream.pipe(res);
264
481
  },
265
482
  onAllReady() {
266
- // Flush any remaining styles + cache state
483
+ // Flush any remaining styles + class list
267
484
  const css = collector.flushCSS();
268
485
  if (css) res.write(`<style data-tasty-ssr>${css}</style>`);
269
486
 
270
- const state = collector.getCacheState();
271
- res.write(`<script data-tasty-cache type="application/json">${JSON.stringify(state)}</script>`);
487
+ const classNames = collector.getRenderedClassNames();
488
+ res.write(
489
+ `<script>(window.__TASTY__=window.__TASTY__||[]).push(${JSON.stringify(classNames)})</script>`,
490
+ );
272
491
  },
273
- }
492
+ }),
274
493
  );
275
494
  ```
276
495
 
277
- ### AsyncLocalStorage (no React context)
278
-
279
- If your framework doesn't support wrapping the React tree with a provider, use `runWithCollector`:
280
-
281
- ```tsx
282
- import {
283
- ServerStyleCollector,
284
- runWithCollector,
285
- hydrateTastyCache,
286
- } from '@tenphi/tasty/ssr';
287
-
288
- const collector = new ServerStyleCollector();
289
-
290
- const html = await runWithCollector(collector, () =>
291
- renderToString(<App />)
292
- );
293
-
294
- const css = collector.getCSS();
295
- // ... inject into HTML as above
296
- ```
297
-
298
496
  ---
299
497
 
300
498
  ## API Reference
301
499
 
302
500
  ### Entry points
303
501
 
304
- | Import path | Description |
305
- |---|---|
306
- | `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `TastySSRContext`, `runWithCollector`, `hydrateTastyCache` |
307
- | `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
308
- | `@tenphi/tasty/ssr/astro` | Astro: `tastyMiddleware`, auto-hydration on import |
502
+ | Import path | Description |
503
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
504
+ | `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `createServerStyleCollector`, `runWithCollector`, `hydrateTastyClasses` |
505
+ | `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
506
+ | `@tenphi/tasty/ssr/next-config` | Next.js config wrapper: shared, content-hashed global stylesheet |
507
+ | `@tenphi/tasty/ssr/astro` | Astro: `tastyIntegration`, `tastyMiddleware` |
508
+ | `@tenphi/tasty/ssr/astro-client` | Astro: client-side cache hydration (auto-injected by integration, or import manually) |
509
+ | `@tenphi/tasty/ssr/astro-middleware`<br>`@tenphi/tasty/ssr/astro-middleware-static`<br>`@tenphi/tasty/ssr/astro-middleware-extract`<br>`@tenphi/tasty/ssr/astro-middleware-extract-static` | Astro: the middleware entrypoints `tastyIntegration()` registers via `addMiddleware()`. Exported so Astro can resolve them by specifier; you should not import them. For manual setups use `tastyMiddleware()`. |
309
510
 
310
511
  ### `ServerStyleCollector`
311
512
 
312
513
  Server-safe style collector. One instance per request.
313
514
 
314
- | Method | Description |
315
- |---|---|
316
- | `allocateClassName(cacheKey)` | Allocate a sequential class name (`t0`, `t1`, ...) for a cache key. Returns `{ className, isNewAllocation }`. |
317
- | `collectChunk(cacheKey, className, rules)` | Record CSS rules for a chunk. Deduplicated by `cacheKey`. |
318
- | `collectKeyframes(name, css)` | Record a `@keyframes` rule. Deduplicated by name. |
319
- | `collectProperty(name, css)` | Record a `@property` rule. Deduplicated by name. |
320
- | `collectFontFace(key, css)` | Record a `@font-face` rule. Deduplicated by content hash. |
321
- | `collectCounterStyle(name, css)` | Record a `@counter-style` rule. Deduplicated by name. |
322
- | `getCSS()` | Get all collected CSS as a single string. For non-streaming SSR. |
323
- | `flushCSS()` | Get only CSS collected since the last flush. For streaming SSR. |
324
- | `getCacheState()` | Serialize `{ entries: Record<cacheKey, className>, classCounter }` for client hydration. |
325
-
326
- ### `TastySSRContext`
327
-
328
- React context (`createContext<ServerStyleCollector | null>(null)`). Used by `useStyles()` to find the collector during SSR.
515
+ Constructor: `new ServerStyleCollector(namePrefix?)`, or use the `createServerStyleCollector(namePrefix?)` factory. The optional `namePrefix` overrides the value from `configure({ namePrefix })`; in normal usage you pass nothing and let the global config drive it. See [Configuration: Name prefix](configuration.md#name-prefix).
516
+
517
+ | Method | Description |
518
+ | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
519
+ | `allocateClassName(cacheKey)` | Allocate a deterministic, content-hashed class name for a cache key (e.g. `t1a2b3` with the default prefix). The same `cacheKey` always produces the same class name on server and client when both share the same `namePrefix`. Returns `{ className, isNewAllocation }`. |
520
+ | `collectChunk(cacheKey, className, rules)` | Record CSS rules for a chunk. Deduplicated by `cacheKey`. |
521
+ | `collectKeyframes(name, css)` | Record a `@keyframes` rule. Deduplicated by name. |
522
+ | `allocateKeyframeName(providedName?)` | Allocate a keyframe name. Returns `providedName` if given, otherwise generates one using `${namePrefix}k${counter}` (e.g. `tk0`, `tk1`, ...). |
523
+ | `collectProperty(name, css)` | Record a `@property` rule. Deduplicated by name. |
524
+ | `collectFontFace(key, css)` | Record a `@font-face` rule. Deduplicated by content hash. |
525
+ | `collectCounterStyle(name, css)` | Record a `@counter-style` rule. Deduplicated by name. |
526
+ | `allocateCounterStyleName(providedName?)` | Allocate a counter-style name. Returns `providedName` if given, otherwise generates one using `${namePrefix}c${counter}` (e.g. `tc0`, `tc1`, ...). |
527
+ | `collectGlobalStyles(key, css)` | Record global styles (from `useGlobalStyles`). Deduplicated by key. |
528
+ | `collectRawCSS(key, css)` | Record raw CSS text (from `useRawCSS`). Deduplicated by key. |
529
+ | `collectInternals()` | Collect eager configured globals: `@property`, `:root` tokens and presets, `@font-face`, `@counter-style`, `@function`, and `globalStyles`. Called automatically on first chunk collection; idempotent. |
530
+ | `getCSS()` | Get all collected CSS as a single string. For non-streaming SSR. |
531
+ | `flushCSS()` | Get only CSS collected since the last flush. For streaming SSR. |
532
+ | `getRenderedClassNames()` | Get the list of class names rendered so far. Serialized to `window.__TASTY__` for client hydration via `hydrateTastyClasses()`. |
329
533
 
330
534
  ### `TastyRegistry`
331
535
 
332
536
  Next.js App Router component. Props:
333
537
 
334
- | Prop | Type | Default | Description |
335
- |---|---|---|---|
336
- | `children` | `ReactNode` | required | Application tree |
337
- | `transferCache` | `boolean` | `true` | Embed cache state script for zero-cost hydration |
538
+ | Prop | Type | Default | Description |
539
+ | ------------------ | ----------------- | --------- | ------------------------------------------------------------------------- |
540
+ | `children` | `ReactNode` | required | Application tree |
541
+ | `transferCache` | `boolean` | `true` | Embed cache state script for zero-cost hydration |
542
+ | `sharedStylesheet` | `string \| false` | generated | Override the shared URL, or disable generated shared CSS for the registry |
543
+
544
+ ### `withTastyNext(options)`
545
+
546
+ Next.js configuration wrapper exported from
547
+ `@tenphi/tasty/ssr/next-config`. It generates a shared stylesheet from eager
548
+ Tasty configuration artifacts and wires its URL and immutable cache header
549
+ into the Next config. Route-specific CSS continues through `TastyRegistry`.
550
+
551
+ ### `tastyIntegration(options?)`
552
+
553
+ Astro integration factory. Registers middleware and optionally injects client hydration.
554
+
555
+ | Option | Type | Default | Description |
556
+ | --------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
557
+ | `islands` | `boolean` | `true` | When `true`, injects client hydration script and enables `transferCache`. When `false`, no client JS is shipped. |
338
558
 
339
559
  ### `tastyMiddleware(options?)`
340
560
 
341
- Astro middleware factory. Options:
561
+ Astro middleware factory. Use for manual middleware composition.
342
562
 
343
- | Option | Type | Default | Description |
344
- |---|---|---|---|
345
- | `transferCache` | `boolean` | `true` | Embed cache state script for island hydration |
563
+ | Option | Type | Default | Description |
564
+ | --------------- | --------- | ------- | --------------------------------------------- |
565
+ | `transferCache` | `boolean` | `true` | Embed cache state script for island hydration |
346
566
 
347
- ### `hydrateTastyCache(state?)`
567
+ ### `hydrateTastyClasses(classes?)`
348
568
 
349
- Pre-populate the client injector cache. When called without arguments, reads from `window.__TASTY_SSR_CACHE__` (streaming) or `<script data-tasty-cache>` (non-streaming).
569
+ Pre-populate the client injector's rules map with class names rendered on the server, marking them as already present in the DOM so `computeStyles()` skips re-injection during hydration. When called without arguments, reads the class list from `window.__TASTY__` (populated by the streaming `<script>` tags emitted during SSR).
350
570
 
351
571
  ### `runWithCollector(collector, fn)`
352
572
 
353
- Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All `useStyles()` calls within `fn` (and async continuations) will find this collector.
573
+ Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All style function calls within `fn` (and async continuations) — including `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, and `useCounterStyle()` — will find this collector.
354
574
 
355
575
  ---
356
576
 
@@ -358,25 +578,31 @@ Run a function with a `ServerStyleCollector` bound to the current async context
358
578
 
359
579
  ### Styles flash on page load (FOUC)
360
580
 
361
- The `TastyRegistry` or `tastyMiddleware` is missing. Ensure your layout wraps the app with `TastyRegistry` (Next.js) or the middleware is registered (Astro).
581
+ The `TastyRegistry` or `tastyIntegration` is missing. Ensure your layout wraps the app with `TastyRegistry` (Next.js) or that `tastyIntegration()` is in your Astro config (or `tastyMiddleware()` is registered manually).
362
582
 
363
583
  ### Hydration mismatch warnings
364
584
 
365
- Class names are deterministic for the same render order. If you see mismatches, ensure `hydrateTastyCache()` runs before React hydration. For Next.js, this is automatic. For Astro, import `@tenphi/tasty/ssr/astro` in your island components. For custom setups, call `hydrateTastyCache()` before `hydrateRoot()`.
585
+ Class names are deterministic for the same render order. If you see mismatches, ensure `hydrateTastyClasses()` runs before React hydration. For Next.js, this is automatic. For Astro with `tastyIntegration()`, this is also automatic. For manual Astro middleware setups, import `@tenphi/tasty/ssr/astro-client` in your island components. For custom setups, call `hydrateTastyClasses()` before `hydrateRoot()`.
586
+
587
+ Class names are also derived from the _resolved_ styles, so the server and the client must configure Tasty identically. Anything that changes what a component's styles resolve to will produce a mismatch if it is registered on only one side — `namePrefix`, `recipes`, `handlers`, and the `propHandlers` / `baseStyleProps` extension points described in [Plugins](plugins.md). Call the same `configure()` on both; global CSS is deduplicated automatically, so no `typeof window` guard is needed.
366
588
 
367
589
  ### Styles duplicated after hydration
368
590
 
369
- This is expected and harmless. SSR `<style data-tasty-ssr>` tags remain in the DOM. The client injector creates separate `<style>` elements for any new styles. SSR styles are never modified or removed by the client. If this is a concern for very large apps, call `cleanupSSRStyles()` after hydration:
591
+ **Global CSS** (`:root` tokens, `@property`, `globalStyles`, `@font-face`, `@counter-style`, `@function`) configured via `configure()` is automatically deduplicated. When Tasty detects an inline or extracted `[data-tasty-ssr]` stylesheet in the document, it skips client-side injection of globals that were already rendered by the SSR collector. This means `configure()` can be called with the full config on both server and client — no `typeof window === 'undefined'` guard is needed.
592
+
593
+ **Component CSS**: SSR `<style data-tasty-ssr>` tags remain in the DOM. The client injector creates separate `<style>` elements for any new styles. SSR styles are never modified or removed by the client. If this is a concern for very large apps, you can remove the SSR style tags and hydration scripts manually after hydration:
370
594
 
371
595
  ```tsx
372
- import { hydrateTastyCache } from '@tenphi/tasty/ssr';
596
+ import { hydrateTastyClasses } from '@tenphi/tasty/ssr';
373
597
 
374
- hydrateTastyCache();
598
+ hydrateTastyClasses();
375
599
  hydrateRoot(root, <App />);
376
600
 
377
- // Optional: remove SSR style tags after hydration
378
- document.querySelectorAll('style[data-tasty-ssr]').forEach(el => el.remove());
379
- document.querySelectorAll('script[data-tasty-cache]').forEach(el => el.remove());
601
+ // Optional: remove SSR style tags and class-list scripts after hydration
602
+ document.querySelectorAll('style[data-tasty-ssr]').forEach((el) => el.remove());
603
+ document.querySelectorAll('script').forEach((el) => {
604
+ if (el.textContent?.includes('__TASTY__')) el.remove();
605
+ });
380
606
  ```
381
607
 
382
608
  ### `AsyncLocalStorage` not available