@tenphi/tasty 0.0.0-snapshot.bbd10c7 → 0.0.0-snapshot.bbfcd75

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 (370) hide show
  1. package/README.md +196 -140
  2. package/dist/babel-Zs1Q03qt.d.ts +83 -0
  3. package/dist/chunks/async-storage-CG5HnUT5.js +20 -0
  4. package/dist/chunks/async-storage-CG5HnUT5.js.map +1 -0
  5. package/dist/chunks/build-config-CUQVwdWF.js +42 -0
  6. package/dist/chunks/build-config-CUQVwdWF.js.map +1 -0
  7. package/dist/chunks/collector-B0LTHw6Z.js +208 -0
  8. package/dist/chunks/collector-B0LTHw6Z.js.map +1 -0
  9. package/dist/chunks/config-engine-C3pF9EK9.js +267 -0
  10. package/dist/chunks/config-engine-C3pF9EK9.js.map +1 -0
  11. package/dist/chunks/css-definitions-D_it4XZH.js +875 -0
  12. package/dist/chunks/css-definitions-D_it4XZH.js.map +1 -0
  13. package/dist/chunks/css-resources-BN8apgxt.js +142 -0
  14. package/dist/chunks/css-resources-BN8apgxt.js.map +1 -0
  15. package/dist/{debug.js → chunks/debug-C9LeNVoJ.js} +187 -139
  16. package/dist/chunks/debug-C9LeNVoJ.js.map +1 -0
  17. package/dist/chunks/dsl-C5yuAn41.js +1373 -0
  18. package/dist/chunks/dsl-C5yuAn41.js.map +1 -0
  19. package/dist/chunks/hydration-TRcz5vnk.js +41 -0
  20. package/dist/chunks/hydration-TRcz5vnk.js.map +1 -0
  21. package/dist/chunks/react-runtime-O-OUrKLH.js +557 -0
  22. package/dist/chunks/react-runtime-O-OUrKLH.js.map +1 -0
  23. package/dist/chunks/runtime-engine-DvnE2g-E.js +2437 -0
  24. package/dist/chunks/runtime-engine-DvnE2g-E.js.map +1 -0
  25. package/dist/chunks/runtime-state-BTDqSYBk.js +127 -0
  26. package/dist/chunks/runtime-state-BTDqSYBk.js.map +1 -0
  27. package/dist/{utils/merge-styles.js → chunks/shared-utils-DWA4LjIa.js} +55 -55
  28. package/dist/chunks/shared-utils-DWA4LjIa.js.map +1 -0
  29. package/dist/chunks/style-engine-C1ECBZxR.js +5131 -0
  30. package/dist/chunks/style-engine-C1ECBZxR.js.map +1 -0
  31. package/dist/{zero/extractor.js → chunks/zero-engine-B6EnYcXY.js} +89 -82
  32. package/dist/chunks/zero-engine-B6EnYcXY.js.map +1 -0
  33. package/dist/collector-hl5KNH7k.d.ts +165 -0
  34. package/dist/config-DWL1ZYue.d.ts +1358 -0
  35. package/dist/core/index.d.ts +5 -34
  36. package/dist/core/index.js +9 -27
  37. package/dist/index-D9N9GV24.d.ts +1947 -0
  38. package/dist/index.d.ts +5 -51
  39. package/dist/index.js +11 -36
  40. package/dist/{utils/merge-styles.d.ts → merge-styles-C_oMoJVd.d.ts} +3 -3
  41. package/dist/ssr/astro-client.js +6 -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 +7 -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 +7 -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 +204 -48
  57. package/dist/ssr/astro.js.map +1 -1
  58. package/dist/ssr/index.d.ts +37 -4
  59. package/dist/ssr/index.js +6 -6
  60. package/dist/ssr/index.js.map +1 -1
  61. package/dist/ssr/next-config.d.ts +66 -0
  62. package/dist/ssr/next-config.js +124 -0
  63. package/dist/ssr/next-config.js.map +1 -0
  64. package/dist/ssr/next.d.ts +14 -5
  65. package/dist/ssr/next.js +31 -46
  66. package/dist/ssr/next.js.map +1 -1
  67. package/dist/static/index.d.ts +91 -5
  68. package/dist/static/index.js +31 -3
  69. package/dist/static/index.js.map +1 -0
  70. package/dist/static/inject.js.map +1 -1
  71. package/dist/types-jKZNpias.d.ts +1616 -0
  72. package/dist/zero/babel.d.ts +2 -182
  73. package/dist/zero/babel.js +82 -113
  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 +0 -31
  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 +229 -0
  83. package/docs/comparison.md +76 -48
  84. package/docs/configuration.md +566 -69
  85. package/docs/debug.md +37 -23
  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 +69 -37
  91. package/docs/migration-v3.md +285 -0
  92. package/docs/pipeline.md +744 -0
  93. package/docs/plugins.md +353 -0
  94. package/docs/{runtime.md → react-api.md} +176 -67
  95. package/docs/runtime-benchmarks.md +377 -0
  96. package/docs/ssr.md +350 -94
  97. package/docs/styles.md +108 -27
  98. package/docs/tasty-static.md +149 -87
  99. package/package.json +121 -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/compute-styles.d.ts +0 -31
  112. package/dist/compute-styles.js +0 -356
  113. package/dist/compute-styles.js.map +0 -1
  114. package/dist/config.d.ts +0 -372
  115. package/dist/config.js +0 -507
  116. package/dist/config.js.map +0 -1
  117. package/dist/counter-style/index.js +0 -51
  118. package/dist/counter-style/index.js.map +0 -1
  119. package/dist/debug.d.ts +0 -89
  120. package/dist/debug.js.map +0 -1
  121. package/dist/font-face/index.js +0 -63
  122. package/dist/font-face/index.js.map +0 -1
  123. package/dist/hooks/index.d.ts +0 -7
  124. package/dist/hooks/useCounterStyle.d.ts +0 -50
  125. package/dist/hooks/useCounterStyle.js +0 -45
  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 -69
  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 -77
  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 -63
  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 -108
  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 -34
  141. package/dist/hooks/useRawCSS.js.map +0 -1
  142. package/dist/hooks/useStyles.d.ts +0 -40
  143. package/dist/hooks/useStyles.js +0 -29
  144. package/dist/hooks/useStyles.js.map +0 -1
  145. package/dist/injector/index.d.ts +0 -166
  146. package/dist/injector/index.js +0 -166
  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 -60
  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/async-storage.d.ts +0 -17
  199. package/dist/ssr/async-storage.js +0 -34
  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 -219
  205. package/dist/ssr/collector.js.map +0 -1
  206. package/dist/ssr/format-global-rules.js +0 -22
  207. package/dist/ssr/format-global-rules.js.map +0 -1
  208. package/dist/ssr/format-keyframes.js +0 -69
  209. package/dist/ssr/format-keyframes.js.map +0 -1
  210. package/dist/ssr/format-property.js +0 -49
  211. package/dist/ssr/format-property.js.map +0 -1
  212. package/dist/ssr/format-rules.js +0 -73
  213. package/dist/ssr/format-rules.js.map +0 -1
  214. package/dist/ssr/hydrate.d.ts +0 -22
  215. package/dist/ssr/hydrate.js +0 -49
  216. package/dist/ssr/hydrate.js.map +0 -1
  217. package/dist/ssr/ssr-collector-ref.js +0 -12
  218. package/dist/ssr/ssr-collector-ref.js.map +0 -1
  219. package/dist/states/index.d.ts +0 -49
  220. package/dist/states/index.js +0 -170
  221. package/dist/states/index.js.map +0 -1
  222. package/dist/static/tastyStatic.d.ts +0 -46
  223. package/dist/static/tastyStatic.js +0 -30
  224. package/dist/static/tastyStatic.js.map +0 -1
  225. package/dist/static/types.d.ts +0 -49
  226. package/dist/static/types.js +0 -24
  227. package/dist/static/types.js.map +0 -1
  228. package/dist/styles/border.d.ts +0 -25
  229. package/dist/styles/border.js +0 -120
  230. package/dist/styles/border.js.map +0 -1
  231. package/dist/styles/color.d.ts +0 -14
  232. package/dist/styles/color.js +0 -26
  233. package/dist/styles/color.js.map +0 -1
  234. package/dist/styles/const.js +0 -17
  235. package/dist/styles/const.js.map +0 -1
  236. package/dist/styles/createStyle.js +0 -79
  237. package/dist/styles/createStyle.js.map +0 -1
  238. package/dist/styles/dimension.js +0 -109
  239. package/dist/styles/dimension.js.map +0 -1
  240. package/dist/styles/directional.js +0 -133
  241. package/dist/styles/directional.js.map +0 -1
  242. package/dist/styles/display.d.ts +0 -30
  243. package/dist/styles/display.js +0 -73
  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 -62
  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 -38
  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 -24
  264. package/dist/styles/inset.js +0 -34
  265. package/dist/styles/inset.js.map +0 -1
  266. package/dist/styles/list.d.ts +0 -16
  267. package/dist/styles/list.js +0 -100
  268. package/dist/styles/list.js.map +0 -1
  269. package/dist/styles/margin.d.ts +0 -24
  270. package/dist/styles/margin.js +0 -32
  271. package/dist/styles/margin.js.map +0 -1
  272. package/dist/styles/outline.d.ts +0 -29
  273. package/dist/styles/outline.js +0 -55
  274. package/dist/styles/outline.js.map +0 -1
  275. package/dist/styles/padding.d.ts +0 -24
  276. package/dist/styles/padding.js +0 -32
  277. package/dist/styles/padding.js.map +0 -1
  278. package/dist/styles/placement.d.ts +0 -37
  279. package/dist/styles/placement.js +0 -74
  280. package/dist/styles/placement.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 -127
  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 -83
  289. package/dist/styles/radius.js.map +0 -1
  290. package/dist/styles/scrollMargin.d.ts +0 -24
  291. package/dist/styles/scrollMargin.js +0 -32
  292. package/dist/styles/scrollMargin.js.map +0 -1
  293. package/dist/styles/scrollbar.d.ts +0 -25
  294. package/dist/styles/scrollbar.js +0 -51
  295. package/dist/styles/scrollbar.js.map +0 -1
  296. package/dist/styles/shadow.d.ts +0 -14
  297. package/dist/styles/shadow.js +0 -25
  298. package/dist/styles/shadow.js.map +0 -1
  299. package/dist/styles/shared.js +0 -17
  300. package/dist/styles/shared.js.map +0 -1
  301. package/dist/styles/transition.d.ts +0 -14
  302. package/dist/styles/transition.js +0 -159
  303. package/dist/styles/transition.js.map +0 -1
  304. package/dist/styles/types.d.ts +0 -564
  305. package/dist/styles/width.d.ts +0 -17
  306. package/dist/styles/width.js +0 -19
  307. package/dist/styles/width.js.map +0 -1
  308. package/dist/tasty.d.ts +0 -134
  309. package/dist/tasty.js +0 -240
  310. package/dist/tasty.js.map +0 -1
  311. package/dist/types.d.ts +0 -184
  312. package/dist/utils/cache-wrapper.js +0 -21
  313. package/dist/utils/cache-wrapper.js.map +0 -1
  314. package/dist/utils/case-converter.js +0 -8
  315. package/dist/utils/case-converter.js.map +0 -1
  316. package/dist/utils/color-math.d.ts +0 -46
  317. package/dist/utils/color-math.js +0 -749
  318. package/dist/utils/color-math.js.map +0 -1
  319. package/dist/utils/color-space.d.ts +0 -5
  320. package/dist/utils/color-space.js +0 -228
  321. package/dist/utils/color-space.js.map +0 -1
  322. package/dist/utils/colors.d.ts +0 -5
  323. package/dist/utils/colors.js +0 -10
  324. package/dist/utils/colors.js.map +0 -1
  325. package/dist/utils/css-types.d.ts +0 -7
  326. package/dist/utils/dotize.d.ts +0 -26
  327. package/dist/utils/dotize.js +0 -122
  328. package/dist/utils/dotize.js.map +0 -1
  329. package/dist/utils/filter-base-props.d.ts +0 -15
  330. package/dist/utils/filter-base-props.js +0 -45
  331. package/dist/utils/filter-base-props.js.map +0 -1
  332. package/dist/utils/get-display-name.d.ts +0 -7
  333. package/dist/utils/get-display-name.js +0 -10
  334. package/dist/utils/get-display-name.js.map +0 -1
  335. package/dist/utils/has-keys.js +0 -13
  336. package/dist/utils/has-keys.js.map +0 -1
  337. package/dist/utils/is-dev-env.js +0 -19
  338. package/dist/utils/is-dev-env.js.map +0 -1
  339. package/dist/utils/is-valid-element-type.js +0 -15
  340. package/dist/utils/is-valid-element-type.js.map +0 -1
  341. package/dist/utils/merge-styles.js.map +0 -1
  342. package/dist/utils/mod-attrs.d.ts +0 -6
  343. package/dist/utils/mod-attrs.js +0 -20
  344. package/dist/utils/mod-attrs.js.map +0 -1
  345. package/dist/utils/process-tokens.d.ts +0 -17
  346. package/dist/utils/process-tokens.js +0 -83
  347. package/dist/utils/process-tokens.js.map +0 -1
  348. package/dist/utils/resolve-recipes.d.ts +0 -17
  349. package/dist/utils/resolve-recipes.js +0 -146
  350. package/dist/utils/resolve-recipes.js.map +0 -1
  351. package/dist/utils/selector-transform.js +0 -32
  352. package/dist/utils/selector-transform.js.map +0 -1
  353. package/dist/utils/string.js +0 -8
  354. package/dist/utils/string.js.map +0 -1
  355. package/dist/utils/styles.d.ts +0 -99
  356. package/dist/utils/styles.js +0 -220
  357. package/dist/utils/styles.js.map +0 -1
  358. package/dist/utils/typography.d.ts +0 -47
  359. package/dist/utils/typography.js +0 -51
  360. package/dist/utils/typography.js.map +0 -1
  361. package/dist/utils/warnings.d.ts +0 -16
  362. package/dist/utils/warnings.js +0 -16
  363. package/dist/utils/warnings.js.map +0 -1
  364. package/dist/zero/css-writer.d.ts +0 -45
  365. package/dist/zero/css-writer.js +0 -73
  366. package/dist/zero/css-writer.js.map +0 -1
  367. package/dist/zero/extractor.d.ts +0 -24
  368. package/dist/zero/extractor.js.map +0 -1
  369. package/docs/PIPELINE.md +0 -519
  370. /package/dist/{chunks/cacheKey.d.ts → ssr/astro-client.d.ts} +0 -0
