@tenphi/tasty 0.0.0-snapshot.056b911

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 (332) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +635 -0
  3. package/dist/_virtual/_rolldown/runtime.js +7 -0
  4. package/dist/chunks/cacheKey.d.ts +1 -0
  5. package/dist/chunks/cacheKey.js +77 -0
  6. package/dist/chunks/cacheKey.js.map +1 -0
  7. package/dist/chunks/definitions.d.ts +37 -0
  8. package/dist/chunks/definitions.js +258 -0
  9. package/dist/chunks/definitions.js.map +1 -0
  10. package/dist/chunks/index.d.ts +1 -0
  11. package/dist/chunks/renderChunk.d.ts +1 -0
  12. package/dist/chunks/renderChunk.js +59 -0
  13. package/dist/chunks/renderChunk.js.map +1 -0
  14. package/dist/compute-styles.d.ts +31 -0
  15. package/dist/compute-styles.js +335 -0
  16. package/dist/compute-styles.js.map +1 -0
  17. package/dist/config.d.ts +409 -0
  18. package/dist/config.js +584 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/core/index.d.ts +34 -0
  21. package/dist/core/index.js +27 -0
  22. package/dist/counter-style/index.js +51 -0
  23. package/dist/counter-style/index.js.map +1 -0
  24. package/dist/debug.d.ts +89 -0
  25. package/dist/debug.js +453 -0
  26. package/dist/debug.js.map +1 -0
  27. package/dist/font-face/index.js +63 -0
  28. package/dist/font-face/index.js.map +1 -0
  29. package/dist/hooks/index.d.ts +7 -0
  30. package/dist/hooks/useCounterStyle.d.ts +36 -0
  31. package/dist/hooks/useCounterStyle.js +64 -0
  32. package/dist/hooks/useCounterStyle.js.map +1 -0
  33. package/dist/hooks/useFontFace.d.ts +45 -0
  34. package/dist/hooks/useFontFace.js +66 -0
  35. package/dist/hooks/useFontFace.js.map +1 -0
  36. package/dist/hooks/useGlobalStyles.d.ts +46 -0
  37. package/dist/hooks/useGlobalStyles.js +88 -0
  38. package/dist/hooks/useGlobalStyles.js.map +1 -0
  39. package/dist/hooks/useKeyframes.d.ts +58 -0
  40. package/dist/hooks/useKeyframes.js +54 -0
  41. package/dist/hooks/useKeyframes.js.map +1 -0
  42. package/dist/hooks/useProperty.d.ts +81 -0
  43. package/dist/hooks/useProperty.js +96 -0
  44. package/dist/hooks/useProperty.js.map +1 -0
  45. package/dist/hooks/useRawCSS.d.ts +22 -0
  46. package/dist/hooks/useRawCSS.js +103 -0
  47. package/dist/hooks/useRawCSS.js.map +1 -0
  48. package/dist/hooks/useStyles.d.ts +40 -0
  49. package/dist/hooks/useStyles.js +31 -0
  50. package/dist/hooks/useStyles.js.map +1 -0
  51. package/dist/index.d.ts +51 -0
  52. package/dist/index.js +36 -0
  53. package/dist/injector/index.d.ts +182 -0
  54. package/dist/injector/index.js +185 -0
  55. package/dist/injector/index.js.map +1 -0
  56. package/dist/injector/injector.d.ts +193 -0
  57. package/dist/injector/injector.js +564 -0
  58. package/dist/injector/injector.js.map +1 -0
  59. package/dist/injector/sheet-manager.d.ts +132 -0
  60. package/dist/injector/sheet-manager.js +698 -0
  61. package/dist/injector/sheet-manager.js.map +1 -0
  62. package/dist/injector/types.d.ts +228 -0
  63. package/dist/keyframes/index.js +206 -0
  64. package/dist/keyframes/index.js.map +1 -0
  65. package/dist/parser/classify.js +319 -0
  66. package/dist/parser/classify.js.map +1 -0
  67. package/dist/parser/const.js +60 -0
  68. package/dist/parser/const.js.map +1 -0
  69. package/dist/parser/lru.js +109 -0
  70. package/dist/parser/lru.js.map +1 -0
  71. package/dist/parser/parser.d.ts +25 -0
  72. package/dist/parser/parser.js +115 -0
  73. package/dist/parser/parser.js.map +1 -0
  74. package/dist/parser/tokenizer.js +69 -0
  75. package/dist/parser/tokenizer.js.map +1 -0
  76. package/dist/parser/types.d.ts +51 -0
  77. package/dist/parser/types.js +46 -0
  78. package/dist/parser/types.js.map +1 -0
  79. package/dist/pipeline/conditions.d.ts +134 -0
  80. package/dist/pipeline/conditions.js +406 -0
  81. package/dist/pipeline/conditions.js.map +1 -0
  82. package/dist/pipeline/exclusive.js +230 -0
  83. package/dist/pipeline/exclusive.js.map +1 -0
  84. package/dist/pipeline/index.d.ts +55 -0
  85. package/dist/pipeline/index.js +708 -0
  86. package/dist/pipeline/index.js.map +1 -0
  87. package/dist/pipeline/materialize.js +1103 -0
  88. package/dist/pipeline/materialize.js.map +1 -0
  89. package/dist/pipeline/parseStateKey.d.ts +15 -0
  90. package/dist/pipeline/parseStateKey.js +446 -0
  91. package/dist/pipeline/parseStateKey.js.map +1 -0
  92. package/dist/pipeline/simplify.js +515 -0
  93. package/dist/pipeline/simplify.js.map +1 -0
  94. package/dist/pipeline/warnings.js +18 -0
  95. package/dist/pipeline/warnings.js.map +1 -0
  96. package/dist/plugins/index.d.ts +2 -0
  97. package/dist/plugins/okhsl-plugin.d.ts +35 -0
  98. package/dist/plugins/okhsl-plugin.js +97 -0
  99. package/dist/plugins/okhsl-plugin.js.map +1 -0
  100. package/dist/plugins/types.d.ts +87 -0
  101. package/dist/properties/index.js +222 -0
  102. package/dist/properties/index.js.map +1 -0
  103. package/dist/properties/property-type-resolver.d.ts +24 -0
  104. package/dist/properties/property-type-resolver.js +90 -0
  105. package/dist/properties/property-type-resolver.js.map +1 -0
  106. package/dist/rsc-cache.js +81 -0
  107. package/dist/rsc-cache.js.map +1 -0
  108. package/dist/ssr/astro-client.d.ts +1 -0
  109. package/dist/ssr/astro-client.js +24 -0
  110. package/dist/ssr/astro-client.js.map +1 -0
  111. package/dist/ssr/astro-middleware.d.ts +15 -0
  112. package/dist/ssr/astro-middleware.js +19 -0
  113. package/dist/ssr/astro-middleware.js.map +1 -0
  114. package/dist/ssr/astro.d.ts +106 -0
  115. package/dist/ssr/astro.js +149 -0
  116. package/dist/ssr/astro.js.map +1 -0
  117. package/dist/ssr/async-storage.d.ts +17 -0
  118. package/dist/ssr/async-storage.js +44 -0
  119. package/dist/ssr/async-storage.js.map +1 -0
  120. package/dist/ssr/collect-auto-properties.js +58 -0
  121. package/dist/ssr/collect-auto-properties.js.map +1 -0
  122. package/dist/ssr/collector.d.ts +102 -0
  123. package/dist/ssr/collector.js +227 -0
  124. package/dist/ssr/collector.js.map +1 -0
  125. package/dist/ssr/context.js +16 -0
  126. package/dist/ssr/context.js.map +1 -0
  127. package/dist/ssr/format-global-rules.js +22 -0
  128. package/dist/ssr/format-global-rules.js.map +1 -0
  129. package/dist/ssr/format-keyframes.js +69 -0
  130. package/dist/ssr/format-keyframes.js.map +1 -0
  131. package/dist/ssr/format-property.js +49 -0
  132. package/dist/ssr/format-property.js.map +1 -0
  133. package/dist/ssr/format-rules.js +73 -0
  134. package/dist/ssr/format-rules.js.map +1 -0
  135. package/dist/ssr/hydrate.d.ts +22 -0
  136. package/dist/ssr/hydrate.js +49 -0
  137. package/dist/ssr/hydrate.js.map +1 -0
  138. package/dist/ssr/index.d.ts +4 -0
  139. package/dist/ssr/index.js +10 -0
  140. package/dist/ssr/index.js.map +1 -0
  141. package/dist/ssr/next.d.ts +45 -0
  142. package/dist/ssr/next.js +75 -0
  143. package/dist/ssr/next.js.map +1 -0
  144. package/dist/ssr/ssr-collector-ref.js +29 -0
  145. package/dist/ssr/ssr-collector-ref.js.map +1 -0
  146. package/dist/states/index.d.ts +49 -0
  147. package/dist/states/index.js +170 -0
  148. package/dist/states/index.js.map +1 -0
  149. package/dist/static/index.d.ts +5 -0
  150. package/dist/static/index.js +4 -0
  151. package/dist/static/inject.d.ts +5 -0
  152. package/dist/static/inject.js +17 -0
  153. package/dist/static/inject.js.map +1 -0
  154. package/dist/static/tastyStatic.d.ts +46 -0
  155. package/dist/static/tastyStatic.js +30 -0
  156. package/dist/static/tastyStatic.js.map +1 -0
  157. package/dist/static/types.d.ts +49 -0
  158. package/dist/static/types.js +24 -0
  159. package/dist/static/types.js.map +1 -0
  160. package/dist/styles/border.d.ts +25 -0
  161. package/dist/styles/border.js +120 -0
  162. package/dist/styles/border.js.map +1 -0
  163. package/dist/styles/color.d.ts +14 -0
  164. package/dist/styles/color.js +26 -0
  165. package/dist/styles/color.js.map +1 -0
  166. package/dist/styles/const.js +17 -0
  167. package/dist/styles/const.js.map +1 -0
  168. package/dist/styles/createStyle.js +79 -0
  169. package/dist/styles/createStyle.js.map +1 -0
  170. package/dist/styles/dimension.js +109 -0
  171. package/dist/styles/dimension.js.map +1 -0
  172. package/dist/styles/directional.js +133 -0
  173. package/dist/styles/directional.js.map +1 -0
  174. package/dist/styles/display.d.ts +30 -0
  175. package/dist/styles/display.js +73 -0
  176. package/dist/styles/display.js.map +1 -0
  177. package/dist/styles/fade.d.ts +15 -0
  178. package/dist/styles/fade.js +62 -0
  179. package/dist/styles/fade.js.map +1 -0
  180. package/dist/styles/fill.d.ts +42 -0
  181. package/dist/styles/fill.js +51 -0
  182. package/dist/styles/fill.js.map +1 -0
  183. package/dist/styles/flow.d.ts +16 -0
  184. package/dist/styles/flow.js +12 -0
  185. package/dist/styles/flow.js.map +1 -0
  186. package/dist/styles/gap.d.ts +31 -0
  187. package/dist/styles/gap.js +38 -0
  188. package/dist/styles/gap.js.map +1 -0
  189. package/dist/styles/height.d.ts +17 -0
  190. package/dist/styles/height.js +19 -0
  191. package/dist/styles/height.js.map +1 -0
  192. package/dist/styles/index.d.ts +1 -0
  193. package/dist/styles/index.js +8 -0
  194. package/dist/styles/index.js.map +1 -0
  195. package/dist/styles/inset.d.ts +24 -0
  196. package/dist/styles/inset.js +34 -0
  197. package/dist/styles/inset.js.map +1 -0
  198. package/dist/styles/list.d.ts +16 -0
  199. package/dist/styles/list.js +100 -0
  200. package/dist/styles/list.js.map +1 -0
  201. package/dist/styles/margin.d.ts +24 -0
  202. package/dist/styles/margin.js +32 -0
  203. package/dist/styles/margin.js.map +1 -0
  204. package/dist/styles/outline.d.ts +29 -0
  205. package/dist/styles/outline.js +55 -0
  206. package/dist/styles/outline.js.map +1 -0
  207. package/dist/styles/padding.d.ts +24 -0
  208. package/dist/styles/padding.js +32 -0
  209. package/dist/styles/padding.js.map +1 -0
  210. package/dist/styles/placement.d.ts +37 -0
  211. package/dist/styles/placement.js +74 -0
  212. package/dist/styles/placement.js.map +1 -0
  213. package/dist/styles/predefined.d.ts +71 -0
  214. package/dist/styles/predefined.js +237 -0
  215. package/dist/styles/predefined.js.map +1 -0
  216. package/dist/styles/preset.d.ts +52 -0
  217. package/dist/styles/preset.js +127 -0
  218. package/dist/styles/preset.js.map +1 -0
  219. package/dist/styles/radius.d.ts +12 -0
  220. package/dist/styles/radius.js +83 -0
  221. package/dist/styles/radius.js.map +1 -0
  222. package/dist/styles/scrollMargin.d.ts +24 -0
  223. package/dist/styles/scrollMargin.js +32 -0
  224. package/dist/styles/scrollMargin.js.map +1 -0
  225. package/dist/styles/scrollbar.d.ts +25 -0
  226. package/dist/styles/scrollbar.js +51 -0
  227. package/dist/styles/scrollbar.js.map +1 -0
  228. package/dist/styles/shadow.d.ts +14 -0
  229. package/dist/styles/shadow.js +25 -0
  230. package/dist/styles/shadow.js.map +1 -0
  231. package/dist/styles/shared.js +17 -0
  232. package/dist/styles/shared.js.map +1 -0
  233. package/dist/styles/transition.d.ts +14 -0
  234. package/dist/styles/transition.js +159 -0
  235. package/dist/styles/transition.js.map +1 -0
  236. package/dist/styles/types.d.ts +564 -0
  237. package/dist/styles/width.d.ts +17 -0
  238. package/dist/styles/width.js +19 -0
  239. package/dist/styles/width.js.map +1 -0
  240. package/dist/tasty.d.ts +134 -0
  241. package/dist/tasty.js +243 -0
  242. package/dist/tasty.js.map +1 -0
  243. package/dist/types.d.ts +184 -0
  244. package/dist/utils/cache-wrapper.js +21 -0
  245. package/dist/utils/cache-wrapper.js.map +1 -0
  246. package/dist/utils/case-converter.js +8 -0
  247. package/dist/utils/case-converter.js.map +1 -0
  248. package/dist/utils/color-math.d.ts +46 -0
  249. package/dist/utils/color-math.js +749 -0
  250. package/dist/utils/color-math.js.map +1 -0
  251. package/dist/utils/color-space.d.ts +5 -0
  252. package/dist/utils/color-space.js +228 -0
  253. package/dist/utils/color-space.js.map +1 -0
  254. package/dist/utils/colors.d.ts +5 -0
  255. package/dist/utils/colors.js +10 -0
  256. package/dist/utils/colors.js.map +1 -0
  257. package/dist/utils/css-types.d.ts +7 -0
  258. package/dist/utils/deps-equal.js +15 -0
  259. package/dist/utils/deps-equal.js.map +1 -0
  260. package/dist/utils/dotize.d.ts +26 -0
  261. package/dist/utils/dotize.js +122 -0
  262. package/dist/utils/dotize.js.map +1 -0
  263. package/dist/utils/filter-base-props.d.ts +15 -0
  264. package/dist/utils/filter-base-props.js +45 -0
  265. package/dist/utils/filter-base-props.js.map +1 -0
  266. package/dist/utils/get-display-name.d.ts +7 -0
  267. package/dist/utils/get-display-name.js +10 -0
  268. package/dist/utils/get-display-name.js.map +1 -0
  269. package/dist/utils/has-keys.js +13 -0
  270. package/dist/utils/has-keys.js.map +1 -0
  271. package/dist/utils/hash.js +14 -0
  272. package/dist/utils/hash.js.map +1 -0
  273. package/dist/utils/is-dev-env.js +19 -0
  274. package/dist/utils/is-dev-env.js.map +1 -0
  275. package/dist/utils/is-valid-element-type.js +15 -0
  276. package/dist/utils/is-valid-element-type.js.map +1 -0
  277. package/dist/utils/merge-styles.d.ts +7 -0
  278. package/dist/utils/merge-styles.js +145 -0
  279. package/dist/utils/merge-styles.js.map +1 -0
  280. package/dist/utils/mod-attrs.d.ts +6 -0
  281. package/dist/utils/mod-attrs.js +20 -0
  282. package/dist/utils/mod-attrs.js.map +1 -0
  283. package/dist/utils/process-tokens.d.ts +17 -0
  284. package/dist/utils/process-tokens.js +83 -0
  285. package/dist/utils/process-tokens.js.map +1 -0
  286. package/dist/utils/resolve-recipes.d.ts +17 -0
  287. package/dist/utils/resolve-recipes.js +146 -0
  288. package/dist/utils/resolve-recipes.js.map +1 -0
  289. package/dist/utils/selector-transform.js +32 -0
  290. package/dist/utils/selector-transform.js.map +1 -0
  291. package/dist/utils/string.js +8 -0
  292. package/dist/utils/string.js.map +1 -0
  293. package/dist/utils/styles.d.ts +99 -0
  294. package/dist/utils/styles.js +220 -0
  295. package/dist/utils/styles.js.map +1 -0
  296. package/dist/utils/typography.d.ts +58 -0
  297. package/dist/utils/typography.js +51 -0
  298. package/dist/utils/typography.js.map +1 -0
  299. package/dist/utils/warnings.d.ts +16 -0
  300. package/dist/utils/warnings.js +16 -0
  301. package/dist/utils/warnings.js.map +1 -0
  302. package/dist/zero/babel.d.ts +195 -0
  303. package/dist/zero/babel.js +456 -0
  304. package/dist/zero/babel.js.map +1 -0
  305. package/dist/zero/css-writer.d.ts +45 -0
  306. package/dist/zero/css-writer.js +73 -0
  307. package/dist/zero/css-writer.js.map +1 -0
  308. package/dist/zero/extractor.d.ts +24 -0
  309. package/dist/zero/extractor.js +266 -0
  310. package/dist/zero/extractor.js.map +1 -0
  311. package/dist/zero/index.d.ts +3 -0
  312. package/dist/zero/index.js +3 -0
  313. package/dist/zero/next.d.ts +86 -0
  314. package/dist/zero/next.js +143 -0
  315. package/dist/zero/next.js.map +1 -0
  316. package/docs/PIPELINE.md +519 -0
  317. package/docs/README.md +31 -0
  318. package/docs/adoption.md +298 -0
  319. package/docs/comparison.md +419 -0
  320. package/docs/configuration.md +389 -0
  321. package/docs/debug.md +318 -0
  322. package/docs/design-system.md +436 -0
  323. package/docs/dsl.md +688 -0
  324. package/docs/getting-started.md +217 -0
  325. package/docs/injector.md +544 -0
  326. package/docs/methodology.md +616 -0
  327. package/docs/react-api.md +557 -0
  328. package/docs/ssr.md +440 -0
  329. package/docs/styles.md +596 -0
  330. package/docs/tasty-static.md +532 -0
  331. package/package.json +221 -0
  332. package/tasty.config.ts +14 -0
