@tenphi/tasty 0.0.0-snapshot.fafcbfe → 0.0.0-snapshot.fdb16ea

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 (309) hide show
  1. package/README.md +397 -185
  2. package/dist/async-storage-DKK-wTD4.js +44 -0
  3. package/dist/async-storage-DKK-wTD4.js.map +1 -0
  4. package/dist/babel-CMYf4JZ2.d.ts +83 -0
  5. package/dist/collector-Ctn5s74s.d.ts +145 -0
  6. package/dist/collector-DMSR6ANK.js +304 -0
  7. package/dist/collector-DMSR6ANK.js.map +1 -0
  8. package/dist/config-B23gZrfP.js +12317 -0
  9. package/dist/config-B23gZrfP.js.map +1 -0
  10. package/dist/config-CZx8_DCu.d.ts +1296 -0
  11. package/dist/context-CA8YKeMn.js +24 -0
  12. package/dist/context-CA8YKeMn.js.map +1 -0
  13. package/dist/core/index.d.ts +5 -33
  14. package/dist/core/index.js +6 -26
  15. package/dist/core-B1tyn69d.js +1573 -0
  16. package/dist/core-B1tyn69d.js.map +1 -0
  17. package/dist/css-writer-CvAxkR6S.js +390 -0
  18. package/dist/css-writer-CvAxkR6S.js.map +1 -0
  19. package/dist/format-global-rules-DklyaXv-.js +22 -0
  20. package/dist/format-global-rules-DklyaXv-.js.map +1 -0
  21. package/dist/format-rules-BCTWVvcS.js +130 -0
  22. package/dist/format-rules-BCTWVvcS.js.map +1 -0
  23. package/dist/hydrate-BCPvkJj3.js +37 -0
  24. package/dist/hydrate-BCPvkJj3.js.map +1 -0
  25. package/dist/index-B3o0aCYV.d.ts +1538 -0
  26. package/dist/index-CsvB0EXZ.d.ts +1909 -0
  27. package/dist/index.d.ts +5 -40
  28. package/dist/index.js +921 -32
  29. package/dist/index.js.map +1 -0
  30. package/dist/keyframes-Dxdx3KOo.js +493 -0
  31. package/dist/keyframes-Dxdx3KOo.js.map +1 -0
  32. package/dist/{utils/merge-styles.js → merge-styles-C-uwpoNW.js} +4 -6
  33. package/dist/merge-styles-C-uwpoNW.js.map +1 -0
  34. package/dist/{utils/merge-styles.d.ts → merge-styles-DJTSfz_M.d.ts} +3 -3
  35. package/dist/{utils/resolve-recipes.js → resolve-recipes-BuOvSEa2.js} +5 -8
  36. package/dist/resolve-recipes-BuOvSEa2.js.map +1 -0
  37. package/dist/ssr/astro-client.d.ts +1 -0
  38. package/dist/ssr/astro-client.js +19 -0
  39. package/dist/ssr/astro-client.js.map +1 -0
  40. package/dist/ssr/astro-middleware-static.d.ts +16 -0
  41. package/dist/ssr/astro-middleware-static.js +18 -0
  42. package/dist/ssr/astro-middleware-static.js.map +1 -0
  43. package/dist/ssr/astro-middleware.d.ts +17 -0
  44. package/dist/ssr/astro-middleware.js +19 -0
  45. package/dist/ssr/astro-middleware.js.map +1 -0
  46. package/dist/ssr/astro.d.ts +93 -0
  47. package/dist/ssr/astro.js +157 -0
  48. package/dist/ssr/astro.js.map +1 -0
  49. package/dist/ssr/index.d.ts +37 -0
  50. package/dist/ssr/index.js +10 -0
  51. package/dist/ssr/index.js.map +1 -0
  52. package/dist/ssr/next.d.ts +45 -0
  53. package/dist/ssr/next.js +74 -0
  54. package/dist/ssr/next.js.map +1 -0
  55. package/dist/static/index.d.ts +91 -5
  56. package/dist/static/index.js +49 -4
  57. package/dist/static/index.js.map +1 -0
  58. package/dist/static/inject.d.ts +5 -0
  59. package/dist/static/inject.js +17 -0
  60. package/dist/static/inject.js.map +1 -0
  61. package/dist/zero/babel.d.ts +2 -108
  62. package/dist/zero/babel.js +235 -39
  63. package/dist/zero/babel.js.map +1 -1
  64. package/dist/zero/index.d.ts +81 -3
  65. package/dist/zero/index.js +2 -4
  66. package/dist/zero/next.d.ts +56 -30
  67. package/dist/zero/next.js +106 -41
  68. package/dist/zero/next.js.map +1 -1
  69. package/docs/README.md +39 -0
  70. package/docs/adoption.md +323 -0
  71. package/docs/ai-agents.md +224 -0
  72. package/docs/comparison.md +448 -0
  73. package/docs/configuration.md +823 -0
  74. package/docs/debug.md +322 -0
  75. package/docs/design-system.md +455 -0
  76. package/docs/dsl.md +1021 -0
  77. package/docs/getting-started.md +238 -0
  78. package/docs/injector.md +614 -0
  79. package/docs/methodology.md +648 -0
  80. package/docs/migration-v3.md +285 -0
  81. package/docs/pipeline.md +741 -0
  82. package/docs/plugins.md +353 -0
  83. package/docs/react-api.md +681 -0
  84. package/docs/runtime-benchmarks.md +216 -0
  85. package/docs/ssr.md +451 -0
  86. package/docs/styles.md +645 -0
  87. package/docs/tasty-static.md +582 -0
  88. package/package.json +149 -40
  89. package/tasty.config.ts +6 -0
  90. package/dist/_virtual/_rolldown/runtime.js +0 -8
  91. package/dist/chunks/cacheKey.js +0 -70
  92. package/dist/chunks/cacheKey.js.map +0 -1
  93. package/dist/chunks/definitions.d.ts +0 -37
  94. package/dist/chunks/definitions.js +0 -260
  95. package/dist/chunks/definitions.js.map +0 -1
  96. package/dist/chunks/renderChunk.js +0 -61
  97. package/dist/chunks/renderChunk.js.map +0 -1
  98. package/dist/config.d.ts +0 -280
  99. package/dist/config.js +0 -403
  100. package/dist/config.js.map +0 -1
  101. package/dist/debug.d.ts +0 -204
  102. package/dist/debug.js +0 -733
  103. package/dist/debug.js.map +0 -1
  104. package/dist/hooks/useGlobalStyles.d.ts +0 -27
  105. package/dist/hooks/useGlobalStyles.js +0 -56
  106. package/dist/hooks/useGlobalStyles.js.map +0 -1
  107. package/dist/hooks/useKeyframes.d.ts +0 -56
  108. package/dist/hooks/useKeyframes.js +0 -54
  109. package/dist/hooks/useKeyframes.js.map +0 -1
  110. package/dist/hooks/useProperty.d.ts +0 -79
  111. package/dist/hooks/useProperty.js +0 -91
  112. package/dist/hooks/useProperty.js.map +0 -1
  113. package/dist/hooks/useRawCSS.d.ts +0 -53
  114. package/dist/hooks/useRawCSS.js +0 -28
  115. package/dist/hooks/useRawCSS.js.map +0 -1
  116. package/dist/hooks/useStyles.d.ts +0 -40
  117. package/dist/hooks/useStyles.js +0 -169
  118. package/dist/hooks/useStyles.js.map +0 -1
  119. package/dist/injector/index.d.ts +0 -157
  120. package/dist/injector/index.js +0 -154
  121. package/dist/injector/index.js.map +0 -1
  122. package/dist/injector/injector.d.ts +0 -139
  123. package/dist/injector/injector.js +0 -404
  124. package/dist/injector/injector.js.map +0 -1
  125. package/dist/injector/sheet-manager.d.ts +0 -127
  126. package/dist/injector/sheet-manager.js +0 -714
  127. package/dist/injector/sheet-manager.js.map +0 -1
  128. package/dist/injector/types.d.ts +0 -135
  129. package/dist/keyframes/index.js +0 -206
  130. package/dist/keyframes/index.js.map +0 -1
  131. package/dist/parser/classify.js +0 -319
  132. package/dist/parser/classify.js.map +0 -1
  133. package/dist/parser/const.js +0 -33
  134. package/dist/parser/const.js.map +0 -1
  135. package/dist/parser/lru.js +0 -109
  136. package/dist/parser/lru.js.map +0 -1
  137. package/dist/parser/parser.d.ts +0 -25
  138. package/dist/parser/parser.js +0 -116
  139. package/dist/parser/parser.js.map +0 -1
  140. package/dist/parser/tokenizer.js +0 -69
  141. package/dist/parser/tokenizer.js.map +0 -1
  142. package/dist/parser/types.d.ts +0 -51
  143. package/dist/parser/types.js +0 -46
  144. package/dist/parser/types.js.map +0 -1
  145. package/dist/pipeline/conditions.d.ts +0 -134
  146. package/dist/pipeline/conditions.js +0 -400
  147. package/dist/pipeline/conditions.js.map +0 -1
  148. package/dist/pipeline/exclusive.js +0 -231
  149. package/dist/pipeline/exclusive.js.map +0 -1
  150. package/dist/pipeline/index.d.ts +0 -53
  151. package/dist/pipeline/index.js +0 -641
  152. package/dist/pipeline/index.js.map +0 -1
  153. package/dist/pipeline/materialize.js +0 -908
  154. package/dist/pipeline/materialize.js.map +0 -1
  155. package/dist/pipeline/parseStateKey.d.ts +0 -15
  156. package/dist/pipeline/parseStateKey.js +0 -430
  157. package/dist/pipeline/parseStateKey.js.map +0 -1
  158. package/dist/pipeline/simplify.js +0 -557
  159. package/dist/pipeline/simplify.js.map +0 -1
  160. package/dist/plugins/okhsl-plugin.d.ts +0 -35
  161. package/dist/plugins/okhsl-plugin.js +0 -371
  162. package/dist/plugins/okhsl-plugin.js.map +0 -1
  163. package/dist/plugins/types.d.ts +0 -69
  164. package/dist/properties/index.js +0 -158
  165. package/dist/properties/index.js.map +0 -1
  166. package/dist/states/index.d.ts +0 -49
  167. package/dist/states/index.js +0 -416
  168. package/dist/states/index.js.map +0 -1
  169. package/dist/static/tastyStatic.d.ts +0 -46
  170. package/dist/static/tastyStatic.js +0 -31
  171. package/dist/static/tastyStatic.js.map +0 -1
  172. package/dist/static/types.d.ts +0 -49
  173. package/dist/static/types.js +0 -24
  174. package/dist/static/types.js.map +0 -1
  175. package/dist/styles/align.d.ts +0 -15
  176. package/dist/styles/align.js +0 -14
  177. package/dist/styles/align.js.map +0 -1
  178. package/dist/styles/border.d.ts +0 -25
  179. package/dist/styles/border.js +0 -114
  180. package/dist/styles/border.js.map +0 -1
  181. package/dist/styles/color.d.ts +0 -14
  182. package/dist/styles/color.js +0 -23
  183. package/dist/styles/color.js.map +0 -1
  184. package/dist/styles/createStyle.js +0 -77
  185. package/dist/styles/createStyle.js.map +0 -1
  186. package/dist/styles/dimension.js +0 -97
  187. package/dist/styles/dimension.js.map +0 -1
  188. package/dist/styles/display.d.ts +0 -37
  189. package/dist/styles/display.js +0 -67
  190. package/dist/styles/display.js.map +0 -1
  191. package/dist/styles/fade.d.ts +0 -15
  192. package/dist/styles/fade.js +0 -58
  193. package/dist/styles/fade.js.map +0 -1
  194. package/dist/styles/fill.d.ts +0 -44
  195. package/dist/styles/fill.js +0 -51
  196. package/dist/styles/fill.js.map +0 -1
  197. package/dist/styles/flow.d.ts +0 -16
  198. package/dist/styles/flow.js +0 -12
  199. package/dist/styles/flow.js.map +0 -1
  200. package/dist/styles/gap.d.ts +0 -31
  201. package/dist/styles/gap.js +0 -37
  202. package/dist/styles/gap.js.map +0 -1
  203. package/dist/styles/height.d.ts +0 -17
  204. package/dist/styles/height.js +0 -20
  205. package/dist/styles/height.js.map +0 -1
  206. package/dist/styles/index.d.ts +0 -2
  207. package/dist/styles/index.js +0 -9
  208. package/dist/styles/index.js.map +0 -1
  209. package/dist/styles/inset.d.ts +0 -52
  210. package/dist/styles/inset.js +0 -150
  211. package/dist/styles/inset.js.map +0 -1
  212. package/dist/styles/justify.d.ts +0 -15
  213. package/dist/styles/justify.js +0 -14
  214. package/dist/styles/justify.js.map +0 -1
  215. package/dist/styles/list.d.ts +0 -16
  216. package/dist/styles/list.js +0 -98
  217. package/dist/styles/list.js.map +0 -1
  218. package/dist/styles/margin.d.ts +0 -24
  219. package/dist/styles/margin.js +0 -104
  220. package/dist/styles/margin.js.map +0 -1
  221. package/dist/styles/outline.d.ts +0 -29
  222. package/dist/styles/outline.js +0 -65
  223. package/dist/styles/outline.js.map +0 -1
  224. package/dist/styles/padding.d.ts +0 -24
  225. package/dist/styles/padding.js +0 -104
  226. package/dist/styles/padding.js.map +0 -1
  227. package/dist/styles/predefined.d.ts +0 -73
  228. package/dist/styles/predefined.js +0 -241
  229. package/dist/styles/predefined.js.map +0 -1
  230. package/dist/styles/preset.d.ts +0 -47
  231. package/dist/styles/preset.js +0 -126
  232. package/dist/styles/preset.js.map +0 -1
  233. package/dist/styles/radius.d.ts +0 -14
  234. package/dist/styles/radius.js +0 -51
  235. package/dist/styles/radius.js.map +0 -1
  236. package/dist/styles/scrollbar.d.ts +0 -21
  237. package/dist/styles/scrollbar.js +0 -105
  238. package/dist/styles/scrollbar.js.map +0 -1
  239. package/dist/styles/shadow.d.ts +0 -14
  240. package/dist/styles/shadow.js +0 -24
  241. package/dist/styles/shadow.js.map +0 -1
  242. package/dist/styles/styledScrollbar.d.ts +0 -47
  243. package/dist/styles/styledScrollbar.js +0 -38
  244. package/dist/styles/styledScrollbar.js.map +0 -1
  245. package/dist/styles/transition.d.ts +0 -14
  246. package/dist/styles/transition.js +0 -158
  247. package/dist/styles/transition.js.map +0 -1
  248. package/dist/styles/types.d.ts +0 -498
  249. package/dist/styles/width.d.ts +0 -17
  250. package/dist/styles/width.js +0 -20
  251. package/dist/styles/width.js.map +0 -1
  252. package/dist/tasty.d.ts +0 -982
  253. package/dist/tasty.js +0 -191
  254. package/dist/tasty.js.map +0 -1
  255. package/dist/tokens/typography.d.ts +0 -19
  256. package/dist/tokens/typography.js +0 -237
  257. package/dist/tokens/typography.js.map +0 -1
  258. package/dist/types.d.ts +0 -184
  259. package/dist/utils/cache-wrapper.js +0 -26
  260. package/dist/utils/cache-wrapper.js.map +0 -1
  261. package/dist/utils/case-converter.js +0 -8
  262. package/dist/utils/case-converter.js.map +0 -1
  263. package/dist/utils/colors.d.ts +0 -5
  264. package/dist/utils/colors.js +0 -9
  265. package/dist/utils/colors.js.map +0 -1
  266. package/dist/utils/css-types.d.ts +0 -7
  267. package/dist/utils/dotize.d.ts +0 -26
  268. package/dist/utils/dotize.js +0 -122
  269. package/dist/utils/dotize.js.map +0 -1
  270. package/dist/utils/filter-base-props.d.ts +0 -15
  271. package/dist/utils/filter-base-props.js +0 -45
  272. package/dist/utils/filter-base-props.js.map +0 -1
  273. package/dist/utils/get-display-name.d.ts +0 -7
  274. package/dist/utils/get-display-name.js +0 -10
  275. package/dist/utils/get-display-name.js.map +0 -1
  276. package/dist/utils/hsl-to-rgb.js +0 -38
  277. package/dist/utils/hsl-to-rgb.js.map +0 -1
  278. package/dist/utils/is-dev-env.js +0 -19
  279. package/dist/utils/is-dev-env.js.map +0 -1
  280. package/dist/utils/is-valid-element-type.js +0 -15
  281. package/dist/utils/is-valid-element-type.js.map +0 -1
  282. package/dist/utils/merge-styles.js.map +0 -1
  283. package/dist/utils/mod-attrs.d.ts +0 -8
  284. package/dist/utils/mod-attrs.js +0 -21
  285. package/dist/utils/mod-attrs.js.map +0 -1
  286. package/dist/utils/okhsl-to-rgb.js +0 -296
  287. package/dist/utils/okhsl-to-rgb.js.map +0 -1
  288. package/dist/utils/process-tokens.d.ts +0 -31
  289. package/dist/utils/process-tokens.js +0 -171
  290. package/dist/utils/process-tokens.js.map +0 -1
  291. package/dist/utils/resolve-recipes.d.ts +0 -17
  292. package/dist/utils/resolve-recipes.js.map +0 -1
  293. package/dist/utils/string.js +0 -8
  294. package/dist/utils/string.js.map +0 -1
  295. package/dist/utils/styles.d.ts +0 -183
  296. package/dist/utils/styles.js +0 -585
  297. package/dist/utils/styles.js.map +0 -1
  298. package/dist/utils/typography.d.ts +0 -36
  299. package/dist/utils/typography.js +0 -53
  300. package/dist/utils/typography.js.map +0 -1
  301. package/dist/utils/warnings.d.ts +0 -16
  302. package/dist/utils/warnings.js +0 -16
  303. package/dist/utils/warnings.js.map +0 -1
  304. package/dist/zero/css-writer.d.ts +0 -45
  305. package/dist/zero/css-writer.js +0 -74
  306. package/dist/zero/css-writer.js.map +0 -1
  307. package/dist/zero/extractor.d.ts +0 -24
  308. package/dist/zero/extractor.js +0 -150
  309. package/dist/zero/extractor.js.map +0 -1