@@ -1,6 +1,8 @@
1
1
  # Getting Started
2
2
 
3
- This guide walks you from zero to a working Tasty component, then through the optional shared configuration and tooling layers. It is the right starting point when you already want to try Tasty in code. If you are still deciding whether Tasty fits your team, start with [Comparison](comparison.md) and [Adoption Guide](adoption.md) first. For a feature overview, see the [README](../README.md). For the full style language reference, see the [Style DSL](dsl.md). For the React API, see the [Runtime API](runtime.md). For the rest of the docs by role or task, see the [Docs Hub](README.md).
3
+ Build one stateful component first. You will see how Tasty expresses priority as a state map, why overlapping states do not compete, and where shared design-system configuration fits afterward.
4
+
5
+ This is the right starting point when you are ready to try Tasty in code. If you are still evaluating it, read the [Introduction](../README.md), [Comparison](comparison.md), and [Adoption Guide](adoption.md).
4
6
 
5
7
  ---
6
8
 
@@ -10,7 +12,7 @@ This guide walks you from zero to a working Tasty component, then through the op
10
12
  - **React** >= 18 (peer dependency)
11
13
  - **Package manager**: pnpm, npm, or yarn
12
14
 
13
- Tasty can be used immediately in a React app, but it is most compelling for teams building reusable components with intersecting states, variants, tokens, and design-system conventions.
15
+ No Tasty configuration is required for the first component.
14
16
 