package/docs/ssr.md ADDED
@@ -0,0 +1,440 @@
1
+ # Server-Side Rendering (SSR)
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).
4
+
5
+ ---
6
+
7
+ ## Requirements
8
+
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` |
14
+
15
+ The Astro integration (`@tenphi/tasty/ssr/astro`) has no additional dependencies beyond `react`.
16
+
17
+ ---
18
+
19
+ ## How It Works
20
+
21
+ `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, serializes them as `<style>` tags and a cache state script in the HTML. On the client, `hydrateTastyCache()` pre-populates the injector cache so that `computeStyles()` skips the rendering pipeline entirely during hydration.
22
+
23
+ ```
24
+ Server Client
25
+ ────── ──────
26
+ tasty() renders hydrateTastyCache() pre-populates cache
27
+ └─ computeStyles() └─ cacheKey → className map ready
28
+ └─ collector.collect()
29
+ tasty() renders
30
+ After render: └─ computeStyles()
31
+ <style data-tasty-ssr> └─ cache hit → skip pipeline
32
+ <script data-tasty-cache> └─ no CSS re-injection
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Next.js (App Router)
38
+
39
+ ### 1. Create the registry
40
+
41
+ Create a client component that wraps your tree with `TastyRegistry`:
42
+
43
+ ```tsx
44
+ // app/tasty-registry.tsx
45
+ 'use client';
46
+
47
+ import { TastyRegistry } from '@tenphi/tasty/ssr/next';
48
+
49
+ export default function TastyStyleRegistry({
50
+ children,
51
+ }: {
52
+ children: React.ReactNode;
53
+ }) {
54
+ return <TastyRegistry>{children}</TastyRegistry>;
55
+ }
56
+ ```
57
+
58
+ ### 2. Add to root layout
59
+
60
+ Wrap your application in the registry:
61
+
62
+ ```tsx
63
+ // app/layout.tsx
64
+ import TastyStyleRegistry from './tasty-registry';
65
+
66
+ export default function RootLayout({
67
+ children,
68
+ }: {
69
+ children: React.ReactNode;
70
+ }) {
71
+ return (
72
+ <html>
73
+ <body>
74
+ <TastyStyleRegistry>{children}</TastyStyleRegistry>
75
+ </body>
76
+ </html>
77
+ );
78
+ }
79
+ ```
80
+
81
+ That's it. All `tasty()` components inside the tree automatically get SSR support. No per-component changes needed.
82
+
83
+ ### How it works
84
+
85
+ - `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.
86
+ - 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.
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 inline `<script>` tag merges the `cacheKey → className` mapping into `window.__TASTY_SSR_CACHE__` for each flush. This streaming-friendly approach accumulates cache entries incrementally as Suspense boundaries resolve.
89
+ - When the `@tenphi/tasty/ssr/next` module loads on the client, `hydrateTastyCache()` runs automatically from `window.__TASTY_SSR_CACHE__` and pre-populates the injector cache. During hydration, `computeStyles()` hits the cache and skips the entire pipeline.
90
+
91
+ ### Using Tasty in Server Components
92
+
93
+ All Tasty style functions are hook-free and do not require `'use client'`. They can be used directly in React Server Components:
94
+
95
+ - `tasty()` components — dynamic `styleProps` like `<Grid flow="column">` work normally
96
+ - `useStyles()`, `useGlobalStyles()`, `useRawCSS()` — inject styles by class or selector
97
+ - `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()` — inject ancillary CSS rules
98
+
99
+ 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.
100
+
101
+ ### Options
102
+
103
+ ```tsx
104
+ // Skip cache state transfer (saves payload size at the cost of hydration perf)
105
+ <TastyRegistry transferCache={false}>{children}</TastyRegistry>
106
+ ```
107
+
108
+ ### CSP nonce
109
+
110
+ If your app uses Content Security Policy with nonces, configure it before rendering:
111
+
112
+ ```tsx
113
+ // app/layout.tsx or a server-side init file
114
+ import { configure } from '@tenphi/tasty';
115
+
116
+ configure({ nonce: 'your-nonce-value' });
117
+ ```
118
+
119
+ The nonce is automatically applied to all `<style>` and `<script>` tags injected by `TastyRegistry`.
120
+
121
+ ---
122
+
123
+ ## Astro
124
+
125
+ Tasty offers three levels of Astro integration. Choose the one that matches your needs:
126
+
127
+ | Setup | Config needed | Deduplication | Hooks work | Client JS |
128
+ |---|---|---|---|---|
129
+ | Zero setup | None | Per render tree | Yes (within each tree) | None |
130
+ | `tastyIntegration({ islands: false })` | One line | Cross-tree | Yes | None |
131
+ | `tastyIntegration()` | One line | Cross-tree | Yes | Auto-hydration |
132
+
133
+ ### Zero setup (static pages)
134
+
135
+ `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:
136
+
137
+ ```tsx
138
+ // src/components/Card.tsx
139
+ import { tasty } from '@tenphi/tasty';
140
+
141
+ const Card = tasty({
142
+ styles: {
143
+ padding: '4x',
144
+ fill: '#surface',
145
+ radius: '1r',
146
+ border: true,
147
+ },
148
+ });
149
+
150
+ export default Card;
151
+ ```
152
+
153
+ ```astro
154
+ ---
155
+ // src/pages/index.astro
156
+ import Card from '../components/Card.tsx';
157
+ ---
158
+
159
+ <html>
160
+ <body>
161
+ <Card>Styled with zero setup</Card>
162
+ </body>
163
+ </html>
164
+ ```
165
+
166
+ **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.
167
+
168
+ Best for quick prototyping, small static sites, or trying Tasty out in Astro.
169
+
170
+ ### Astro Integration (recommended)
171
+
172
+ For production use, add `tastyIntegration()` to your Astro config. This registers middleware automatically and, by default, injects client-side hydration for islands.
173
+
174
+ #### With islands (default)
175
+
176
+ ```ts
177
+ // astro.config.mjs
178
+ import { defineConfig } from 'astro/config';
179
+ import react from '@astrojs/react';
180
+ import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
181
+
182
+ export default defineConfig({
183
+ integrations: [react(), tastyIntegration()],
184
+ });
185
+ ```
186
+
187
+ This gives you:
188
+
189
+ - A `ServerStyleCollector` per request via `AsyncLocalStorage`, deduplicating CSS across all React trees on the page
190
+ - A single consolidated `<style data-tasty-ssr>` injected into `</head>`
191
+ - A `<script data-tasty-cache>` tag with the `cacheKey -> className` map for client hydration
192
+ - 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
193
+
194
+ All style functions (`useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`) work on the server.
195
+
196
+ ```astro
197
+ ---
198
+ // src/pages/index.astro
199
+ import Card from '../components/Card.tsx';
200
+ import Interactive from '../components/Interactive.tsx';
201
+ ---
202
+
203
+ <html>
204
+ <body>
205
+ <Card>Static -- styles in <style data-tasty-ssr></Card>
206
+ <Interactive client:load>Island -- cache hydrated automatically</Interactive>
207
+ </body>
208
+ </html>
209
+ ```
210
+
211
+ #### Static only (no client JS)
212
+
213
+ If your site has no `client:*` islands, skip the hydration script and cache transfer:
214
+
215
+ ```ts
216
+ // astro.config.mjs
217
+ import { defineConfig } from 'astro/config';
218
+ import react from '@astrojs/react';
219
+ import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
220
+
221
+ export default defineConfig({
222
+ integrations: [react(), tastyIntegration({ islands: false })],
223
+ });
224
+ ```
225
+
226
+ This gives the same middleware deduplication and hook support, but ships zero client-side JavaScript. No `<script data-tasty-cache>` is emitted.
227
+
228
+ ### Manual middleware (advanced)
229
+
230
+ If you need to compose Tasty's middleware with other middleware (e.g., via `sequence()`), use `tastyMiddleware()` directly:
231
+
232
+ ```ts
233
+ // src/middleware.ts
234
+ import { sequence } from 'astro:middleware';
235
+ import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
236
+
237
+ export const onRequest = sequence(
238
+ tastyMiddleware(),
239
+ myOtherMiddleware,
240
+ );
241
+ ```
242
+
243
+ For island hydration with manual middleware, import the client module in a shared entry point or in each island:
244
+
245
+ ```tsx
246
+ import '@tenphi/tasty/ssr/astro-client';
247
+ ```
248
+
249
+ #### Options
250
+
251
+ ```ts
252
+ // Skip cache state transfer (static-only, no islands)
253
+ export const onRequest = tastyMiddleware({ transferCache: false });
254
+ ```
255
+
256
+ ### How it works
257
+
258
+ 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.
259
+
260
+ - **Static components** (no `client:*`): Styles are collected during `renderToString` and injected into `</head>` as a single `<style>` tag. No JavaScript is shipped.
261
+ - **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 cache state from `<script data-tasty-cache>` and pre-populates the injector. The island's `computeStyles()` calls hit the cache during hydration.
262
+ - The middleware reads the full response body, then injects the collected CSS into `</head>` before sending the final HTML.
263
+
264
+ ### CSP nonce
265
+
266
+ Call `configure({ nonce: '...' })` before any rendering happens. The middleware reads the nonce and applies it to injected `<style>` and `<script>` tags.
267
+
268
+ ---
269
+
270
+ ## Generic Framework Integration
271
+
272
+ 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.
273
+
274
+ ```tsx
275
+ import {
276
+ ServerStyleCollector,
277
+ runWithCollector,
278
+ hydrateTastyCache,
279
+ } from '@tenphi/tasty/ssr';
280
+ import { renderToString } from 'react-dom/server';
281
+ import { hydrateRoot } from 'react-dom/client';
282
+
283
+ // ── Server ──────────────────────────────────────────────
284
+
285
+ const collector = new ServerStyleCollector();
286
+
287
+ const html = await runWithCollector(collector, () =>
288
+ renderToString(<App />)
289
+ );
290
+
291
+ const css = collector.getCSS();
292
+ const cacheState = collector.getCacheState();
293
+
294
+ // Embed in your HTML template:
295
+ const fullHtml = `
296
+ <html>
297
+ <head>
298
+ <style data-tasty-ssr>${css}</style>
299
+ <script data-tasty-cache type="application/json">
300
+ ${JSON.stringify(cacheState)}
301
+ </script>
302
+ </head>
303
+ <body>
304
+ <div id="root">${html}</div>
305
+ </body>
306
+ </html>
307
+ `;
308
+
309
+ // ── Client ──────────────────────────────────────────────
310
+
311
+ // Before hydration:
312
+ hydrateTastyCache(); // reads from <script data-tasty-cache>
313
+
314
+ hydrateRoot(document.getElementById('root'), <App />);
315
+ ```
316
+
317
+ ### Streaming SSR
318
+
319
+ For streaming with `renderToPipeableStream`, use `flushCSS()` instead of `getCSS()`:
320
+
321
+ ```tsx
322
+ const collector = new ServerStyleCollector();
323
+
324
+ const stream = await runWithCollector(collector, () =>
325
+ renderToPipeableStream(<App />, {
326
+ onShellReady() {
327
+ // Flush styles collected so far
328
+ const css = collector.flushCSS();
329
+ res.write(`<style data-tasty-ssr>${css}</style>`);
330
+ stream.pipe(res);
331
+ },
332
+ onAllReady() {
333
+ // Flush any remaining styles + cache state
334
+ const css = collector.flushCSS();
335
+ if (css) res.write(`<style data-tasty-ssr>${css}</style>`);
336
+
337
+ const state = collector.getCacheState();
338
+ res.write(`<script data-tasty-cache type="application/json">${JSON.stringify(state)}</script>`);
339
+ },
340
+ })
341
+ );
342
+ ```
343
+
344
+ ---
345
+
346
+ ## API Reference
347
+
348
+ ### Entry points
349
+
350
+ | Import path | Description |
351
+ |---|---|
352
+ | `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `runWithCollector`, `hydrateTastyCache` |
353
+ | `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
354
+ | `@tenphi/tasty/ssr/astro` | Astro: `tastyIntegration`, `tastyMiddleware` |
355
+ | `@tenphi/tasty/ssr/astro-client` | Astro: client-side cache hydration (auto-injected by integration, or import manually) |
356
+
357
+ ### `ServerStyleCollector`
358
+
359
+ Server-safe style collector. One instance per request.
360
+
361
+ | Method | Description |
362
+ |---|---|
363
+ | `allocateClassName(cacheKey)` | Allocate a sequential class name (`t0`, `t1`, ...) for a cache key. Returns `{ className, isNewAllocation }`. |
364
+ | `collectChunk(cacheKey, className, rules)` | Record CSS rules for a chunk. Deduplicated by `cacheKey`. |
365
+ | `collectKeyframes(name, css)` | Record a `@keyframes` rule. Deduplicated by name. |
366
+ | `allocateKeyframeName(providedName?)` | Allocate a keyframe name. Returns `providedName` if given, otherwise generates one (`k0`, `k1`, ...). |
367
+ | `collectProperty(name, css)` | Record a `@property` rule. Deduplicated by name. |
368
+ | `collectFontFace(key, css)` | Record a `@font-face` rule. Deduplicated by content hash. |
369
+ | `collectCounterStyle(name, css)` | Record a `@counter-style` rule. Deduplicated by name. |
370
+ | `allocateCounterStyleName(providedName?)` | Allocate a counter-style name. Returns `providedName` if given, otherwise generates one (`cs0`, `cs1`, ...). |
371
+ | `collectGlobalStyles(key, css)` | Record global styles (from `useGlobalStyles`). Deduplicated by key. |
372
+ | `collectRawCSS(key, css)` | Record raw CSS text (from `useRawCSS`). Deduplicated by key. |
373
+ | `collectInternals()` | Collect internal `@property` rules, `:root` token defaults, `@font-face`, and `@counter-style` rules from the global config. Called automatically on first chunk collection; idempotent. |
374
+ | `getCSS()` | Get all collected CSS as a single string. For non-streaming SSR. |
375
+ | `flushCSS()` | Get only CSS collected since the last flush. For streaming SSR. |
376
+ | `getCacheState()` | Serialize `{ entries: Record<cacheKey, className>, classCounter }` for client hydration. |
377
+
378
+ ### `TastyRegistry`
379
+
380
+ Next.js App Router component. Props:
381
+
382
+ | Prop | Type | Default | Description |
383
+ |---|---|---|---|
384
+ | `children` | `ReactNode` | required | Application tree |
385
+ | `transferCache` | `boolean` | `true` | Embed cache state script for zero-cost hydration |
386
+
387
+ ### `tastyIntegration(options?)`
388
+
389
+ Astro integration factory. Registers middleware and optionally injects client hydration.
390
+
391
+ | Option | Type | Default | Description |
392
+ |---|---|---|---|
393
+ | `islands` | `boolean` | `true` | When `true`, injects client hydration script and enables `transferCache`. When `false`, no client JS is shipped. |
394
+
395
+ ### `tastyMiddleware(options?)`
396
+
397
+ Astro middleware factory. Use for manual middleware composition.
398
+
399
+ | Option | Type | Default | Description |
400
+ |---|---|---|---|
401
+ | `transferCache` | `boolean` | `true` | Embed cache state script for island hydration |
402
+
403
+ ### `hydrateTastyCache(state?)`
404
+
405
+ Pre-populate the client injector cache. When called without arguments, reads from `window.__TASTY_SSR_CACHE__` (streaming) or `<script data-tasty-cache>` (non-streaming).
406
+
407
+ ### `runWithCollector(collector, fn)`
408
+
409
+ 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.
410
+
411
+ ---
412
+
413
+ ## Troubleshooting
414
+
415
+ ### Styles flash on page load (FOUC)
416
+
417
+ 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).
418
+
419
+ ### Hydration mismatch warnings
420
+
421
+ 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 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 `hydrateTastyCache()` before `hydrateRoot()`.
422
+
423
+ ### Styles duplicated after hydration
424
+
425
+ 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:
426
+
427
+ ```tsx
428
+ import { hydrateTastyCache } from '@tenphi/tasty/ssr';
429
+
430
+ hydrateTastyCache();
431
+ hydrateRoot(root, <App />);
432
+
433
+ // Optional: remove SSR style tags after hydration
434
+ document.querySelectorAll('style[data-tasty-ssr]').forEach(el => el.remove());
435
+ document.querySelectorAll('script[data-tasty-cache]').forEach(el => el.remove());
436
+ ```
437
+
438
+ ### `AsyncLocalStorage` not available
439
+
440
+ The `@tenphi/tasty/ssr` entry point imports from `node:async_hooks`. This is excluded from client bundles by the build configuration. If you see import errors on the client, ensure your bundler treats `node:async_hooks` as external or use the `@tenphi/tasty/ssr/next` entry point (which does not use ALS).