@@ -0,0 +1,216 @@
1
+ # Runtime Benchmarks
2
+
3
+ Tasty keeps its performance claims in reproducible benchmarks rather than
4
+ combining unlike measurements into one score. The repository measures four
5
+ different costs:
6
+
7
+ 1. Style parsing and generation in Node.
8
+ 2. The React overhead of an empty `tasty({})` wrapper.
9
+ 3. Cold browser generation and injection compared with equivalent CSS that is
10
+ already on the page.
11
+ 4. A representative React update that combines wrapper, cache, generation,
12
+ injection, and style-resolution work.
13
+
14
+ These are focused microbenchmarks, not page-load or interaction scores. Run
15
+ them several times on an otherwise idle machine and use a production profile
16
+ to decide whether any cost matters in an application.
17
+
18
+ ## Reproducing the Results
19
+
20
+ ```bash
21
+ pnpm bench
22
+ pnpm bench:overhead
23
+ pnpm bench:injection
24
+ pnpm bench:tree
25
+ ```
26
+
27
+ `pnpm bench` runs the core pipeline benchmarks in Node. The other three commands
28
+ use production code paths in headless Chromium. The first checkout may require
29
+ `pnpm test:setup` to download Chromium.
30
+
31
+ Run the Node and browser suites separately so they do not compete for CPU. The
32
+ browser timer has 0.1 ms resolution, so both browser benchmarks perform many
33
+ matched operations per sample and divide the absolute difference by the number
34
+ of elements or rules. Machine load, browser versions, and CPU power will move
35
+ the results.
36
+
37
+ ## Core Style Pipeline
38
+
39
+ The following numbers are single-call throughput measured on an Apple M1 Max
40
+ with Node 22:
41
+
42
+ | Operation | ops/sec | Latency (mean) |
43
+ | ----------------------------------------------------------- | -------------------: | -------------: |
44
+ | `renderStyles` — 5 flat properties (cold) | ~60,000 | ~17 us |
45
+ | `renderStyles` — state map with media/hover/modifier (cold) | ~18,500 | ~54 us |
46
+ | `renderStyles` — same styles (cached) | ~5,800,000 | ~0.17 us |
47
+ | `parseStateKey` — simple key like `:hover` (cold) | ~790,000 | ~1.3 us |
48
+ | `parseStateKey` — complex OR/AND/NOT key (cold) | ~140,000 | ~7 us |
49
+ | `parseStateKey` — any key (cached) | ~3,400,000–8,300,000 | ~0.1–0.3 us |
50
+ | `parseStyle` — value tokens like `2x 4x` (cold) | ~344,000 | ~2.9 us |
51
+ | `parseStyle` — color tokens (cold) | ~567,000 | ~1.8 us |
52
+ | `parseStyle` — any value (cached) | ~15,250,000 | ~0.07 us |
53
+
54
+ “Cold” cases use unique inputs to bypass the relevant caches. Cached cases
55
+ reuse one input and measure the LRU hot path. Expect roughly ±10% between runs.
56
+ These benchmarks do not include React, DOM work, stylesheet injection, style
57
+ resolution, layout, or paint.
58
+
59
+ The benchmark sources are colocated with the code they exercise:
60
+ [`pipeline.bench.ts`](../src/pipeline/pipeline.bench.ts),
61
+ [`parseStateKey.bench.ts`](../src/pipeline/parseStateKey.bench.ts), and the
62
+ parser benchmark files under [`src/parser`](../src/parser).
63
+
64
+ ## Empty Wrapper Overhead
65
+
66
+ Skipping the style pipeline does not make a `tasty()` component free. Even
67
+ `tasty({})` is a React component between its parent and the host element. React
68
+ tracks another fiber, and Tasty still processes and forwards the element's
69
+ props.
70
+
71
+ [`tasty-overhead.bench.tsx`](../src/tasty-overhead.bench.tsx) compares 10,000
72
+ raw `<div className>` siblings with 10,000 instances of one module-scoped
73
+ `tasty({})` component. Both receive the same props, and the benchmark fails if
74
+ they do not produce equivalent DOM.
75
+
76
+ The benchmark uses production React in headless Chromium. The factory is
77
+ created and its empty class-name cache is warmed before timing, so factory
78
+ creation, style generation, and injection are excluded. A detached container
79
+ excludes layout, paint, and stylesheet matching. Every commit is wrapped in
80
+ `flushSync`, keeping its synchronous reconciliation and commit inside the
81
+ sample. This does not estimate React's concurrent scheduling latency.
82
+
83
+ On an Apple M3 Pro with React 19.2.4 and Chromium 151, three consecutive runs
84
+ produced these ranges:
85
+
86
+ | Work on 10,000 siblings | Raw elements | `tasty({})` | Extra per wrapped element |
87
+ | ----------------------------------- | -----------: | -----------: | ------------------------: |
88
+ | Mount + remove | 4.5–4.9 ms | 14.0–14.9 ms | 0.95–1.00 us |
89
+ | Rerender, same host props | 1.3–1.4 ms | 10.9–11.4 ms | 0.96–1.00 us |
90
+ | Rerender, change one host attribute | 2.8–3.4 ms | 15.0–15.7 ms | 1.21–1.27 us |
91
+
92
+ The useful result is the raw/Tasty time difference divided by 10,000, not the
93
+ ratio between the two times. The ratio becomes large because the raw baseline
94
+ is tiny. In this synthetic workload, an empty wrapper adds roughly 1 us per
95
+ participating element, or 1.2–1.3 us when React also changes a DOM attribute.
96
+
97
+ This is the floor Tasty consumes when it has no styling job. It is not a
98
+ page-level score. Real trees include application components, effects, layout,
99
+ paint, and usually far fewer simultaneous styled-element updates. The benchmark
100
+ also does not measure retained memory; that requires a matched-tree heap
101
+ snapshot experiment with controlled garbage collection.
102
+
103
+ ## Cold Generation and Injection
104
+
105
+ [`tasty-injection.bench.ts`](../src/tasty-injection.bench.ts) measures the extra
106
+ work when Tasty must generate and inject CSS that an otherwise equivalent page
107
+ already has. It does not compare different stylesheet insertion techniques.
108
+
109
+ The benchmark covers four group sizes with one style-resolution boundary per
110
+ group:
111
+
112
+ - **One new rule:** add one rule, append its element, and immediately read its
113
+ computed style. Each sample performs 50 independent groups for timer
114
+ stability.
115
+ - **10 new rules together:** generate and insert 10 rules, append their 10
116
+ elements, then read every computed color without another write. Each sample
117
+ performs 10 independent groups.
118
+ - **100 or 1,000 new rules together:** perform the same sequence once per
119
+ sample with 100 or 1,000 rules before the resolution boundary.
120
+
121
+ Repeating the smaller groups inside one sample only raises the measurement
122
+ above Chromium's timer resolution. Results are divided by that repetition
123
+ count, so every row below describes one group with one resolution boundary.
124
+
125
+ For every transaction, the existing-CSS control has the equivalent stylesheet
126
+ parsed, adopted, and attached before timing. The runtime root has a Tasty
127
+ stylesheet pre-created with an unrelated sentinel rule, but not the measured
128
+ rules. Both paths perform the same class assignment, DOM commit, and
129
+ computed-style reads. Only the runtime path calls `computeStyles()` and inserts
130
+ the new rules.
131
+
132
+ Preparation and cleanup happen outside the sample timer. Every runtime style
133
+ value is unique within a cycle, the relevant caches are cleared between cycles,
134
+ and a guard verifies that both paths resolve to the same color. React and the
135
+ `tasty()` wrapper are absent so their independently measured costs do not enter
136
+ the result. Pre-creating both stylesheets also excludes one-time sheet creation
137
+ and adoption from the subtraction.
138
+
139
+ On an Apple M3 Pro with Chromium 151, three consecutive runs produced these
140
+ normalized ranges:
141
+
142
+ | New rules before one resolution | Groups per sample | Incremental cost per group | Incremental cost per rule |
143
+ | ------------------------------: | ----------------: | -------------------------: | ------------------------: |
144
+ | 1 | 50 | 0.111–0.114 ms | 111–114 us |
145
+ | 10 | 10 | 0.142–0.159 ms | 14.2–15.9 us |
146
+ | 100 | 1 | 0.90–0.94 ms | 9.0–9.4 us |
147
+ | 1,000 | 1 | 7.58–8.10 ms | 7.6–8.1 us |
148
+
149
+ The curve makes the fixed work visible. Delivering 1,000 rules before one
150
+ resolution boundary cost roughly 67–73 times as much in total as delivering
151
+ one rule and resolving it, not 1,000 times as much. The average incremental
152
+ cost per rule was roughly 14–15 times lower because the group amortized fixed
153
+ transaction work and let the browser resolve all stylesheet writes together.
154
+
155
+ The subtraction is the meaningful result. It includes Tasty's cold style
156
+ generation, cache and injector bookkeeping, rule insertion, and any additional
157
+ style invalidation exposed by that workload's resolution boundary. It does not
158
+ pretend to isolate `insertRule()` from the system that calls it.
159
+
160
+ This is a deliberately cold workload. Reused styles resolve from cache and do
161
+ not inject another rule. Different rule complexity, DOM shape, stylesheet size,
162
+ browser, and hardware will change the number. Because the same group and
163
+ resolution pattern is present in each workload's control, the difference
164
+ answers the narrower delivery question: how much extra work did Tasty perform
165
+ when the same CSS was not already there?
166
+
167
+ ## Representative React Tree
168
+
169
+ [`tasty-tree.bench.tsx`](../src/tasty-tree.bench.tsx) bridges the isolated
170
+ measurements with one production React update. Every path updates 1,000 host
171
+ elements sharing 20 combinations of `color` and `padding`, then reads computed
172
+ styles after the commit:
173
+
174
+ - **CSS already present:** raw elements switch between classes whose rules were
175
+ attached before timing.
176
+ - **Tasty, warm:** the elements switch between two sets of Tasty styles that
177
+ were generated before timing.
178
+ - **Tasty, cold:** every update introduces 20 new combinations shared across
179
+ the 1,000 elements.
180
+
181
+ The Tasty paths use the normal component API and `TastyBatchProvider`. On an
182
+ Apple M3 Pro with production React 19.2.4 and Chromium 151, three consecutive
183
+ runs produced these mean ranges:
184
+
185
+ | Update path | Total time for 1,000 elements |
186
+ | ----------------------------------- | ----------------------------: |
187
+ | CSS already present | 1.64–1.70 ms |
188
+ | Tasty, shared styles already cached | 4.17–4.43 ms |
189
+ | Tasty, 20 new shared styles | 4.62–4.80 ms |
190
+
191
+ In this workload, the warmed Tasty path added 2.52–2.73 ms over raw elements
192
+ with existing CSS. Introducing 20 new shared combinations added another
193
+ 0.38–0.55 ms over the warm Tasty update. This is a synthetic update, not an
194
+ application score: it excludes effects, layout reads, paint, and concurrent
195
+ scheduling, while deliberately making all 1,000 elements participate in every
196
+ commit.
197
+
198
+ ## Reading the Results Together
199
+
200
+ Do not add all three benchmark numbers to estimate an application blindly.
201
+ They describe different paths:
202
+
203
+ - A stable `tasty()` factory can skip the style pipeline on later renders, but
204
+ its React wrapper still participates in reconciliation.
205
+ - A cached style avoids cold parsing and generation and does not insert a new
206
+ rule.
207
+ - A genuinely new style pays generation and injection once, then becomes
208
+ reusable.
209
+ - A tree that shares a small style vocabulary pays the wrapper path per element
210
+ but cold generation only per new combination.
211
+ - Browser style resolution, layout, and paint depend on the actual document and
212
+ need application-level profiling.
213
+
214
+ The practical optimization target is therefore repeated work: keep style input
215
+ stable when possible, reuse generated chunks, and generate CSS at build or
216
+ server time when runtime flexibility is unnecessary.