15
17
  ---
16
18
 
@@ -22,36 +24,57 @@ pnpm add @tenphi/tasty
22
24
 
23
25
  ---
24
26
 
25
- ## Your first component
27
+ ## Build a button whose states don’t fight
26
28
 
27
29
  ```tsx
28
30
  import { tasty } from '@tenphi/tasty';
29
31
 
30
- const Card = tasty({
31
- as: 'div',
32
+ const Button = tasty({
33
+ as: 'button',
32
34
  styles: {
33
- display: 'flex',
34
- flow: 'column',
35
- padding: '4x',
36
- gap: '2x',
37
- fill: '#white',
38
- border: true,
39
- radius: true,
35
+ padding: '12px 20px',
36
+ radius: '8px',
37
+ border: '0',
38
+ fill: {
39
+ '': 'royalblue',
40
+ ':hover': 'blue',
41
+ ':active': 'navy',
42
+ disabled: 'lightgray',
43
+ },
44
+ color: {
45
+ '': 'white',
46
+ disabled: 'dimgray',
47
+ },
48
+ cursor: {
49
+ '': 'pointer',
50
+ disabled: 'not-allowed',
51
+ },
40
52
  },
41
53
  });
42
54
 
43
55
  export default function App() {
44
- return <Card>Hello, Tasty!</Card>;
56
+ return (
57
+ <>
58
+ <Button>Save changes</Button>
59
+ <Button disabled>Disabled</Button>
60
+ </>
61
+ );
45
62
  }
46
63
  ```
47
64
 
48
- `tasty()` returns a normal React component. Values like `4x`, `#white`, `true`, and `1r` are Tasty DSL — they map to CSS custom properties, shorthand expansions, and design tokens. See [Style Properties](styles.md) for the full reference.
65
+ `tasty()` returns a normal React component. The keys in each state map define priority: later branches win over earlier ones. Because `disabled` is last, a disabled button keeps its disabled styles even while the pointer is over it.
66
+
67
+ `disabled` and `checked` are built-in automatic states. Tasty activates them from the corresponding native prop or attribute, so `disabled` is the concise Tasty form of `[disabled]`, and `checked` is the concise form of `[checked]`.
68
+
69
+ Tasty compiles that priority into selectors that exclude one another. The hover, active, and disabled background rules cannot all match and ask the CSS cascade to decide the winner.
70
+
71
+ The example uses ordinary CSS values so it works without setup. A design system will usually replace them with shared tokens, units, and state aliases in the next step. See the [Style DSL](dsl.md) for the complete state-map syntax and [Style Properties](styles.md) for Tasty’s enhanced CSS properties.
49
72
 
