meno-core 1.0.53 → 1.1.0

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 (415) hide show
  1. package/.claude/settings.local.json +1 -3
  2. package/bin/cli.ts +66 -261
  3. package/build-astro.ts +150 -128
  4. package/dist/bin/cli.js +42 -185
  5. package/dist/bin/cli.js.map +2 -2
  6. package/dist/chunks/chunk-2AR55GYH.js +42 -0
  7. package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
  8. package/dist/chunks/{chunk-JGWFTO6P.js → chunk-2IIQK7T3.js} +2624 -1389
  9. package/dist/chunks/chunk-2IIQK7T3.js.map +7 -0
  10. package/dist/chunks/chunk-4ZRU52J2.js +169 -0
  11. package/dist/chunks/chunk-4ZRU52J2.js.map +7 -0
  12. package/dist/chunks/{chunk-UB44F4Z2.js → chunk-5X4HCN7J.js} +5 -7
  13. package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
  14. package/dist/chunks/{chunk-WQFG7PAH.js → chunk-7CCVOL5M.js} +15 -19
  15. package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
  16. package/dist/chunks/{chunk-YBLHKYFF.js → chunk-AR7LHIFY.js} +8 -2
  17. package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
  18. package/dist/chunks/{chunk-O3NAGJP4.js → chunk-NUP7H7D3.js} +135 -41
  19. package/dist/chunks/chunk-NUP7H7D3.js.map +7 -0
  20. package/dist/chunks/{chunk-X754AHS5.js → chunk-QWTQZHG3.js} +194 -90
  21. package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
  22. package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-XTKNX4FW.js} +216 -87
  23. package/dist/chunks/chunk-XTKNX4FW.js.map +7 -0
  24. package/dist/chunks/{fs-JGINUXGL.js → fs-G3ANWEFP.js} +2 -2
  25. package/dist/lib/client/index.js +1003 -475
  26. package/dist/lib/client/index.js.map +3 -3
  27. package/dist/lib/server/index.js +14393 -6371
  28. package/dist/lib/server/index.js.map +4 -4
  29. package/dist/lib/shared/index.js +342 -101
  30. package/dist/lib/shared/index.js.map +4 -4
  31. package/dist/lib/shared/richtext/index.js +1 -1
  32. package/dist/lib/test-utils/index.js +64 -80
  33. package/dist/lib/test-utils/index.js.map +2 -2
  34. package/entries/client-router.tsx +10 -8
  35. package/lib/client/ClientInitializer.ts +8 -8
  36. package/lib/client/ErrorBoundary.test.tsx +246 -186
  37. package/lib/client/ErrorBoundary.tsx +183 -132
  38. package/lib/client/componentRegistry.test.ts +96 -108
  39. package/lib/client/componentRegistry.ts +1 -3
  40. package/lib/client/contexts/ThemeContext.tsx +3 -2
  41. package/lib/client/core/ComponentBuilder.test.ts +663 -560
  42. package/lib/client/core/ComponentBuilder.ts +427 -155
  43. package/lib/client/core/ComponentRenderer.test.tsx +2 -4
  44. package/lib/client/core/ComponentRenderer.tsx +46 -33
  45. package/lib/client/core/builders/embedBuilder.ts +242 -54
  46. package/lib/client/core/builders/linkBuilder.ts +72 -45
  47. package/lib/client/core/builders/linkNodeBuilder.ts +79 -54
  48. package/lib/client/core/builders/listBuilder.ts +155 -89
  49. package/lib/client/core/builders/localeListBuilder.ts +97 -62
  50. package/lib/client/core/builders/types.ts +5 -5
  51. package/lib/client/core/cmsTemplateProcessor.ts +8 -9
  52. package/lib/client/elementRegistry.ts +3 -3
  53. package/lib/client/fontFamiliesService.test.ts +2 -10
  54. package/lib/client/fontFamiliesService.ts +3 -3
  55. package/lib/client/hmr/HMRManager.tsx +47 -30
  56. package/lib/client/hmrCssReload.ts +49 -14
  57. package/lib/client/hmrWebSocket.ts +14 -19
  58. package/lib/client/hooks/useColorVariables.test.ts +21 -21
  59. package/lib/client/hooks/useColorVariables.ts +23 -18
  60. package/lib/client/hooks/usePropertyAutocomplete.ts +4 -6
  61. package/lib/client/hooks/useVariables.ts +6 -4
  62. package/lib/client/hydration/HydrationUtils.test.ts +24 -25
  63. package/lib/client/hydration/HydrationUtils.ts +3 -4
  64. package/lib/client/i18nConfigService.test.ts +2 -7
  65. package/lib/client/i18nConfigService.ts +2 -2
  66. package/lib/client/meno-filter/MenoFilter.test.ts +47 -49
  67. package/lib/client/meno-filter/MenoFilter.ts +14 -15
  68. package/lib/client/meno-filter/bindings.ts +18 -43
  69. package/lib/client/meno-filter/init.ts +4 -4
  70. package/lib/client/meno-filter/renderer.ts +30 -32
  71. package/lib/client/meno-filter/script.generated.ts +1 -3
  72. package/lib/client/meno-filter/ui.ts +4 -6
  73. package/lib/client/meno-filter/updates.ts +18 -23
  74. package/lib/client/meno-filter/utils.ts +1 -1
  75. package/lib/client/navigation.test.ts +174 -178
  76. package/lib/client/navigation.ts +0 -1
  77. package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
  78. package/lib/client/responsiveStyleResolver.test.ts +234 -232
  79. package/lib/client/responsiveStyleResolver.ts +74 -20
  80. package/lib/client/routing/RouteLoader.test.ts +24 -25
  81. package/lib/client/routing/RouteLoader.ts +27 -35
  82. package/lib/client/routing/Router.tsx +210 -153
  83. package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
  84. package/lib/client/scripts/ScriptExecutor.ts +18 -36
  85. package/lib/client/services/PrefetchService.test.ts +3 -3
  86. package/lib/client/services/PrefetchService.ts +23 -29
  87. package/lib/client/styleProcessor.test.ts +9 -9
  88. package/lib/client/styleProcessor.ts +24 -15
  89. package/lib/client/styles/StyleInjector.test.ts +122 -115
  90. package/lib/client/styles/StyleInjector.ts +10 -7
  91. package/lib/client/styles/UtilityClassCollector.ts +62 -28
  92. package/lib/client/styles/cspNonce.test.ts +2 -5
  93. package/lib/client/templateEngine.test.ts +554 -488
  94. package/lib/client/templateEngine.ts +203 -73
  95. package/lib/client/theme.test.ts +1 -1
  96. package/lib/client/theme.ts +0 -1
  97. package/lib/client/utils/toast.ts +0 -1
  98. package/lib/server/__integration__/api-routes.test.ts +8 -4
  99. package/lib/server/__integration__/cms-integration.test.ts +1 -4
  100. package/lib/server/__integration__/server-lifecycle.test.ts +4 -5
  101. package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
  102. package/lib/server/__integration__/static-assets.test.ts +3 -3
  103. package/lib/server/__integration__/test-helpers.ts +87 -74
  104. package/lib/server/ab/generateFunctions.ts +12 -10
  105. package/lib/server/astro/cmsPageEmitter.ts +26 -42
  106. package/lib/server/astro/componentEmitter.ts +35 -43
  107. package/lib/server/astro/cssCollector.ts +10 -26
  108. package/lib/server/astro/nodeToAstro.test.ts +1754 -34
  109. package/lib/server/astro/nodeToAstro.ts +232 -216
  110. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +115 -119
  111. package/lib/server/astro/normalizeOrphanTemplateProps.ts +11 -18
  112. package/lib/server/astro/pageEmitter.ts +9 -13
  113. package/lib/server/astro/tailwindMapper.test.ts +10 -37
  114. package/lib/server/astro/tailwindMapper.ts +33 -40
  115. package/lib/server/astro/templateTransformer.ts +17 -20
  116. package/lib/server/createServer.ts +7 -19
  117. package/lib/server/cssGenerator.test.ts +35 -44
  118. package/lib/server/cssGenerator.ts +8 -19
  119. package/lib/server/fileWatcher.test.ts +164 -13
  120. package/lib/server/fileWatcher.ts +209 -124
  121. package/lib/server/index.ts +14 -17
  122. package/lib/server/jsonLoader.test.ts +39 -2
  123. package/lib/server/jsonLoader.ts +39 -34
  124. package/lib/server/middleware/cors.test.ts +21 -21
  125. package/lib/server/middleware/cors.ts +28 -4
  126. package/lib/server/middleware/errorHandler.test.ts +7 -5
  127. package/lib/server/middleware/errorHandler.ts +3 -8
  128. package/lib/server/middleware/index.ts +0 -1
  129. package/lib/server/middleware/logger.test.ts +10 -8
  130. package/lib/server/middleware/logger.ts +17 -29
  131. package/lib/server/migrateTemplates.ts +3 -3
  132. package/lib/server/pageCache.test.ts +76 -77
  133. package/lib/server/pageCache.ts +0 -1
  134. package/lib/server/projectContext.ts +5 -4
  135. package/lib/server/providers/fileSystemCMSProvider.test.ts +142 -99
  136. package/lib/server/providers/fileSystemCMSProvider.ts +62 -38
  137. package/lib/server/routes/api/cms.test.ts +26 -14
  138. package/lib/server/routes/api/cms.ts +9 -14
  139. package/lib/server/routes/api/colors.test.ts +2 -2
  140. package/lib/server/routes/api/components.ts +31 -35
  141. package/lib/server/routes/api/config.ts +3 -4
  142. package/lib/server/routes/api/core-routes.ts +49 -76
  143. package/lib/server/routes/api/enums.test.ts +2 -2
  144. package/lib/server/routes/api/functions.ts +9 -17
  145. package/lib/server/routes/api/index.ts +3 -6
  146. package/lib/server/routes/api/pages.ts +8 -11
  147. package/lib/server/routes/api/shared.test.ts +1 -1
  148. package/lib/server/routes/api/shared.ts +12 -9
  149. package/lib/server/routes/api/variables.test.ts +3 -5
  150. package/lib/server/routes/api/variables.ts +1 -1
  151. package/lib/server/routes/index.ts +25 -28
  152. package/lib/server/routes/pages.ts +35 -31
  153. package/lib/server/routes/static.ts +17 -6
  154. package/lib/server/runtime/bundler.ts +98 -61
  155. package/lib/server/runtime/fs.ts +16 -26
  156. package/lib/server/runtime/httpServer.ts +8 -12
  157. package/lib/server/services/ColorService.test.ts +3 -3
  158. package/lib/server/services/ColorService.ts +37 -29
  159. package/lib/server/services/EnumService.test.ts +4 -8
  160. package/lib/server/services/EnumService.ts +11 -3
  161. package/lib/server/services/VariableService.test.ts +8 -12
  162. package/lib/server/services/VariableService.ts +11 -3
  163. package/lib/server/services/cmsService.test.ts +158 -120
  164. package/lib/server/services/cmsService.ts +36 -146
  165. package/lib/server/services/componentService.test.ts +125 -45
  166. package/lib/server/services/componentService.ts +70 -46
  167. package/lib/server/services/configService.test.ts +22 -45
  168. package/lib/server/services/configService.ts +50 -27
  169. package/lib/server/services/fileWatcherService.ts +42 -8
  170. package/lib/server/services/index.ts +0 -1
  171. package/lib/server/services/pageService.test.ts +8 -10
  172. package/lib/server/services/pageService.ts +32 -18
  173. package/lib/server/ssr/attributeBuilder.ts +18 -8
  174. package/lib/server/ssr/buildErrorOverlay.ts +11 -12
  175. package/lib/server/ssr/clientDataInjector.ts +7 -21
  176. package/lib/server/ssr/cmsSSRProcessor.ts +4 -8
  177. package/lib/server/ssr/cssCollector.ts +1 -1
  178. package/lib/server/ssr/errorOverlay.test.ts +1 -1
  179. package/lib/server/ssr/errorOverlay.ts +3 -9
  180. package/lib/server/ssr/htmlGenerator.nonce.test.ts +9 -14
  181. package/lib/server/ssr/htmlGenerator.test.ts +120 -44
  182. package/lib/server/ssr/htmlGenerator.ts +126 -91
  183. package/lib/server/ssr/imageMetadata.test.ts +3 -1
  184. package/lib/server/ssr/imageMetadata.ts +27 -21
  185. package/lib/server/ssr/jsCollector.test.ts +8 -18
  186. package/lib/server/ssr/jsCollector.ts +3 -8
  187. package/lib/server/ssr/liveReloadIntegration.test.ts +76 -36
  188. package/lib/server/ssr/metaTagGenerator.ts +2 -2
  189. package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
  190. package/lib/server/ssr/ssrRenderer.test.ts +316 -256
  191. package/lib/server/ssr/ssrRenderer.ts +795 -243
  192. package/lib/server/ssrRenderer.test.ts +1048 -954
  193. package/lib/server/utils/jsonLineMapper.test.ts +28 -28
  194. package/lib/server/utils/jsonLineMapper.ts +7 -7
  195. package/lib/server/validateStyleCoverage.ts +18 -21
  196. package/lib/server/websocketManager.ts +28 -19
  197. package/lib/shared/attributeNodeUtils.test.ts +15 -15
  198. package/lib/shared/attributeNodeUtils.ts +5 -12
  199. package/lib/shared/breakpoints.test.ts +5 -5
  200. package/lib/shared/breakpoints.ts +4 -11
  201. package/lib/shared/cmsQuery.test.ts +81 -0
  202. package/lib/shared/cmsQuery.ts +92 -0
  203. package/lib/shared/cmsQueryParser.test.ts +50 -42
  204. package/lib/shared/cmsQueryParser.ts +60 -38
  205. package/lib/shared/colorConversions.ts +9 -9
  206. package/lib/shared/colorVariableUtils.test.ts +27 -5
  207. package/lib/shared/colorVariableUtils.ts +21 -12
  208. package/lib/shared/componentRefs.ts +1 -5
  209. package/lib/shared/constants.test.ts +3 -3
  210. package/lib/shared/constants.ts +8 -8
  211. package/lib/shared/cssGeneration.test.ts +285 -144
  212. package/lib/shared/cssGeneration.ts +217 -531
  213. package/lib/shared/cssNamedColors.ts +152 -30
  214. package/lib/shared/cssProperties.test.ts +69 -70
  215. package/lib/shared/cssProperties.ts +298 -114
  216. package/lib/shared/elementClassName.test.ts +109 -109
  217. package/lib/shared/elementClassName.ts +3 -3
  218. package/lib/shared/elementUtils.ts +12 -16
  219. package/lib/shared/errorLogger.test.ts +12 -12
  220. package/lib/shared/errorLogger.ts +2 -10
  221. package/lib/shared/errors.test.ts +2 -13
  222. package/lib/shared/errors.ts +2 -8
  223. package/lib/shared/expressionEvaluator.test.ts +145 -0
  224. package/lib/shared/expressionEvaluator.ts +52 -24
  225. package/lib/shared/fontLoader.test.ts +32 -18
  226. package/lib/shared/fontLoader.ts +2 -2
  227. package/lib/shared/friendlyError.ts +2 -3
  228. package/lib/shared/gradientUtils.test.ts +3 -7
  229. package/lib/shared/gradientUtils.ts +17 -14
  230. package/lib/shared/hrefRefs.test.ts +2 -2
  231. package/lib/shared/hrefRefs.ts +2 -10
  232. package/lib/shared/i18n.test.ts +4 -4
  233. package/lib/shared/i18n.ts +18 -38
  234. package/lib/shared/index.ts +27 -0
  235. package/lib/shared/inlineSvgStyleRules.ts +14 -13
  236. package/lib/shared/interactiveStyleMappings.test.ts +32 -54
  237. package/lib/shared/interactiveStyleMappings.ts +11 -24
  238. package/lib/shared/interactiveStyles.test.ts +165 -188
  239. package/lib/shared/interfaces/contentProvider.ts +1 -1
  240. package/lib/shared/itemTemplateUtils.test.ts +11 -17
  241. package/lib/shared/itemTemplateUtils.ts +27 -39
  242. package/lib/shared/jsonRepair.ts +16 -10
  243. package/lib/shared/libraryLoader.test.ts +21 -55
  244. package/lib/shared/libraryLoader.ts +7 -22
  245. package/lib/shared/linkUtils.ts +1 -1
  246. package/lib/shared/logger.test.ts +66 -0
  247. package/lib/shared/logger.ts +94 -0
  248. package/lib/shared/markdown.ts +25 -0
  249. package/lib/shared/netlifyLocale404.test.ts +179 -0
  250. package/lib/shared/netlifyLocale404.ts +110 -0
  251. package/lib/shared/nodeUtils.test.ts +28 -16
  252. package/lib/shared/nodeUtils.ts +76 -23
  253. package/lib/shared/pathArrayUtils.test.ts +1 -2
  254. package/lib/shared/pathArrayUtils.ts +1 -1
  255. package/lib/shared/pathSecurity.ts +2 -2
  256. package/lib/shared/pathUtils.test.ts +4 -6
  257. package/lib/shared/pathUtils.ts +42 -49
  258. package/lib/shared/paths/Path.test.ts +2 -2
  259. package/lib/shared/paths/Path.ts +0 -1
  260. package/lib/shared/paths/PathConverter.test.ts +1 -1
  261. package/lib/shared/paths/PathConverter.ts +17 -20
  262. package/lib/shared/paths/PathUtils.ts +13 -13
  263. package/lib/shared/paths/PathValidator.test.ts +2 -15
  264. package/lib/shared/paths/PathValidator.ts +12 -10
  265. package/lib/shared/paths/index.ts +1 -2
  266. package/lib/shared/permissions.test.ts +168 -0
  267. package/lib/shared/permissions.ts +162 -0
  268. package/lib/shared/propResolver.test.ts +240 -244
  269. package/lib/shared/propResolver.ts +15 -26
  270. package/lib/shared/pxToRem.test.ts +7 -6
  271. package/lib/shared/pxToRem.ts +2 -5
  272. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +12 -6
  273. package/lib/shared/registry/ClientRegistry.ts +0 -2
  274. package/lib/shared/registry/ComponentRegistry.test.ts +46 -32
  275. package/lib/shared/registry/ComponentRegistry.ts +12 -9
  276. package/lib/shared/registry/NodeTypeDefinition.ts +16 -9
  277. package/lib/shared/registry/RegistryManager.ts +1 -2
  278. package/lib/shared/registry/SSRRegistry.ts +0 -2
  279. package/lib/shared/registry/createNodeType.ts +7 -9
  280. package/lib/shared/registry/defineNodeType.ts +2 -6
  281. package/lib/shared/registry/fieldPresets.ts +3 -1
  282. package/lib/shared/registry/index.ts +0 -1
  283. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
  284. package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
  285. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
  286. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +48 -19
  287. package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
  288. package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
  289. package/lib/shared/registry/nodeTypes/ListNodeType.ts +117 -77
  290. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
  291. package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
  292. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +12 -8
  293. package/lib/shared/registry/nodeTypes/index.ts +26 -3
  294. package/lib/shared/responsiveScaling.test.ts +16 -34
  295. package/lib/shared/responsiveScaling.ts +61 -42
  296. package/lib/shared/responsiveStyleUtils.test.ts +0 -1
  297. package/lib/shared/responsiveStyleUtils.ts +11 -13
  298. package/lib/shared/richtext/htmlToTiptap.test.ts +139 -130
  299. package/lib/shared/richtext/htmlToTiptap.ts +2 -4
  300. package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
  301. package/lib/shared/richtext/types.ts +1 -8
  302. package/lib/shared/slugTranslator.test.ts +13 -13
  303. package/lib/shared/slugTranslator.ts +12 -16
  304. package/lib/shared/slugify.ts +9 -15
  305. package/lib/shared/styleNodeUtils.test.ts +8 -8
  306. package/lib/shared/styleNodeUtils.ts +18 -20
  307. package/lib/shared/styleUtils.test.ts +87 -61
  308. package/lib/shared/styleUtils.ts +5 -6
  309. package/lib/shared/themeDefaults.test.ts +11 -14
  310. package/lib/shared/themeDefaults.ts +3 -4
  311. package/lib/shared/tree/PathBuilder.test.ts +63 -145
  312. package/lib/shared/tree/PathBuilder.ts +59 -47
  313. package/lib/shared/treePathUtils.test.ts +51 -12
  314. package/lib/shared/treePathUtils.ts +91 -60
  315. package/lib/shared/types/api.ts +105 -8
  316. package/lib/shared/types/cms.test.ts +34 -0
  317. package/lib/shared/types/cms.ts +90 -34
  318. package/lib/shared/types/comment.ts +49 -16
  319. package/lib/shared/types/components.ts +51 -25
  320. package/lib/shared/types/errors.test.ts +1 -6
  321. package/lib/shared/types/errors.ts +3 -7
  322. package/lib/shared/types/experiments.ts +28 -28
  323. package/lib/shared/types/index.ts +25 -2
  324. package/lib/shared/types/permissions.ts +139 -0
  325. package/lib/shared/types/styles.ts +0 -1
  326. package/lib/shared/types/variables.test.ts +4 -13
  327. package/lib/shared/types/variables.ts +51 -30
  328. package/lib/shared/types.ts +1 -2
  329. package/lib/shared/utilityClassConfig.ts +751 -321
  330. package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
  331. package/lib/shared/utilityClassMapper.test.ts +433 -95
  332. package/lib/shared/utilityClassMapper.ts +322 -249
  333. package/lib/shared/utilityClassNames.test.ts +38 -0
  334. package/lib/shared/utilityClassNames.ts +458 -0
  335. package/lib/shared/utils.test.ts +2 -10
  336. package/lib/shared/utils.ts +19 -10
  337. package/lib/shared/validation/cmsValidators.ts +2 -1
  338. package/lib/shared/validation/commentValidators.test.ts +53 -0
  339. package/lib/shared/validation/commentValidators.ts +12 -1
  340. package/lib/shared/validation/index.ts +1 -0
  341. package/lib/shared/validation/permissionsValidators.test.ts +49 -0
  342. package/lib/shared/validation/permissionsValidators.ts +49 -0
  343. package/lib/shared/validation/propValidator.test.ts +19 -21
  344. package/lib/shared/validation/propValidator.ts +13 -18
  345. package/lib/shared/validation/schemas.test.ts +64 -32
  346. package/lib/shared/validation/schemas.ts +616 -345
  347. package/lib/shared/validation/validators.test.ts +3 -8
  348. package/lib/shared/validation/validators.ts +89 -68
  349. package/lib/shared/viewportUnits.integration.test.ts +14 -10
  350. package/lib/shared/viewportUnits.test.ts +24 -23
  351. package/lib/shared/viewportUnits.ts +12 -5
  352. package/lib/test-utils/dom-setup.ts +1 -1
  353. package/lib/test-utils/factories/ConsoleMockFactory.ts +10 -14
  354. package/lib/test-utils/factories/DomMockFactory.ts +17 -23
  355. package/lib/test-utils/factories/EventMockFactory.ts +7 -13
  356. package/lib/test-utils/factories/FetchMockFactory.ts +39 -57
  357. package/lib/test-utils/factories/ServerMockFactory.ts +6 -10
  358. package/lib/test-utils/factories/StoreMockFactory.ts +18 -29
  359. package/lib/test-utils/fixtures.ts +45 -45
  360. package/lib/test-utils/helpers/asyncHelpers.test.ts +15 -18
  361. package/lib/test-utils/helpers/asyncHelpers.ts +11 -20
  362. package/lib/test-utils/helpers.ts +1 -5
  363. package/lib/test-utils/index.ts +0 -4
  364. package/lib/test-utils/mockFactories.ts +13 -19
  365. package/lib/test-utils/mocks.ts +6 -4
  366. package/package.json +3 -5
  367. package/scripts/build-for-publish.mjs +2 -4
  368. package/scripts/build-meno-filter.ts +4 -7
  369. package/tsconfig.json +1 -0
  370. package/vite.config.ts +4 -4
  371. package/build-next.ts +0 -1361
  372. package/build-static.test.ts +0 -424
  373. package/build-static.ts +0 -1074
  374. package/dist/build-static.js +0 -36
  375. package/dist/chunks/chunk-2MHDV5BF.js +0 -251
  376. package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
  377. package/dist/chunks/chunk-AZQYF6KE.js +0 -559
  378. package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
  379. package/dist/chunks/chunk-GZHGVVW3.js +0 -322
  380. package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
  381. package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
  382. package/dist/chunks/chunk-I7YIGZXT.js +0 -467
  383. package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
  384. package/dist/chunks/chunk-IGYR22T6.js +0 -6192
  385. package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
  386. package/dist/chunks/chunk-JGP5A3Y5.js +0 -1136
  387. package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
  388. package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
  389. package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
  390. package/dist/chunks/chunk-QB2LNO4W.js +0 -77
  391. package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
  392. package/dist/chunks/chunk-R6XHAFBF.js +0 -3821
  393. package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
  394. package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
  395. package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
  396. package/dist/chunks/chunk-X754AHS5.js.map +0 -7
  397. package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
  398. package/dist/chunks/configService-R3OGU2UD.js +0 -13
  399. package/dist/chunks/configService-R3OGU2UD.js.map +0 -7
  400. package/dist/chunks/constants-STK2YBIW.js +0 -46
  401. package/dist/chunks/constants-STK2YBIW.js.map +0 -7
  402. package/dist/chunks/fs-JGINUXGL.js.map +0 -7
  403. package/dist/entries/server-router.js +0 -67
  404. package/dist/entries/server-router.js.map +0 -7
  405. package/entries/server-router.tsx +0 -81
  406. package/lib/server/providers/fileSystemPageProvider.ts +0 -160
  407. package/lib/server/webflow/buildWebflow.ts +0 -635
  408. package/lib/server/webflow/index.ts +0 -23
  409. package/lib/server/webflow/nodeToWebflow.test.ts +0 -3170
  410. package/lib/server/webflow/nodeToWebflow.ts +0 -2435
  411. package/lib/server/webflow/styleMapper.test.ts +0 -389
  412. package/lib/server/webflow/styleMapper.ts +0 -695
  413. package/lib/server/webflow/templateWrapper.ts +0 -49
  414. package/lib/server/webflow/types.ts +0 -396
  415. /package/dist/{build-static.js.map → chunks/fs-G3ANWEFP.js.map} +0 -0