50
73
  ---
51
74
 
52
- ## Optional: add shared configuration
75
+ ## Add your design system’s language
53
76
 
54
- Use `configure()` once, before your app renders, when your app or design system needs shared state aliases, tokens, recipes, or parser extensions:
77
+ The first component needs no configuration. Use `configure()` once, before your app renders, when your app or design system is ready to share tokens, state aliases, recipes, units, or parser extensions:
55
78
 
56
79
  ```tsx
57
80
  // src/tasty-config.ts
@@ -91,8 +114,8 @@ configure({
91
114
  '#primary': 'oklch(55% 0.25 265)',
92
115
  '#surface': '#fff',
93
116
  '#text': '#111',
94
- '$gap': '8px',
95
- '$radius': '4px',
117
+ $gap: '8px',
118
+ $radius: '4px',
96
119
  '$border-width': '1px',
97
120
  '$outline-width': '2px',
98
121
  },
@@ -148,23 +171,21 @@ export default [
148
171
 
149
172
  The `recommended` config enables 18 of the plugin's 27 total rules. It covers the most common issues without turning on the stricter governance rules:
150
173
 
151
- | Category | Rules | Examples |
152
- |----------|-------|---------|
153
- | Property validation | `known-property`, `valid-boolean-property`, `valid-sub-element` | Flags typos like `pading` or invalid boolean usage |
154
- | Value validation | `valid-value`, `valid-color-token`, `valid-custom-unit` | Catches `#nonexistent` tokens, bad unit syntax |
155
- | State validation | `valid-state-key`, `no-nested-state-map`, `require-default-state` | Validates state key syntax, ensures `''` default exists |
156
- | Structure | `valid-styles-structure`, `no-important`, `no-nested-selector` | Prevents `!important`, invalid nesting |
157
- | Static mode | `static-no-dynamic-values`, `static-valid-selector` | Enforces build-time constraints in `tastyStatic()` |
158
- | Style properties | `valid-preset`, `valid-recipe`, `valid-transition`, `valid-directional-modifier`, `valid-radius-shape` | Validates preset names, recipe references, transition syntax |
174
+ | Category | Rules | Examples |
175
+ | ------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ |
176
+ | Property validation | `known-property`, `valid-boolean-property`, `valid-sub-element` | Flags typos like `pading` or invalid boolean usage |
177
+ | Value validation | `valid-value`, `valid-color-token`, `valid-custom-unit` | Catches `#nonexistent` tokens, bad unit syntax |
178
+ | State validation | `valid-state-key`, `no-nested-state-map`, `require-default-state` | Validates state key syntax, ensures `''` default exists |
179
+ | Structure | `valid-styles-structure`, `no-important`, `no-nested-selector` | Prevents `!important`, invalid nesting |
180
+ | Static mode | `static-no-dynamic-values`, `static-valid-selector` | Enforces build-time constraints in `tastyStatic()` |
181
+ | Style properties | `valid-preset`, `valid-recipe`, `valid-transition`, `valid-directional-modifier`, `valid-radius-shape` | Validates preset names, recipe references, transition syntax |
159
182
 
160
183
  ### Strict config
161
184
 
162
185
  For stricter governance, use `tasty.configs.strict`. It adds rules that enforce best practices like preferring shorthand properties, consistent token usage, and flagging direct `styles` prop usage:
163
186
 
164
187
  ```js
165
- export default [
166
- tasty.configs.strict,
167
- ];
188
+ export default [tasty.configs.strict];
168
189
  ```
169
190
 
170
191
  ---
@@ -179,19 +200,19 @@ export default [
179
200
 
180
201
  ## Choosing a rendering mode
181
202
 
182
- Tasty has two styling approaches. Pick the one that fits your use case, then decide whether your runtime setup also needs server rendering support:
203
+ `tasty()` is the default for all React apps. All `tasty()` components and style functions are hook-free and work as React Server Components without `'use client'`. Zero-runtime delivery is not tied to one API: both server-only `tasty()` and build-time `tastyStatic()` can ship no Tasty styling runtime to the browser.
183
204
 
184
- | Approach | Entry point | Best for | Trade-off |
185
- |------|-------------|----------|-----------|
186
- | **Runtime** | `tasty()` from `@tenphi/tasty` | Interactive apps with reusable stateful components, design systems | CSS generated at runtime; full feature set (styleProps, sub-elements, variants) |
187
- | **Zero-runtime** | `tastyStatic()` from `@tenphi/tasty/static` | Static sites, landing pages, SSG | Zero JS overhead; requires Babel plugin; no dynamic props |
205
+ | Approach | Authoring API | CSS is generated | Tasty runtime in the browser | Best for |
206
+ | ------------------------- | ------------------------------------------- | ----------------------------------- | ---------------------------- | ---------------------------------------------------------------- |
207
+ | **Server-only React** | `tasty()`; optional `@tenphi/tasty/ssr/*` | During server or static rendering | None | Astro without islands, server-only RSC, SSG |
208
+ | **Hydrated React** | `tasty()` plus `@tenphi/tasty/ssr/*` | During SSR, then on demand in React | Only in hydrated components | Interactive React apps, Next.js client components, Astro islands |
209
+ | **Build-time extraction** | `tastyStatic()` from `@tenphi/tasty/static` | During the build | None in file mode | Non-React frameworks or extraction before rendering |
188
210
 
189
- Both share the same DSL, tokens, units, and state mappings.
211
+ Both `tasty()` and `tastyStatic()` share the same DSL, tokens, units, and state mappings.
190
212
 
191
- - Runtime is the default and requires no extra setup beyond `@tenphi/tasty`.
192
- - If your framework can execute runtime code during server rendering, add SSR support on top of runtime with `@tenphi/tasty/ssr/next`, `@tenphi/tasty/ssr/astro`, or the core SSR API. This still uses `tasty()`; it just collects CSS on the server and hydrates the cache on the client.
193
- - Zero-runtime requires the Babel plugin and additional peer dependencies. See [Zero Runtime (tastyStatic)](tasty-static.md).
194
- - SSR works with existing `tasty()` components — wrap your app with a registry, middleware, or collector. See [Server-Side Rendering](ssr.md).
213
+ - **Server-only `tasty()`** keeps the full feature set (`styleProps`, sub-elements, variants) while generating CSS during server rendering. `tasty()` plus `tastyIntegration({ islands: false })` in Astro is the verified concrete setup and ships no client JavaScript. The same server-only architecture applies to Next.js RSC; verify the generated output for your deployment.
214
+ - **Hydrated `tasty()`** uses the SSR integrations to batch and deduplicate server CSS, prevent FOUC, and hydrate the client cache. The browser styling pipeline is available only where React components hydrate. See [Server-Side Rendering](ssr.md).
215
+ - **Build-time extraction** uses the Babel plugin and additional peer dependencies. Choose `tastyStatic()` when CSS must be generated before rendering or when the consumer is not React. File mode ships no styling runtime; inject mode intentionally includes a tiny CSS injector. See [Build-Time Extraction (`tastyStatic`)](tasty-static.md).
195
216
 
196
217
  ---
197
218
 
@@ -200,7 +221,7 @@ Both share the same DSL, tokens, units, and state mappings.
200
221
  - **[Docs Hub](README.md)** — Pick the next guide by role, styling approach, or task
201
222
  - **[Methodology](methodology.md)** — The recommended patterns for structuring Tasty components: sub-elements, styleProps, tokens, extension
202
223
  - **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
203
- - **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements, hooks
224
+ - **[React API](react-api.md)** — `tasty()` factory, component props, variants, sub-elements, style functions
204
225
  - **[Building a Design System](design-system.md)** — Practical guide to building a DS layer with Tasty: tokens, recipes, primitives, compound components
205
226
  - **[Adoption Guide](adoption.md)** — Roll out Tasty inside an existing design system or platform team
206
227
  - **[Comparison](comparison.md)** — Evaluate Tasty against other styling systems
@@ -214,4 +235,4 @@ Both share the same DSL, tokens, units, and state mappings.
214
235
 
215
236
  - Styles are missing on first render: make sure the file that calls `configure()` is imported before any `tasty()` component renders.
216
237
  - Token or unit values are not what you expect: check your `configure({ tokens, units })` setup, then inspect the generated CSS variables with [Debug Utilities](debug.md).
217
- - You need build-time extraction or server-rendered CSS delivery: use [Zero Runtime (tastyStatic)](tasty-static.md) for extraction, or add [Server-Side Rendering](ssr.md) on top of runtime `tasty()` when your framework renders on the server.
238
+ - You need zero-runtime delivery: use server-only [`tasty()` with server rendering](ssr.md) to keep the full React API, or use [`tastyStatic()` build-time extraction](tasty-static.md) when styles must be compiled before rendering.
package/docs/injector.md CHANGED
@@ -9,9 +9,9 @@ A high-performance CSS-in-JS solution that powers the Tasty design system with e
9
9
  The Style Injector is the core engine behind Tasty's styling system, providing:
10
10
 
11
11
  - **Hash-based deduplication** - Identical CSS gets the same className
12
- - **Reference counting** - Automatic cleanup when components unmount (refCount = 0)
12
+ - **DOM-driven lifetime** - Styles are collected once nothing renders them
13
13
  - **CSS nesting flattening** - Handles `&`, `.Class`, `SubElement` patterns
14
- - **Keyframes injection** - First-class `@keyframes` support with immediate disposal
14
+ - **At-rule injection** - First-class `@keyframes`, `@property`, `@font-face`, `@counter-style`, and `@function` support
15
15
  - **Smart cleanup** - CSS rules batched cleanup, keyframes disposed immediately
16
16
  - **SSR support** - Deterministic class names and CSS extraction
17
17
  - **Multiple roots** - Works with Document and ShadowRoot
@@ -63,10 +63,16 @@ const result = inject([{
63
63
 
64
64
  console.log(result.className); // 't-abc123'
65
65
 
66
- // Cleanup when component unmounts (refCount decremented)
66
+ // Release the pin; the class becomes collectible once nothing renders it
67
67
  result.dispose();
68
68
  ```
69
69
 
70
+ `inject()` **pins** the class it returns, and `gc()` never evicts a pinned class
71
+ — that is what `dispose()` releases. Pass `{ pin: false }` when the caller keeps
72
+ no handle and the DOM is the only record that the class is in use; `dispose()`
73
+ is then a no-op. The render path (`tasty()` / `computeStyles()`) injects this
74
+ way, because a hook-free render has no unmount signal to dispose on.
75
+
70
76
  ### `injectGlobal(rules, options?): { dispose: () => void }`
71
77
 
72
78
  Injects global styles that don't reserve tasty class names.
@@ -115,11 +121,13 @@ dispose();
115
121
 
116
122
  ### `useRawCSS(css, options?)` or `useRawCSS(factory, deps, options?)`
117
123
 
118
- React hook for injecting raw CSS. Uses `useInsertionEffect` for proper timing and cleanup.
124
+ Inject raw CSS without parsing. Hook-free — works in client components, SSR, and React Server Components.
119
125
 
120
126
  Supports two overloads:
121
- - **Static CSS**: `useRawCSS(cssString, options?)`
122
- - **Factory function**: `useRawCSS(() => cssString, deps, options?)` - re-evaluates when deps change (like `useMemo`)
127
+ - **Static CSS**: `useRawCSS(cssString, options?)` — content-based deduplication
128
+ - **Factory function**: `useRawCSS(() => cssString, deps, options?)` — factory called on every invocation, dedup handled internally
129
+
130
+ Use the `id` option for update tracking — when the CSS changes for the same id, the previous injection is replaced:
123
131
 
124
132
  ```tsx
125
133
  import { useRawCSS } from '@tenphi/tasty';
@@ -132,7 +140,7 @@ function GlobalReset() {
132
140
  return null;
133
141
  }
134
142
 
135
- // Dynamic CSS with factory function (like useMemo)
143
+ // Dynamic CSS with factory function and update tracking
136
144
  function ThemeStyles({ theme }: { theme: 'dark' | 'light' }) {
137
145
  useRawCSS(() => `
138
146
  body {
@@ -140,7 +148,7 @@ function ThemeStyles({ theme }: { theme: 'dark' | 'light' }) {
140
148
  background: ${theme === 'dark' ? '#000' : '#fff'};
141
149
  color: ${theme === 'dark' ? '#fff' : '#000'};
142
150
  }
143
- `, [theme]);
151
+ `, [theme], { id: 'theme-body' });
144
152
 
145
153
  return null;
146
154
  }
@@ -207,13 +215,12 @@ import { configure } from '@tenphi/tasty';
207
215
  configure({
208
216
  devMode: true, // Enable development features (auto-detected)
209
217
  maxRulesPerSheet: 8192, // Cap rules per stylesheet (default: 8192)
210
- unusedStylesThreshold: 500, // Trigger cleanup threshold (CSS rules only)
211
- bulkCleanupDelay: 5000, // Cleanup delay (ms) - ignored if idleCleanup is true
212
- idleCleanup: true, // Use requestIdleCallback for cleanup
213
- bulkCleanupBatchRatio: 0.5, // Clean up oldest 50% per batch
214
- unusedStylesMinAgeMs: 10000, // Minimum age before cleanup (ms)
215
218
  forceTextInjection: false, // Force textContent insertion (auto-detected for tests)
216
219
  nonce: 'csp-nonce', // CSP nonce for security
220
+ gc: { // Garbage collection for unused styles
221
+ touchInterval: 1000, // Touch events between GC cycles (default: 1000)
222
+ capacity: 1000, // Max unused styles to retain (default: 1000)
223
+ },
217
224
  states: { // Global predefined states for advanced state mapping
218
225
  '@mobile': '@media(w < 768px)',
219
226
  '@dark': '@root(schema=dark)',
@@ -223,13 +230,75 @@ configure({
223
230
 
224
231
  **Auto-Detection Features:**
225
232
  - `devMode`: Automatically enabled in development environments (detected via `isDevEnv()`)
226
- - `forceTextInjection`: Automatically enabled in test environments (Jest, Vitest, Mocha, jsdom)
233
+ - `forceTextInjection`: Automatically enabled in test environments (Jest, Vitest, Mocha, happy-dom, jsdom)
234
+
235
+ **Injection Modes:**
236
+
237
+ Each sheet picks its write mode once, when it is created, and keeps it for its
238
+ lifetime:
239
+
240
+ | Mode | How rules are written | How rules are removed |
241
+ |---|---|---|
242
+ | CSSOM (default) | `styleSheet.insertRule(rule, index)` | `styleSheet.deleteRule(index)` |
243
+ | Text (`forceTextInjection`, or when `styleElement.sheet` is unavailable) | appended to `<style>.textContent` | rule texts are tracked per sheet and the element's text is rebuilt without them |
244
+ | Adopted (ShadowRoot with constructable sheets) | `insertRule` on the constructable sheet | `deleteRule` on the constructable sheet |
245
+
246
+ Text mode cannot edit a single rule in place the way CSSOM can, so the sheet
247
+ keeps the inserted rule texts in index order and rewrites the element on delete.
248
+ Dispose, ref-counted cleanup and GC therefore behave identically in every mode.
227
249
 
228
250
  **Configuration Notes:**
229
251
  - Most options have sensible defaults and auto-detection
230
252
  - `configure()` is optional - the injector works with defaults
231
253
  - **Configuration is locked after styles are generated** - calling `configure()` after first render will emit a warning and be ignored
232
- - `unusedStylesMinAgeMs`: Minimum time (ms) a style must remain unused before being eligible for cleanup. Helps prevent removal of styles that might be quickly reactivated.
254
+ - `gc.touchInterval`: Number of renders between sweeps. When the counter reaches this value, a sweep is scheduled via `requestIdleCallback`; without idle callbacks nothing is collected automatically.
255
+ - `gc.grace`: How long a class is left alone after collection first notices nothing is carrying it, in milliseconds (default `10000`).
256
+
257
+ **What a sweep does.** Everything the injector holds falls into one of five bands, and only the last is ever deleted:
258
+
259
+ | Band | | Deleted |
260
+ |---|---|---|
261
+ | 1 | **Rendered** — some element carries the class right now | never |
262
+ | 2 | **Not ours** — queued for a batched write, pre-allocated, or server-rendered | never |
263
+ | 3 | **Hot** — nothing carries it, but that was noticed less than `grace` ago | never |
264
+ | 4 | **Cached** — cold, but within `capacity` when ordered by when it went cold | never |
265
+ | 5 | Everything else | on every sweep |
266
+
267
+ `gc({ force: true })` and `cleanup()` take bands 4 and 5 together, ignoring capacity. Band 3 is spared even then: an explicit cleanup is still no reason to take rules from a render that has not committed yet.
268
+
269
+ > **Why band 3 exists.** Rendering is not commit-aware: a render can resolve a class and commit it a little later, and in between nothing on the page carries it. From outside React that is indistinguishable from a class that is finished, so collection does not try to tell them apart — it leaves alone anything only just noticed to be cold. A render would have to stay pending for the whole window to lose its rules, and it gets them back on its next render.
270
+ >
271
+ > The clock starts when a sweep *notices*, not when the element actually left — nothing observes that moment, so starting it at the sighting is what gives every class the same full window however long ago it went.
272
+ >
273
+ > This is also why collection costs nothing to run: the timestamps are written by the sweep's own DOM scan, so rendering tracks nothing per class.
274
+ - `gc.capacity`: Maximum number of unused styles (not in the DOM, not pinned) to retain. When exceeded, the least recently used are evicted first. Rendered and pinned styles don't count against this limit.
275
+
276
+ ---
277
+
278
+ ### At-rule injection
279
+
280
+ Beyond ordinary rules, the injector has a dedicated entry point per at-rule. All of them deduplicate by name and are **permanent** — there is no ref-counting or dispose, because a rule that other CSS references by name cannot be safely removed while any of it might still apply.
281
+
282
+ | Function | Injects | Notes |
283
+ |---|---|---|
284
+ | `keyframes(steps, nameOrOptions?)` | `@keyframes` | The exception: ref-counted and disposed immediately when unused. Returns the injected name, which may differ from the requested one. |
285
+ | `property(name, options?)` | `@property` | `name` accepts `$name`, `#name`, or `--name`. Usually unnecessary — types are inferred automatically unless `configure({ autoPropertyTypes: false })`. |
286
+ | `fontFace(family, descriptors, options?)` | `@font-face` | Deduplicated by family **and** descriptor content, so several faces of one family coexist. |
287
+ | `counterStyle(name, descriptors, options?)` | `@counter-style` | |
288
+ | `func(name, definition, options?)` | `@function` | `name` accepts `$$name`, `$name`, or `--name`. Abbreviated because `function` is a reserved word. |
289
+
290
+ Every one of these takes an optional `root` (a `Document` or `ShadowRoot`).
291
+
292
+ `func()` also accepts `weak: true`, which registers the rule **without** overriding an existing one of the same name. `configure()` uses it for global `@function` definitions so a component-local definition always wins over the global default, regardless of which is injected first.
293
+
294
+ ```ts
295
+ import { counterStyle, fontFace, func, property } from '@tenphi/tasty';
296
+
297
+ property('$card-elevation', { syntax: '<number>', initialValue: '0' });
298
+ fontFace('Inter', { src: 'url(/inter.woff2)', fontWeight: '400 700' });
299
+ counterStyle('dashes', { system: 'cyclic', symbols: '"—"', suffix: ' ' });
300
+ func('$$negative', { args: ['$value'], result: '(-1 * $value)' });
301
+ ```
233
302
 
234
303
  ---
235
304
 
@@ -256,6 +325,17 @@ const styleRule: StyleResult = {
256
325
  };
257
326
  ```
258
327
 
328
+ ### Batched Injection
329
+
330
+ `configure({ batchInjection: true })` queues every sheet write — component
331
+ rules, global rules, raw CSS and at-rules — into one FIFO and applies them
332
+ together, so the document is style-invalidated once per flush instead of once per
333
+ component. `<TastyBatchProvider>` flushes in `useInsertionEffect`, before any
334
+ layout effect, so a queued write can never be observed by a measurement. See
335
+ [Batched injection](configuration.md#batched-injection) for the modes and the
336
+ ordering guarantee. `flushStyles()` applies pending writes on demand; every read
337
+ API here calls it for you.
338
+
259
339
  ### Deduplication & Performance
260
340
 
261
341
  ```typescript
@@ -274,40 +354,65 @@ const button2 = inject([{
274
354
  console.log(button1.className === button2.className); // true
275
355
  ```
276
356
 
277
- ### Reference Counting
357
+ ### Pinning
278
358
 
279
359
  ```typescript
280
- // Multiple components using the same styles
360
+ // Multiple callers using the same styles
281
361
  const comp1 = inject([commonStyle]);
282
362
  const comp2 = inject([commonStyle]);
283
363
  const comp3 = inject([commonStyle]);
284
364
 
285
- // Style is kept alive while any component uses it
286
- comp1.dispose(); // refCount: 3 → 2
287
- comp2.dispose(); // refCount: 2 → 1
288
- comp3.dispose(); // refCount: 1 → 0, eligible for bulk cleanup
365
+ // Style is pinned while any caller holds a handle
366
+ comp1.dispose(); // pins: 3 → 2
367
+ comp2.dispose(); // pins: 2 → 1
368
+ comp3.dispose(); // pins: 1 → 0, now up to the DOM and gc()
289
369
 
290
- // Rule exists but refCount = 0 means unused
291
- // Next inject() with same styles will increment refCount and reuse immediately
370
+ // Unpinned does not mean deleted: the rule stays cached and is reused instantly
371
+ // by the next inject(). gc() decides when it actually goes.
292
372
  ```
293
373
 
294
- ### Smart Cleanup System
374
+ Pinning covers callers that hold a handle. Styles that come from rendering are
375
+ never pinned — see `{ pin: false }` under
376
+ [`inject()`](#injectrules-options-injectresult) — so what keeps them alive is
377
+ being in the DOM, and nothing else.
378
+
379
+ ### Garbage Collection
295
380
 
296
381
  ```typescript
297
- import { configure } from '@tenphi/tasty';
382
+ import { configure, gc } from '@tenphi/tasty';
298
383
 
299
- // CSS rules: Not immediately deleted, marked for bulk cleanup (refCount = 0)
300
384
  // Keyframes: Disposed immediately when refCount = 0 (safer for global scope)
385
+ // CSS rules: Tracked by touch count and cleaned up via gc()
386
+ //
387
+ // A CSS rule is collectible when no element carries its class AND nobody
388
+ // pinned it with inject().
301
389
 
302
390
  configure({
303
- unusedStylesThreshold: 100, // Cleanup when 100+ unused CSS rules
304
- bulkCleanupBatchRatio: 0.3, // Remove oldest 30% each time
391
+ gc: {
392
+ touchInterval: 1000, // Schedule GC every 1000 touches
393
+ capacity: 1000, // Max unused styles to retain
394
+ },
305
395
  });
306
396
 
397
+ // Manual GC (synchronous, returns number of swept styles):
398
+ gc();
399
+
400
+ // Force-remove ALL unused styles (e.g. on route change or test teardown):
401
+ gc({ force: true });
402
+
403
+ // cleanup() is the same thing:
404
+ cleanup();
405
+
406
+ // Every `touchInterval` renders, a sweep is scheduled in idle time. It scans
407
+ // the DOM for the classes actually on the page and sorts everything the
408
+ // injector holds into five bands — only the last one is deleted. See below.
409
+
307
410
  // Benefits:
308
- // - CSS rules: Batch cleanup prevents DOM manipulation overhead
411
+ // - Activity-proportional: busy apps trigger GC more often
412
+ // - DOM-safe: styles currently in the DOM are never evicted
413
+ // - Oldest-first: least recently used styles are evicted first
309
414
  // - Keyframes: Immediate cleanup prevents global namespace pollution
310
- // - Unused styles can be instantly reactivated (just increment refCount)
415
+ // - Unused styles stay cached until evicted, so re-rendering them is a cache hit
311
416
  ```
312
417
 
313
418
  ### Shadow DOM Support
@@ -335,14 +440,14 @@ const shadowAnimation = keyframes({
335
440
  ### Server-Side Rendering
336
441
 
337
442
  ```typescript
338
- import { getCssText, getCssTextForNode } from '@tenphi/tasty';
443
+ import { getCSSText, getCSSTextForNode } from '@tenphi/tasty';
339
444
 
340
445
  // Extract all CSS for SSR
341
- const cssText = getCssText();
446
+ const cssText = getCSSText();
342
447
 
343
448
  // Extract CSS for specific DOM subtree (like jest-styled-components)
344
449
  const container = render(<MyComponent />);
345
- const componentCSS = getCssTextForNode(container);
450
+ const componentCSS = getCSSTextForNode(container);
346
451
  ```
347
452
 
348
453
  ### Test Environment Detection
@@ -351,7 +456,7 @@ const componentCSS = getCssTextForNode(container);
351
456
  // Automatically detected test environments:
352
457
  // - NODE_ENV === 'test'
353
458
  // - Jest globals (jest, describe, it, expect)
354
- // - jsdom user agent
459
+ // - jsdom / HappyDOM user agent
355
460
  // - Vitest globals (vitest)
356
461
  // - Mocha globals (mocha)
357
462
 
@@ -402,11 +507,11 @@ const metrics = injector.instance.getMetrics();
402
507
  console.log({
403
508
  cacheHits: metrics.hits, // Successful cache hits
404
509
  cacheMisses: metrics.misses, // New styles injected
405
- unusedHits: metrics.unusedHits, // Current unused styles (calculated on demand)
510
+ unusedHits: metrics.unusedHits, // Styles currently eligible for eviction (scans the DOM)
406
511
  bulkCleanups: metrics.bulkCleanups, // Number of bulk cleanup operations
407
512
  stylesCleanedUp: metrics.stylesCleanedUp, // Total styles removed in bulk cleanups
408
513
  totalInsertions: metrics.totalInsertions, // Lifetime insertions
409
- totalUnused: metrics.totalUnused, // Total styles marked as unused (refCount = 0)
514
+ totalUnused: metrics.totalUnused, // Times a pinned style lost its last pin
410
515
  startTime: metrics.startTime, // Metrics collection start timestamp
411
516
  cleanupHistory: metrics.cleanupHistory, // Detailed cleanup operation history
412
517
  });
@@ -450,10 +555,10 @@ metrics.cleanupHistory.forEach(cleanup => {
450
555
 
451
556
  ```typescript
452
557
  // ✅ Reuse styles - identical CSS gets deduplicated
453
- const buttonBase = { padding: '8px 16px', borderRadius: '4px' };
558
+ const buttonBase = 'padding: 8px 16px; border-radius: 4px;';
454
559
 
455
560
  // ✅ Avoid frequent disposal and re-injection
456
- // Let the reference counting system handle cleanup
561
+ // Let the injector handle cleanup
457
562
 
458
563
  // ✅ Use bulk operations for global styles
459
564
  injectGlobal([
@@ -462,13 +567,14 @@ injectGlobal([
462
567
  { selector: '.container', declarations: 'max-width: 1200px;' }
463
568
  ]);
464
569
 
465
- // ✅ Configure appropriate thresholds for your app (BEFORE first render!)
570
+ // ✅ Configure GC for your app (BEFORE first render!)
466
571
  import { configure } from '@tenphi/tasty';
467
572
 
468
573
  configure({
469
- unusedStylesThreshold: 500, // Default threshold (adjust based on app size)
470
- bulkCleanupBatchRatio: 0.5, // Default: clean oldest 50% per batch
471
- unusedStylesMinAgeMs: 10000, // Wait 10s before cleanup eligibility
574
+ gc: {
575
+ touchInterval: 1000, // Schedule GC every 1000 style touches
576
+ capacity: 1000, // Max unused styles to retain
577
+ },
472
578
  });
473
579
  ```
474
580
 
@@ -478,13 +584,13 @@ configure({
478
584
  // The injector automatically manages memory through:
479
585
 
480
586
  // 1. Hash-based deduplication - same CSS = same className
481
- // 2. Reference counting - styles stay alive while in use (refCount > 0)
482
- // 3. Immediate keyframes cleanup - disposed instantly when refCount = 0
483
- // 4. Batch CSS cleanup - unused CSS rules (refCount = 0) cleaned in batches
484
- // 5. Non-stacking cleanups - prevents timeout accumulation
587
+ // 2. DOM-driven lifetime - a rendered class is never evicted
588
+ // 3. Pinning - inject() callers hold their classes until they dispose
589
+ // 4. Immediate keyframes cleanup - disposed instantly when refCount = 0
590
+ // 5. Touch-count GC - unused CSS rules are evicted oldest-first when over capacity
485
591
 
486
592
  // Manual cleanup is rarely needed but available:
487
- cleanup(); // Force immediate cleanup of all unused CSS rules (refCount = 0)
593
+ cleanup(); // Remove every rule that is neither rendered nor pinned
488
594
  destroy(); // Nuclear option: remove all stylesheets and reset
489
595
  ```
490
596
 
@@ -506,12 +612,12 @@ const StyledButton = tasty({
506
612
 
507
613
  // Internally uses the injector:
508
614
  // 1. Styles are parsed into StyleResult objects
509
- // 2. inject() is called with the parsed results
615
+ // 2. inject() is called with the parsed results, unpinned
510
616
  // 3. Component gets the returned className
511
- // 4. dispose() is called when component unmounts
617
+ // 4. gc() reclaims the class once no element carries it
512
618
  ```
513
619
 
514
- For most development, you'll use the [Runtime API](./runtime.md) rather than the injector directly. The injector provides the high-performance foundation that makes Tasty's declarative styling possible.
620
+ For most development, you'll use the [React API](./react-api.md) rather than the injector directly. The injector provides the high-performance foundation that makes Tasty's declarative styling possible.
515
621
 
516
622
  ---
517
623
 
@@ -525,4 +631,4 @@ Direct injector usage is recommended for:
525
631
  - **Performance-critical scenarios** where you need direct control
526
632
  - **Testing utilities** that need to inject or extract CSS
527
633
 
528
- For regular component styling, prefer the [`tasty()` API](./runtime.md) which provides a more developer-friendly interface.
634
+ For regular component styling, prefer the [`tasty()` API](./react-api.md) which provides a more developer-friendly interface.