@@ -1,19 +1,40 @@
1
1
  /**
2
- * Maps CSS property values to utility class names
3
- * Example: { padding: "10px", display: "flex" } → ["p-10px", "flex"]
2
+ * Maps CSS property values to Tailwind-style utility class names
3
+ * Example: { padding: "10px", display: "flex" } → ["p-[10px]", "flex"]
4
4
  * Uses centralized configuration from utilityClassConfig.ts
5
5
  */
6
6
 
7
- import type { ResponsiveStyleObject, StyleObject, StyleMapping } from './types';
7
+ import type { ResponsiveStyleObject, StyleObject, StyleMapping, StyleValue } from './types';
8
+ import { isResponsiveStyle } from './styleUtils';
8
9
  import {
9
10
  propertyMap,
10
- specialValueMappings,
11
- classToStyleSpecialCases,
11
+ propertyOrder,
12
+ staticUtilities,
13
+ staticUtilityReverse,
14
+ presetClassReverse,
15
+ keywordValues,
16
+ genericKeywordRoots,
17
+ integerValueRoots,
18
+ colorTokenProps,
12
19
  shadowPresets,
13
20
  gradientPresets,
14
21
  borderPresets,
22
+ SPACING_SCALE_ROOTS,
23
+ SPACING_SCALE_REVERSE,
15
24
  } from './utilityClassConfig';
16
- import { registerStyleValue, registerDynamicStyle, getStyleValue } from './styleValueRegistry';
25
+ import {
26
+ camelToKebab,
27
+ cssPropertyToCamel,
28
+ encodeArbitraryValue,
29
+ parseUtilityClass,
30
+ splitVariantPrefix,
31
+ breakpointClassPrefix,
32
+ normalizeBreakpointVariant,
33
+ resolveRootProperty,
34
+ ROOT_CAMEL_OVERRIDES,
35
+ isBareColorTokenName,
36
+ } from './utilityClassNames';
37
+ import { registerStyleValue, registerDynamicStyle, getStyleValue, getDynamicStyle } from './styleValueRegistry';
17
38
  import {
18
39
  SCALABLE_CSS_PROPERTIES,
19
40
  buildFluidClampWithExplicitMin,
@@ -22,43 +43,13 @@ import {
22
43
  type ResponsiveScales,
23
44
  } from './responsiveScaling';
24
45
 
25
- // Function name abbreviations for shorter class names
26
- const functionAbbreviations: Record<string, string> = {
27
- blur: 'bl',
28
- translateY: 'ty',
29
- translateX: 'tx',
30
- translate: 't',
31
- scale: 'sc',
32
- scaleX: 'scx',
33
- scaleY: 'scy',
34
- rotate: 'ro',
35
- skew: 'sk',
36
- skewX: 'skx',
37
- skewY: 'sky',
38
- repeat: 're',
39
- minmax: 'mm',
40
- calc: 'ca',
41
- };
42
-
43
46
  /**
44
47
  * Check if a value is a StyleMapping object
45
48
  */
46
49
  function isStyleMapping(value: unknown): value is StyleMapping {
47
- return typeof value === 'object' && value !== null && '_mapping' in value && (value as StyleMapping)._mapping === true;
48
- }
49
-
50
- /**
51
- * Sanitize a value for use in a CSS class name
52
- * Replaces special characters with safe alternatives
53
- */
54
- function sanitizeClassValue(value: string): string {
55
- return value
56
- .replace(/\s+/g, '-') // spaces to hyphens
57
- .replace(/[()]/g, '') // remove parentheses
58
- .replace(/,/g, '_') // commas to underscores
59
- .replace(/%/g, 'p') // percent to 'p'
60
- .replace(/\./g, 'd') // dots to 'd' (for decimals)
61
- .replace(/[^\w-]/g, ''); // remove other special chars
50
+ return (
51
+ typeof value === 'object' && value !== null && '_mapping' in value && (value as StyleMapping)._mapping === true
52
+ );
62
53
  }
63
54
 
64
55
  /**
@@ -76,166 +67,184 @@ function shortHash(input: string): string {
76
67
  }
77
68
 
78
69
  /**
79
- * True if a class name contains characters illegal in CSS selectors. We
80
- * generate dynamic class names from CSS values; complex values (especially
81
- * Webflow `clamp(2*1rem, ((..2)/(90 - 20)*20)*1rem + ..*100vw, 4*1rem)`)
82
- * end up with `*`, `(`, `)`, `+`, `,` baked into the class name, none of
83
- * which are valid in CSS selectors. Browser silently drops the rule and
84
- * the styled property reverts to UA defaults. Hash-fallback gives us a
85
- * short, valid-CSS class name, with the actual value resolved via the
86
- * style-value registry at CSS generation time.
70
+ * Whether the natural `root-(--x)` / `root-[value]` form would round-trip to a DIFFERENT property
71
+ * than `prop`. Roots shared by a shorthand and a color/width longhand (`border` → border/borderColor,
72
+ * `outline` → outline/outlineWidth/outlineColor) are disambiguated on parse by value type via
73
+ * `resolveRootProperty` so a color-looking value (notably any opaque `var(--x)`) bound to the
74
+ * SHORTHAND reads back as the color longhand. Callers fall back to the explicit arbitrary-property
75
+ * form (`[border:var(--x)]`) when this returns true.
76
+ *
77
+ * Mirrors the inverse parser's property derivation EXACTLY (`classToStyle`): `ROOT_CAMEL_OVERRIDES`
78
+ * wins first (px → paddingInline, …), else `resolveRootProperty`. Comparing kebab CSS names instead
79
+ * would false-positive on those overridden roots (`px` resolves to `padding`, not `padding-inline`).
87
80
  */
88
- function hasIllegalClassChars(className: string): boolean {
89
- return /[^\w-]/.test(className);
81
+ function rootFormMisresolves(root: string, prop: string, value: string): boolean {
82
+ const parsedProp = ROOT_CAMEL_OVERRIDES[root] ?? cssPropertyToCamel(resolveRootProperty(root, value));
83
+ return parsedProp !== prop;
90
84
  }
91
85
 
92
86
  /**
93
- * Converts a CSS property value to a utility class name
94
- * Example: { prop: "padding", value: "10px" } → "p-10px"
87
+ * Converts a CSS property value to a Tailwind-style utility class name.
88
+ * Examples:
89
+ * { prop: "padding", value: "16px" } → "p-4" (exact scale step)
90
+ * { prop: "padding", value: "13px" } → "p-[13px]" (off-scale → arbitrary)
91
+ * { prop: "display", value: "flex" } → "flex"
92
+ * { prop: "color", value: "primary" } → "text-primary"
93
+ * { prop: "width", value: "100%" } → "w-full"
94
+ * { prop: "textOverflow", value: "ellipsis" } → "[text-overflow:ellipsis]"
95
95
  *
96
- * For properties not in propertyMap, generates a dynamic class using
97
- * a shortened property name prefix (e.g., "textOverflow" → "txov-ellipsis")
96
+ * Values that can't be encoded reversibly fall back to a hash class
97
+ * (`pt-h1y9pr6i`) resolved through the style-value registry.
98
98
  */
99
99
  function propertyValueToClass(prop: string, value: string | number): string | null {
100
- const prefix = propertyMap[prop];
101
-
102
- // For properties not in propertyMap, generate a dynamic fallback class
103
- if (!prefix) {
104
- const stringValue = String(value);
105
- // Generate a short prefix from the property name (first 2-4 chars of each word)
106
- // e.g., "textOverflow" → "txov", "WebkitBackgroundClip" → "wbbg"
107
- const dynamicPrefix = prop
108
- .replace(/([A-Z])/g, '-$1') // camelCase to kebab
109
- .toLowerCase()
110
- .split('-')
111
- .filter(Boolean)
112
- .map(word => word.slice(0, 2)) // first 2 chars of each word
113
- .join('');
114
-
115
- // Sanitize value for class name
116
- const sanitizedValue = sanitizeClassValue(stringValue);
117
- const className = `${dynamicPrefix}-${sanitizedValue}`;
118
-
119
- // Convert camelCase to kebab-case for CSS property
120
- const cssProperty = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
121
-
122
- // Register in dynamic registry with property info
123
- registerDynamicStyle(className, cssProperty, value);
124
- return className;
125
- }
126
-
127
100
  let stringValue = String(value);
128
101
 
129
102
  // Strip outer quotes from fontFamily values (e.g. "Space Grotesk" → Space Grotesk)
130
103
  if (prop === 'fontFamily') {
131
- stringValue = stringValue.split(',').map(font => {
132
- const trimmed = font.trim();
133
- if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
134
- (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
135
- return trimmed.slice(1, -1);
136
- }
137
- return trimmed;
138
- }).join(', ');
104
+ stringValue = stringValue
105
+ .split(',')
106
+ .map((font) => {
107
+ const trimmed = font.trim();
108
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
109
+ return trimmed.slice(1, -1);
110
+ }
111
+ return trimmed;
112
+ })
113
+ .join(', ');
139
114
  }
140
115
 
141
- let className: string | null = null;
142
-
143
- // Check for special value mappings first
144
- if (specialValueMappings[prop]) {
145
- const mapping = specialValueMappings[prop];
146
- if (mapping[stringValue]) {
147
- className = mapping[stringValue];
148
- }
116
+ // Static utilities (Tailwind names): display: flex "flex", textAlign: center → "text-center"
117
+ const staticClass = staticUtilities[prop]?.[stringValue];
118
+ if (staticClass) {
119
+ registerStyleValue(staticClass, stringValue);
120
+ return staticClass;
149
121
  }
150
122
 
151
- // Check for preset values (e.g., boxShadow: "2" → sh-2)
152
- if (!className && prop === 'boxShadow' && shadowPresets[stringValue]) {
153
- className = `sh-${stringValue}`;
123
+ // Preset values (e.g., boxShadow: "2" → shadow-2)
124
+ const shadowPreset = shadowPresets[stringValue];
125
+ if (prop === 'boxShadow' && shadowPreset) {
126
+ const className = `shadow-${stringValue}`;
127
+ registerStyleValue(className, shadowPreset);
128
+ return className;
154
129
  }
155
- if (!className && (prop === 'background' || prop === 'backgroundImage') && gradientPresets[stringValue]) {
156
- className = `gr-${stringValue}`;
130
+ const gradientPreset = gradientPresets[stringValue];
131
+ if ((prop === 'background' || prop === 'backgroundImage') && gradientPreset) {
132
+ const className = `bg-gradient-${stringValue}`;
133
+ registerStyleValue(className, gradientPreset);
134
+ return className;
157
135
  }
158
- if (!className && prop === 'border' && borderPresets[stringValue]) {
159
- className = `bd-${stringValue}`;
136
+ const borderPreset = borderPresets[stringValue];
137
+ if (prop === 'border' && borderPreset) {
138
+ const className = `border-preset-${stringValue}`;
139
+ registerStyleValue(className, borderPreset);
140
+ return className;
160
141
  }
161
142
 
162
- // Handle CSS function values: blur(), translateY(), rotate(), scale(), repeat(), etc.
163
- // Uses abbreviations for shorter class names: blur(8px) → bl8, translateY(-15px) → tyn15
164
- if (!className && stringValue.includes('(') && stringValue.includes(')')) {
165
- const funcMatch = stringValue.match(/^([a-zA-Z]+)\((.+)\)$/);
166
- if (funcMatch) {
167
- const [, funcName, args] = funcMatch;
168
- const abbrev = functionAbbreviations[funcName] || funcName.slice(0, 2);
169
- const argNormalized = args
170
- .replace(/,\s*/g, '-') // "3, 1fr" → "3-1fr"
171
- .replace(/\s+/g, '') // Remove spaces
172
- .replace(/-(\d)/g, 'n$1') // "-15px" → "n15px"
173
- .replace(/px$/i, '') // Remove trailing px for brevity
174
- .replace(/%$/, 'p'); // "50%" → "50p"
175
- className = `${prefix}-${abbrev}${argNormalized}`;
176
- }
143
+ const root = propertyMap[prop];
144
+
145
+ // Properties without a Tailwind root use arbitrary-property syntax:
146
+ // textOverflow: ellipsis → "[text-overflow:ellipsis]"
147
+ if (!root) {
148
+ const cssProperty = camelToKebab(prop);
149
+ const encoded = encodeArbitraryValue(stringValue);
150
+ const className = encoded != null ? `[${cssProperty}:${encoded}]` : `${cssProperty}-h${shortHash(stringValue)}`;
151
+ registerDynamicStyle(className, cssProperty, value);
152
+ return className;
177
153
  }
178
154
 
179
- // CSS variables (e.g., var(--background))
180
- if (!className && stringValue.includes('var(')) {
181
- const varMatch = stringValue.match(/var\((--[\w-]+)\)/);
182
- if (varMatch) {
183
- const varName = varMatch[1].replace('--', '');
184
- className = `${prefix}-${varName}`;
155
+ let className: string | null = null;
156
+
157
+ // Named keyword values: width: 100% → w-full, margin: auto → m-auto
158
+ const kwTable = keywordValues[root];
159
+ if (kwTable) {
160
+ for (const [keyword, cssValue] of Object.entries(kwTable)) {
161
+ if (cssValue === stringValue) {
162
+ className = `${root}-${keyword}`;
163
+ break;
164
+ }
185
165
  }
186
166
  }
187
167
 
188
- // Detect variable names for color/background properties (e.g., color: "text"c-text)
189
- // These are stored as plain names without var() wrapper and should be treated as variables
190
- if (!className &&
191
- (prop === 'color' || prop === 'backgroundColor' || prop === 'borderColor') &&
192
- !stringValue.match(/^\d/) && !stringValue.includes('#') && !stringValue.includes('rgb')) {
193
- className = `${prefix}-${stringValue}`;
168
+ // Generic keyword roots: cursor: pointer cursor-pointer, transformOrigin: centerorigin-center
169
+ if (!className && genericKeywordRoots.has(root) && /^[a-z][a-z-]*$/.test(stringValue)) {
170
+ className = `${root}-${stringValue}`;
194
171
  }
195
172
 
196
- // Direct values (with units)
197
- if (!className && (stringValue.match(/^\d+px$/) || stringValue.match(/^\d+em$/))) {
198
- className = `${prefix}-${stringValue}`;
173
+ // Bare numeric values Tailwind-style: zIndex: 10 → z-10, opacity: 0.5 → opacity-50
174
+ if (!className && integerValueRoots.has(root) && /^-?\d+$/.test(stringValue)) {
175
+ className = `${root}-${stringValue}`;
199
176
  }
200
-
201
- // Percentage values - convert % to p (e.g., "50%" → "50p")
202
- if (!className && stringValue.match(/^\d+%$/)) {
203
- const percentValue = stringValue.replace('%', 'p');
204
- className = `${prefix}-${percentValue}`;
177
+ if (!className && prop === 'opacity') {
178
+ const num = Number(stringValue);
179
+ if (Number.isFinite(num) && num >= 0 && num <= 1) {
180
+ const scaled = Math.round(num * 100);
181
+ if (Math.abs(num * 100 - scaled) < 1e-9) {
182
+ className = `opacity-${scaled}`;
183
+ }
184
+ }
205
185
  }
206
186
 
207
- // Handle values with spaces by replacing spaces with hyphens (e.g., "92px 0" "p-92px-0")
208
- if (!className && stringValue.includes(' ')) {
209
- const spacedValue = stringValue.replace(/\s+/g, '-');
210
- className = `${prefix}-${spacedValue}`;
187
+ // Named spacing scale (Tailwind steps): padding 16px p-4, gap 24pxgap-6.
188
+ // Only EXACT steps; off-scale values fall through to the arbitrary form below.
189
+ // Keyword values (w-full, m-auto) are matched above, so they still win.
190
+ if (!className && SPACING_SCALE_ROOTS.has(root)) {
191
+ const step = SPACING_SCALE_REVERSE[stringValue];
192
+ if (step !== undefined) {
193
+ className = `${root}-${step}`;
194
+ }
211
195
  }
212
196
 
213
- // Special handling for margin/padding "auto"
214
- if (!className && stringValue === 'auto') {
215
- className = `${prefix}-auto`;
197
+ // CSS variables: var(--x) root-(--x). The `font-*` root is shared by font-weight and
198
+ // font-family and is disambiguated by value type — but a `var(--x)` value is ambiguous, so
199
+ // weight/size vars carry a Tailwind data-type hint (`number:` / `length:`) the parser reads
200
+ // back. Without it a `fontWeight: var(--h-fw)` binding would resolve to `font-family` (an
201
+ // invalid `font-family: 450`), dropping the weight entirely.
202
+ if (!className) {
203
+ const varMatch = stringValue.match(/^var\((--[\w-]+)\)$/);
204
+ if (varMatch) {
205
+ const varName = (varMatch[1] ?? '').slice(2); // strip the leading `--`
206
+ if (prop === 'fontSize') className = `text-(length:${varMatch[1]})`;
207
+ else if (prop === 'fontWeight') className = `font-(number:${varMatch[1]})`;
208
+ // The `border`/`outline` roots are shared by a shorthand and a color longhand
209
+ // (`border` vs `borderColor`). The inverse parser disambiguates by value type, but a
210
+ // `var(--x)` value is opaque and reads as a color — so `border-(--x)` would round-trip
211
+ // to `borderColor`. When the prop is the shorthand/width longhand, fall back to the
212
+ // unambiguous arbitrary-property form (`[border:var(--x)]`), which names it explicitly.
213
+ else if (rootFormMisresolves(root, prop, stringValue)) {
214
+ className = `[${camelToKebab(prop)}:${stringValue}]`;
215
+ }
216
+ // A `var(--token)` on a color-token prop IS a Meno token reference → emit the bare token
217
+ // form (`bg-muted`), matching the bare-value path so `var(--muted)` and `muted` agree.
218
+ // (Token-aware inverse recovers it; palette-shaped names fall through to the var shorthand.)
219
+ else if (colorTokenProps.has(prop) && isBareColorTokenName(varName)) {
220
+ className = `${root}-${varName}`;
221
+ } else className = `${root}-(${varMatch[1]})`;
222
+ }
216
223
  }
217
224
 
218
- // Handle values with slash (e.g., aspectRatio "1/1" → "ar-1s1")
219
- if (!className && stringValue.includes('/')) {
220
- const slashValue = stringValue.replace(/\//g, 's');
221
- className = `${prefix}-${slashValue}`;
225
+ // Bare color tokens (Meno convention): color: "primary" → text-primary
226
+ if (!className && colorTokenProps.has(prop) && isBareColorTokenName(stringValue)) {
227
+ className = `${root}-${stringValue}`;
222
228
  }
223
229
 
224
- // For other cases, append the value
230
+ // Arbitrary value (Tailwind bracket syntax): padding: "92px 0" → p-[92px_0]
225
231
  if (!className) {
226
- className = `${prefix}-${stringValue}`;
232
+ const encoded = encodeArbitraryValue(stringValue);
233
+ if (encoded != null) {
234
+ // Same shorthand/longhand guard as the var branch: a bare color value bound to the
235
+ // `border`/`outline` shorthand would parse back as the color longhand, so name the
236
+ // property explicitly via the arbitrary-property form instead of `root-[…]`.
237
+ className = rootFormMisresolves(root, prop, stringValue)
238
+ ? `[${camelToKebab(prop)}:${encoded}]`
239
+ : `${root}-[${encoded}]`;
240
+ }
227
241
  }
228
242
 
229
- // Hash-fallback for class names with illegal CSS selector chars. Webflow's
230
- // fluid `clamp(2*1rem, ((2 - .5)/70*20)*1rem + ..*100vw, 4*1rem)` would
231
- // otherwise produce `pt-cl2*1rem-((2-.5)/70*20)*1rem+..*100vw-4*1rem`,
232
- // with `*`, `(`, `)`, `+`, `,` — none of which are valid in CSS selectors.
233
- // Browser drops the rule, the styled element reverts to defaults
234
- // (paddings collapse to 0, etc.). Short FNV-1a hash + style-value
235
- // registry preserves the original value for the CSS generator without
236
- // baking it into the selector.
237
- if (hasIllegalClassChars(className)) {
238
- className = `${prefix}-h${shortHash(stringValue)}`;
243
+ // Hash-fallback for values that can't be encoded reversibly (quotes,
244
+ // brackets, literal underscores). The original value is preserved in the
245
+ // style-value registry for the CSS generator.
246
+ if (!className) {
247
+ className = `${root}-h${shortHash(stringValue)}`;
239
248
  }
240
249
 
241
250
  // Register original value for CSS generation (single registration point)
@@ -243,24 +252,19 @@ function propertyValueToClass(prop: string, value: string | number): string | nu
243
252
  return className;
244
253
  }
245
254
 
246
- // Get property order from propertyMap for consistent class ordering
247
- const propertyOrder = Object.keys(propertyMap);
248
-
249
255
  /**
250
256
  * Convert a style object to an array of utility classes
251
- * Example: { padding: "10px", display: "flex" } → ["p-10px", "flex"]
257
+ * Example: { padding: "10px", display: "flex" } → ["p-[10px]", "flex"]
252
258
  *
253
- * Properties are sorted according to propertyMap order to ensure
259
+ * Properties are sorted according to propertyOrder to ensure
254
260
  * dependent properties (like borderColor) come after their base (border)
255
261
  *
256
262
  * StyleMapping values are filtered out - they are handled at render time
257
263
  */
258
- export function stylesToClasses(
259
- styles: StyleObject | Record<string, string | number> | null | undefined
260
- ): string[] {
264
+ export function stylesToClasses(styles: StyleObject | Record<string, string | number> | null | undefined): string[] {
261
265
  if (!styles) return [];
262
266
 
263
- // Sort properties by their order in propertyMap
267
+ // Sort properties by their canonical order
264
268
  // This ensures border comes before borderColor, etc.
265
269
  const sortedProps = Object.keys(styles).sort((a, b) => {
266
270
  const indexA = propertyOrder.indexOf(a);
@@ -274,6 +278,7 @@ export function stylesToClasses(
274
278
  const classes: string[] = [];
275
279
  for (const prop of sortedProps) {
276
280
  const value = styles[prop];
281
+ if (value === undefined) continue;
277
282
  // Skip StyleMapping values - they are resolved at render time, not converted to classes
278
283
  if (isStyleMapping(value)) continue;
279
284
  const className = propertyValueToClass(prop, value);
@@ -287,10 +292,10 @@ export function stylesToClasses(
287
292
  /**
288
293
  * Convert a responsive style object to utility classes with responsive prefixes
289
294
  * Example: { base: { padding: "10px" }, tablet: { padding: "24px" } }
290
- * → ["p-10px", "t-p-24px"]
295
+ * → ["p-[10px]", "tablet:p-[24px]"]
291
296
  *
292
297
  * Also handles flat merged style objects:
293
- * Example: { padding: "10px", display: "flex" } → ["p-10px", "flex"]
298
+ * Example: { padding: "10px", display: "flex" } → ["p-[10px]", "flex"]
294
299
  */
295
300
  /** Values that don't make sense in a fluid container pattern. Mirrors cssGeneration.ts. */
296
301
  const CONTAINER_RESERVED_VALUES = new Set(['auto', 'inherit', 'initial', 'unset', '']);
@@ -322,7 +327,7 @@ function transformContainerIntent(style: StyleObject): StyleObject {
322
327
  * In fluid mode, consume per-node mobile overrides for scalable properties
323
328
  * into the base's `clamp()` value. The mobile value becomes the clamp's MIN
324
329
  * (small-viewport endpoint), the base stays as MAX. The mobile property is
325
- * then stripped from the mobile slice so no `.mob-*` @media rule is emitted
330
+ * then stripped from the mobile slice so no `mobile:*` @media rule is emitted
326
331
  * for it — avoiding the jump at the mobile breakpoint that a hard override
327
332
  * would create.
328
333
  *
@@ -332,12 +337,12 @@ function transformContainerIntent(style: StyleObject): StyleObject {
332
337
  *
333
338
  * Bails out when units mismatch, parsing fails, or the registered values
334
339
  * aren't plain numeric+unit strings — those cases fall back to today's
335
- * behavior (mobile emits its own .mob-* rule).
340
+ * behavior (mobile emits its own `mobile:*` rule).
336
341
  */
337
342
  function consumeMobileIntoBaseClamp(
338
343
  base: StyleObject,
339
344
  mobile: StyleObject,
340
- responsiveScales: ResponsiveScales
345
+ responsiveScales: ResponsiveScales,
341
346
  ): { patchedBase: StyleObject; patchedMobile: StyleObject } {
342
347
  const fluidRange = responsiveScales.fluidRange ?? DEFAULT_FLUID_RANGE;
343
348
  const patchedBase: StyleObject = { ...base };
@@ -364,7 +369,7 @@ function consumeMobileIntoBaseClamp(
364
369
  baseParsed.value,
365
370
  baseParsed.unit,
366
371
  fluidRange.min,
367
- fluidRange.max
372
+ fluidRange.max,
368
373
  );
369
374
 
370
375
  patchedBase[prop] = clampValue;
@@ -376,7 +381,7 @@ function consumeMobileIntoBaseClamp(
376
381
 
377
382
  export function responsiveStylesToClasses(
378
383
  styles: StyleObject | ResponsiveStyleObject | null | undefined,
379
- options?: { fluidActive?: boolean; responsiveScales?: ResponsiveScales }
384
+ options?: { fluidActive?: boolean; responsiveScales?: ResponsiveScales },
380
385
  ): string[] {
381
386
  if (!styles) return [];
382
387
  const fluidActive = options?.fluidActive === true;
@@ -395,45 +400,35 @@ export function responsiveStylesToClasses(
395
400
  let baseSource = responsiveStyles.base;
396
401
  let mobileSource = responsiveStyles.mobile;
397
402
  if (fluidActive && responsiveScales?.enabled === true && baseSource && mobileSource) {
398
- const { patchedBase, patchedMobile } = consumeMobileIntoBaseClamp(
399
- baseSource,
400
- mobileSource,
401
- responsiveScales
402
- );
403
+ const { patchedBase, patchedMobile } = consumeMobileIntoBaseClamp(baseSource, mobileSource, responsiveScales);
403
404
  baseSource = patchedBase;
404
405
  mobileSource = patchedMobile;
405
406
  }
406
407
 
407
408
  // Base styles (no prefix)
408
409
  if (baseSource) {
409
- const base = fluidActive
410
- ? transformContainerIntent(baseSource)
411
- : baseSource;
410
+ const base = fluidActive ? transformContainerIntent(baseSource) : baseSource;
412
411
  classes.push(...stylesToClasses(base));
413
412
  }
414
413
 
415
- // Tablet styles (t- prefix)
414
+ // Tablet styles (tablet: variant prefix)
416
415
  if (responsiveStyles.tablet) {
417
- const tablet = fluidActive
418
- ? transformContainerIntent(responsiveStyles.tablet)
419
- : responsiveStyles.tablet;
416
+ const tablet = fluidActive ? transformContainerIntent(responsiveStyles.tablet) : responsiveStyles.tablet;
420
417
  const tabletClasses = stylesToClasses(tablet);
421
- classes.push(...tabletClasses.map((cls) => `t-${cls}`));
418
+ const prefix = breakpointClassPrefix('tablet');
419
+ classes.push(...tabletClasses.map((cls) => `${prefix}${cls}`));
422
420
  }
423
421
 
424
- // Mobile styles (mob- prefix to avoid conflict with margin)
422
+ // Mobile styles (mobile: variant prefix)
425
423
  if (mobileSource && Object.keys(mobileSource).length > 0) {
426
- const mobile = fluidActive
427
- ? transformContainerIntent(mobileSource)
428
- : mobileSource;
424
+ const mobile = fluidActive ? transformContainerIntent(mobileSource) : mobileSource;
429
425
  const mobileClasses = stylesToClasses(mobile);
430
- classes.push(...mobileClasses.map((cls) => `mob-${cls}`));
426
+ const prefix = breakpointClassPrefix('mobile');
427
+ classes.push(...mobileClasses.map((cls) => `${prefix}${cls}`));
431
428
  }
432
429
  } else {
433
430
  // Flat style object - treat all as base classes
434
- const flat = fluidActive
435
- ? transformContainerIntent(styles as StyleObject)
436
- : (styles as StyleObject);
431
+ const flat = fluidActive ? transformContainerIntent(styles as StyleObject) : (styles as StyleObject);
437
432
  classes.push(...stylesToClasses(flat));
438
433
  }
439
434
 
@@ -442,57 +437,58 @@ export function responsiveStylesToClasses(
442
437
 
443
438
  /**
444
439
  * Parse a utility class back to CSS property and value
445
- * Example: "p-10px" → { padding: "10px" }
440
+ * Example: "p-[10px]" → { prop: "padding", value: "10px" }
446
441
  */
447
- export function classToStyle(className: string): { prop: string; value: string } | null {
448
- // Remove responsive prefixes for parsing
449
- let cleanClass = className;
450
- let responsivePrefix = '';
451
-
452
- if (className.startsWith('t-')) {
453
- responsivePrefix = 't';
454
- cleanClass = className.slice(2);
455
- } else if (className.startsWith('mob-')) {
456
- responsivePrefix = 'mob';
457
- cleanClass = className.slice(4);
442
+ export function classToStyle(
443
+ className: string,
444
+ knownTokens?: ReadonlySet<string>,
445
+ ): { prop: string; value: string } | null {
446
+ // Remove responsive variant prefix for parsing (tablet:p-[10px] → p-[10px])
447
+ const { base: cleanClass } = splitVariantPrefix(className);
448
+
449
+ // Static utilities and presets have fixed reverse mappings
450
+ if (staticUtilityReverse[cleanClass]) {
451
+ return staticUtilityReverse[cleanClass];
458
452
  }
459
-
460
- // Check special cases from config
461
- if (classToStyleSpecialCases[cleanClass]) {
462
- return classToStyleSpecialCases[cleanClass];
453
+ if (presetClassReverse[cleanClass]) {
454
+ return presetClassReverse[cleanClass];
463
455
  }
464
456
 
465
- // Parse prefix-value pattern (e.g., "p-10px", "m-20px")
466
- const match = cleanClass.match(/^([a-z-]+)-(.*?)$/);
467
- if (!match) return null;
457
+ const parsed = parseUtilityClass(cleanClass, knownTokens);
468
458
 
469
- const [, prefix, value] = match;
470
-
471
- // Find the property from the prefix
472
- let prop = '';
473
- for (const [key, val] of Object.entries(propertyMap)) {
474
- if (val === prefix) {
475
- prop = key;
476
- break;
459
+ if (!parsed) {
460
+ // Dynamic (arbitrary-property hash) classes registered for unknown roots
461
+ const dynamic = getDynamicStyle(cleanClass);
462
+ if (dynamic) {
463
+ return { prop: cssPropertyToCamel(dynamic.property), value: String(dynamic.value) };
477
464
  }
465
+ return null;
478
466
  }
479
467
 
480
- if (!prop) return null;
468
+ if (parsed.kind === 'arbitrary-property') {
469
+ return { prop: cssPropertyToCamel(parsed.property), value: parsed.value! };
470
+ }
481
471
 
482
472
  // Hash-fallback class — value is `h<base36>`, the real CSS value lives in
483
473
  // the style-value registry (set when the class was minted from a complex
484
- // `clamp()` / `calc()` value that wouldn't fit in a CSS selector). Pull
485
- // the real value back out so editor reads see the original expression.
486
- if (/^h[0-9a-z]+$/.test(value)) {
474
+ // value that wouldn't fit in a class name). Pull the real value back out
475
+ // so editor reads see the original expression.
476
+ if (parsed.kind === 'hash') {
487
477
  const registered = getStyleValue(cleanClass);
488
- if (registered != null) {
489
- return { prop, value: String(registered) };
490
- }
478
+ if (registered == null) return null;
479
+ const value = String(registered);
480
+ const root = parsed.root!;
481
+ const prop = ROOT_CAMEL_OVERRIDES[root] ?? cssPropertyToCamel(resolveRootProperty(root, value));
482
+ return { prop, value };
491
483
  }
492
484
 
493
- // Handle CSS variables
494
- if (value.includes('background') || value.includes('text') || value.includes('border')) {
495
- return { prop, value: `var(--${value})` };
485
+ const root = parsed.root;
486
+ const prop = root && ROOT_CAMEL_OVERRIDES[root] ? ROOT_CAMEL_OVERRIDES[root] : cssPropertyToCamel(parsed.property);
487
+ let value = parsed.value!;
488
+
489
+ // Color token props store bare tokens (var(--primary) → "primary")
490
+ if (parsed.kind === 'var' && colorTokenProps.has(prop)) {
491
+ value = value.replace(/^var\(--([\w-]+)\)$/, '$1');
496
492
  }
497
493
 
498
494
  return { prop, value };
@@ -500,24 +496,18 @@ export function classToStyle(className: string): { prop: string; value: string }
500
496
 
501
497
  /**
502
498
  * Convert utility classes back to a style object
503
- * Example: ["p-10px", "t-p-24px"] → { base: { padding: "10px" }, tablet: { padding: "24px" } }
499
+ * Example: ["p-[10px]", "tablet:p-[24px]"] → { base: { padding: "10px" }, tablet: { padding: "24px" } }
504
500
  */
505
- export function classesToStyles(classes: string[]): ResponsiveStyleObject | null {
501
+ export function classesToStyles(classes: string[], knownTokens?: ReadonlySet<string>): ResponsiveStyleObject | null {
506
502
  const styles: ResponsiveStyleObject = { base: {}, tablet: {}, mobile: {} };
507
503
 
508
504
  for (const className of classes) {
509
- let breakpoint: 'base' | 'tablet' | 'mobile' = 'base';
510
- let cleanClass = className;
511
-
512
- if (className.startsWith('t-')) {
513
- breakpoint = 'tablet';
514
- cleanClass = className.slice(2);
515
- } else if (className.startsWith('mob-')) {
516
- breakpoint = 'mobile';
517
- cleanClass = className.slice(4);
518
- }
505
+ const { breakpoint: variant, base: cleanClass } = splitVariantPrefix(className);
506
+ // Accept both the current desktop-first form (`max-tablet:`) and the legacy bare form (`tablet:`).
507
+ const bp = normalizeBreakpointVariant(variant);
508
+ const breakpoint: 'base' | 'tablet' | 'mobile' = bp === 'tablet' ? 'tablet' : bp === 'mobile' ? 'mobile' : 'base';
519
509
 
520
- const styleEntry = classToStyle(cleanClass);
510
+ const styleEntry = classToStyle(cleanClass, knownTokens);
521
511
  if (styleEntry) {
522
512
  if (!styles[breakpoint]) {
523
513
  styles[breakpoint] = {};
@@ -530,5 +520,88 @@ export function classesToStyles(classes: string[]): ResponsiveStyleObject | null
530
520
  if (Object.keys(styles.tablet || {}).length === 0) delete styles.tablet;
531
521
  if (Object.keys(styles.mobile || {}).length === 0) delete styles.mobile;
532
522
 
533
- return Object.keys(styles.base || {}).length > 0 ? styles : null;
523
+ // Return the object when ANY breakpoint carries a declaration — not only base. A class string
524
+ // can legitimately style just a narrower breakpoint (e.g. "tablet:p-[16px]"), and dropping it to
525
+ // null there would lose class-string styling for that node (class-styling adapter, §11).
526
+ const hasAny =
527
+ Object.keys(styles.base || {}).length > 0 ||
528
+ Object.keys(styles.tablet || {}).length > 0 ||
529
+ Object.keys(styles.mobile || {}).length > 0;
530
+ return hasAny ? styles : null;
531
+ }
532
+
533
+ /**
534
+ * Whether a single style value can be stored as a STATIC utility class. Static strings (no
535
+ * `{{template}}`) and numbers can; template-bearing strings and `_mapping` objects cannot — a
536
+ * template would mint a broken `p-[{{x}}px]`, and a mapping is prop-driven. The latter must stay in
537
+ * the `style` object (rendered via the runtime var-bridge / `class:list`). This is the boundary that
538
+ * lets styling default to class storage while keeping dynamic values correct.
539
+ */
540
+ export function isClassableStyleValue(value: unknown): boolean {
541
+ if (typeof value === 'number') return true;
542
+ if (typeof value === 'string') return !value.includes('{{');
543
+ return false;
544
+ }
545
+
546
+ function splitStyleBucket(bucket: StyleObject): {
547
+ classable: StyleObject;
548
+ residual: StyleObject;
549
+ } {
550
+ const classable: StyleObject = {};
551
+ const residual: StyleObject = {};
552
+ for (const [k, v] of Object.entries(bucket)) {
553
+ if (isClassableStyleValue(v)) classable[k] = v as string | number;
554
+ else residual[k] = v as StyleMapping;
555
+ }
556
+ return { classable, residual };
557
+ }
558
+
559
+ /**
560
+ * Split a style value into the part storable as a utility-class string (static values) and the
561
+ * residual that must stay in the `style` object (templates + `_mapping`s). Preserves the responsive
562
+ * shape; a flat object is treated as `base`. Either side is null when it has no declarations. The
563
+ * canonical home (shared by the studio persistence boundary and the project-wide migration) for the
564
+ * "what can be a class" decision.
565
+ */
566
+ export function splitStyleByClassability(style: StyleValue | null | undefined): {
567
+ classable: ResponsiveStyleObject | null;
568
+ residual: ResponsiveStyleObject | null;
569
+ } {
570
+ if (!style || typeof style !== 'object') return { classable: null, residual: null };
571
+
572
+ if (isResponsiveStyle(style)) {
573
+ const classable: ResponsiveStyleObject = {};
574
+ const residual: ResponsiveStyleObject = {};
575
+ for (const [bp, bucket] of Object.entries(style)) {
576
+ if (!bucket || typeof bucket !== 'object') continue;
577
+ const split = splitStyleBucket(bucket);
578
+ if (Object.keys(split.classable).length > 0) classable[bp] = split.classable;
579
+ if (Object.keys(split.residual).length > 0) residual[bp] = split.residual;
580
+ }
581
+ return {
582
+ classable: Object.keys(classable).length > 0 ? classable : null,
583
+ residual: Object.keys(residual).length > 0 ? residual : null,
584
+ };
585
+ }
586
+
587
+ const split = splitStyleBucket(style as StyleObject);
588
+ return {
589
+ classable: Object.keys(split.classable).length > 0 ? { base: split.classable } : null,
590
+ residual: Object.keys(split.residual).length > 0 ? { base: split.residual } : null,
591
+ };
592
+ }
593
+
594
+ /**
595
+ * Whether a style value contains at least one prop `_mapping` (in any breakpoint) — flat or
596
+ * responsive. Prop-driven styling can't be a static utility class, so callers that gate class
597
+ * conversion (the studio panel, the project migration) check this. Single source of truth.
598
+ */
599
+ export function styleHasMapping(style: StyleValue | null | undefined): boolean {
600
+ if (!style || typeof style !== 'object') return false;
601
+ const buckets = isResponsiveStyle(style) ? Object.values(style) : [style as Record<string, unknown>];
602
+ for (const bucket of buckets) {
603
+ if (!bucket || typeof bucket !== 'object') continue;
604
+ for (const v of Object.values(bucket)) if (isStyleMapping(v)) return true;
605
+ }
606
+ return false;
534
607
  }