meno-core 1.0.53 → 1.0.54

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 (387) hide show
  1. package/.claude/settings.local.json +1 -3
  2. package/bin/cli.ts +48 -57
  3. package/build-astro.ts +131 -113
  4. package/build-next.ts +109 -96
  5. package/build-static.test.ts +39 -10
  6. package/build-static.ts +120 -122
  7. package/dist/bin/cli.js +34 -38
  8. package/dist/bin/cli.js.map +2 -2
  9. package/dist/build-static.js +12 -11
  10. package/dist/chunks/chunk-2AR55GYH.js +42 -0
  11. package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
  12. package/dist/chunks/{chunk-IGYR22T6.js → chunk-2FN4UOVO.js} +512 -239
  13. package/dist/chunks/chunk-2FN4UOVO.js.map +7 -0
  14. package/dist/chunks/chunk-3XER4E5W.js +168 -0
  15. package/dist/chunks/chunk-3XER4E5W.js.map +7 -0
  16. package/dist/chunks/{chunk-X754AHS5.js → chunk-5ETZFREW.js} +9 -12
  17. package/dist/chunks/chunk-5ETZFREW.js.map +7 -0
  18. package/dist/chunks/{chunk-2MHDV5BF.js → chunk-7E4IF5L7.js} +15 -21
  19. package/dist/chunks/chunk-7E4IF5L7.js.map +7 -0
  20. package/dist/chunks/{chunk-JGWFTO6P.js → chunk-7HWQUVTU.js} +1578 -1349
  21. package/dist/chunks/chunk-7HWQUVTU.js.map +7 -0
  22. package/dist/chunks/{chunk-O3NAGJP4.js → chunk-AE3QK5QW.js} +110 -21
  23. package/dist/chunks/chunk-AE3QK5QW.js.map +7 -0
  24. package/dist/chunks/{chunk-GZHGVVW3.js → chunk-F6KTJYGV.js} +7 -7
  25. package/dist/chunks/chunk-F6KTJYGV.js.map +7 -0
  26. package/dist/chunks/{chunk-WQFG7PAH.js → chunk-FZITJSSS.js} +2 -6
  27. package/dist/chunks/chunk-FZITJSSS.js.map +7 -0
  28. package/dist/chunks/{chunk-QB2LNO4W.js → chunk-GSYYA5GX.js} +2 -2
  29. package/dist/chunks/chunk-GSYYA5GX.js.map +7 -0
  30. package/dist/chunks/{chunk-YBLHKYFF.js → chunk-HIZMY3EP.js} +1 -1
  31. package/dist/chunks/chunk-HIZMY3EP.js.map +7 -0
  32. package/dist/chunks/{chunk-AZQYF6KE.js → chunk-I2WEGYA7.js} +41 -176
  33. package/dist/chunks/chunk-I2WEGYA7.js.map +7 -0
  34. package/dist/chunks/{chunk-I7YIGZXT.js → chunk-JNO3CNLJ.js} +6 -9
  35. package/dist/chunks/chunk-JNO3CNLJ.js.map +7 -0
  36. package/dist/chunks/{chunk-UB44F4Z2.js → chunk-NVRBTSQG.js} +2 -4
  37. package/dist/chunks/chunk-NVRBTSQG.js.map +7 -0
  38. package/dist/chunks/{chunk-JGP5A3Y5.js → chunk-Q4OBWKXG.js} +40 -33
  39. package/dist/chunks/chunk-Q4OBWKXG.js.map +7 -0
  40. package/dist/chunks/{chunk-R6XHAFBF.js → chunk-QTE32Y53.js} +250 -242
  41. package/dist/chunks/chunk-QTE32Y53.js.map +7 -0
  42. package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-STDY3OVM.js} +214 -85
  43. package/dist/chunks/chunk-STDY3OVM.js.map +7 -0
  44. package/dist/chunks/configService-PRJZF7Y6.js +14 -0
  45. package/dist/chunks/{constants-STK2YBIW.js → constants-KIQEYMAM.js} +2 -2
  46. package/dist/chunks/{fs-JGINUXGL.js → fs-ZI5JEU7V.js} +2 -2
  47. package/dist/entries/server-router.js +14 -19
  48. package/dist/entries/server-router.js.map +2 -2
  49. package/dist/lib/client/index.js +638 -332
  50. package/dist/lib/client/index.js.map +2 -2
  51. package/dist/lib/server/index.js +177 -235
  52. package/dist/lib/server/index.js.map +2 -2
  53. package/dist/lib/shared/index.js +80 -44
  54. package/dist/lib/shared/index.js.map +2 -2
  55. package/dist/lib/shared/richtext/index.js +1 -1
  56. package/dist/lib/test-utils/index.js +38 -60
  57. package/dist/lib/test-utils/index.js.map +2 -2
  58. package/entries/client-router.tsx +10 -8
  59. package/entries/server-router.tsx +1 -7
  60. package/lib/client/ClientInitializer.ts +8 -8
  61. package/lib/client/ErrorBoundary.test.tsx +146 -143
  62. package/lib/client/ErrorBoundary.tsx +175 -127
  63. package/lib/client/componentRegistry.test.ts +96 -108
  64. package/lib/client/componentRegistry.ts +1 -2
  65. package/lib/client/contexts/ThemeContext.tsx +3 -2
  66. package/lib/client/core/ComponentBuilder.test.ts +513 -560
  67. package/lib/client/core/ComponentBuilder.ts +318 -146
  68. package/lib/client/core/ComponentRenderer.test.tsx +1 -2
  69. package/lib/client/core/ComponentRenderer.tsx +46 -33
  70. package/lib/client/core/builders/embedBuilder.ts +241 -53
  71. package/lib/client/core/builders/linkBuilder.ts +71 -44
  72. package/lib/client/core/builders/linkNodeBuilder.ts +78 -53
  73. package/lib/client/core/builders/listBuilder.ts +115 -86
  74. package/lib/client/core/builders/localeListBuilder.ts +95 -60
  75. package/lib/client/core/builders/types.ts +5 -5
  76. package/lib/client/core/cmsTemplateProcessor.ts +7 -7
  77. package/lib/client/elementRegistry.ts +3 -3
  78. package/lib/client/fontFamiliesService.test.ts +2 -10
  79. package/lib/client/fontFamiliesService.ts +3 -3
  80. package/lib/client/hmr/HMRManager.tsx +8 -0
  81. package/lib/client/hmrCssReload.ts +14 -8
  82. package/lib/client/hmrWebSocket.ts +9 -14
  83. package/lib/client/hooks/useColorVariables.test.ts +21 -21
  84. package/lib/client/hooks/useColorVariables.ts +12 -10
  85. package/lib/client/hooks/usePropertyAutocomplete.ts +3 -5
  86. package/lib/client/hooks/useVariables.ts +4 -4
  87. package/lib/client/hydration/HydrationUtils.test.ts +24 -25
  88. package/lib/client/hydration/HydrationUtils.ts +3 -4
  89. package/lib/client/i18nConfigService.test.ts +2 -7
  90. package/lib/client/i18nConfigService.ts +2 -2
  91. package/lib/client/meno-filter/MenoFilter.test.ts +19 -21
  92. package/lib/client/meno-filter/MenoFilter.ts +5 -9
  93. package/lib/client/meno-filter/bindings.ts +15 -40
  94. package/lib/client/meno-filter/init.ts +1 -1
  95. package/lib/client/meno-filter/renderer.ts +23 -29
  96. package/lib/client/meno-filter/script.generated.ts +1 -3
  97. package/lib/client/meno-filter/ui.ts +3 -5
  98. package/lib/client/meno-filter/updates.ts +15 -21
  99. package/lib/client/navigation.test.ts +159 -159
  100. package/lib/client/navigation.ts +0 -1
  101. package/lib/client/responsiveStyleResolver.test.ts +230 -228
  102. package/lib/client/responsiveStyleResolver.ts +13 -16
  103. package/lib/client/routing/RouteLoader.test.ts +23 -24
  104. package/lib/client/routing/RouteLoader.ts +22 -35
  105. package/lib/client/routing/Router.tsx +31 -3
  106. package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
  107. package/lib/client/scripts/ScriptExecutor.ts +15 -33
  108. package/lib/client/services/PrefetchService.test.ts +2 -2
  109. package/lib/client/services/PrefetchService.ts +10 -24
  110. package/lib/client/styleProcessor.test.ts +9 -9
  111. package/lib/client/styleProcessor.ts +18 -15
  112. package/lib/client/styles/StyleInjector.test.ts +122 -115
  113. package/lib/client/styles/StyleInjector.ts +9 -6
  114. package/lib/client/styles/UtilityClassCollector.ts +19 -26
  115. package/lib/client/styles/cspNonce.test.ts +2 -5
  116. package/lib/client/templateEngine.test.ts +554 -482
  117. package/lib/client/templateEngine.ts +200 -64
  118. package/lib/client/theme.ts +0 -1
  119. package/lib/client/utils/toast.ts +0 -1
  120. package/lib/server/__integration__/api-routes.test.ts +8 -4
  121. package/lib/server/__integration__/cms-integration.test.ts +1 -4
  122. package/lib/server/__integration__/server-lifecycle.test.ts +2 -5
  123. package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
  124. package/lib/server/__integration__/static-assets.test.ts +1 -1
  125. package/lib/server/__integration__/test-helpers.ts +84 -70
  126. package/lib/server/ab/generateFunctions.ts +12 -10
  127. package/lib/server/astro/cmsPageEmitter.ts +23 -38
  128. package/lib/server/astro/componentEmitter.ts +25 -36
  129. package/lib/server/astro/cssCollector.ts +10 -26
  130. package/lib/server/astro/nodeToAstro.test.ts +1750 -30
  131. package/lib/server/astro/nodeToAstro.ts +198 -187
  132. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +105 -109
  133. package/lib/server/astro/normalizeOrphanTemplateProps.ts +4 -12
  134. package/lib/server/astro/pageEmitter.ts +9 -13
  135. package/lib/server/astro/tailwindMapper.test.ts +10 -37
  136. package/lib/server/astro/tailwindMapper.ts +33 -40
  137. package/lib/server/astro/templateTransformer.ts +14 -17
  138. package/lib/server/createServer.ts +5 -17
  139. package/lib/server/cssGenerator.test.ts +35 -44
  140. package/lib/server/cssGenerator.ts +6 -17
  141. package/lib/server/fileWatcher.test.ts +124 -10
  142. package/lib/server/fileWatcher.ts +124 -118
  143. package/lib/server/index.ts +7 -1
  144. package/lib/server/jsonLoader.test.ts +39 -2
  145. package/lib/server/jsonLoader.ts +33 -31
  146. package/lib/server/middleware/cors.test.ts +20 -20
  147. package/lib/server/middleware/cors.ts +28 -4
  148. package/lib/server/middleware/errorHandler.test.ts +5 -3
  149. package/lib/server/middleware/errorHandler.ts +3 -8
  150. package/lib/server/middleware/index.ts +0 -1
  151. package/lib/server/middleware/logger.test.ts +7 -5
  152. package/lib/server/middleware/logger.ts +10 -22
  153. package/lib/server/pageCache.test.ts +76 -77
  154. package/lib/server/pageCache.ts +0 -1
  155. package/lib/server/projectContext.ts +4 -3
  156. package/lib/server/providers/fileSystemCMSProvider.test.ts +124 -95
  157. package/lib/server/providers/fileSystemCMSProvider.ts +35 -20
  158. package/lib/server/providers/fileSystemPageProvider.test.ts +84 -0
  159. package/lib/server/providers/fileSystemPageProvider.ts +31 -12
  160. package/lib/server/routes/api/cms.test.ts +26 -14
  161. package/lib/server/routes/api/cms.ts +9 -14
  162. package/lib/server/routes/api/components.ts +30 -34
  163. package/lib/server/routes/api/config.ts +0 -1
  164. package/lib/server/routes/api/core-routes.ts +47 -74
  165. package/lib/server/routes/api/functions.ts +8 -16
  166. package/lib/server/routes/api/index.ts +3 -6
  167. package/lib/server/routes/api/pages.ts +8 -11
  168. package/lib/server/routes/api/shared.test.ts +1 -1
  169. package/lib/server/routes/api/shared.ts +12 -9
  170. package/lib/server/routes/api/variables.test.ts +1 -3
  171. package/lib/server/routes/api/variables.ts +1 -1
  172. package/lib/server/routes/index.ts +23 -26
  173. package/lib/server/routes/pages.ts +34 -29
  174. package/lib/server/routes/static.ts +16 -4
  175. package/lib/server/runtime/bundler.ts +47 -32
  176. package/lib/server/runtime/fs.ts +3 -13
  177. package/lib/server/runtime/httpServer.ts +5 -9
  178. package/lib/server/services/ColorService.ts +32 -27
  179. package/lib/server/services/EnumService.test.ts +2 -6
  180. package/lib/server/services/EnumService.ts +7 -2
  181. package/lib/server/services/VariableService.test.ts +1 -5
  182. package/lib/server/services/VariableService.ts +6 -1
  183. package/lib/server/services/cmsService.test.ts +116 -78
  184. package/lib/server/services/cmsService.ts +24 -54
  185. package/lib/server/services/componentService.test.ts +122 -36
  186. package/lib/server/services/componentService.ts +54 -38
  187. package/lib/server/services/configService.test.ts +9 -31
  188. package/lib/server/services/configService.ts +20 -27
  189. package/lib/server/services/fileWatcherService.ts +40 -6
  190. package/lib/server/services/index.ts +0 -1
  191. package/lib/server/services/pageService.test.ts +1 -3
  192. package/lib/server/services/pageService.ts +14 -13
  193. package/lib/server/ssr/attributeBuilder.ts +16 -6
  194. package/lib/server/ssr/buildErrorOverlay.ts +11 -12
  195. package/lib/server/ssr/clientDataInjector.ts +7 -21
  196. package/lib/server/ssr/cmsSSRProcessor.ts +3 -6
  197. package/lib/server/ssr/cssCollector.ts +1 -1
  198. package/lib/server/ssr/errorOverlay.test.ts +1 -1
  199. package/lib/server/ssr/errorOverlay.ts +3 -9
  200. package/lib/server/ssr/htmlGenerator.nonce.test.ts +3 -9
  201. package/lib/server/ssr/htmlGenerator.test.ts +120 -43
  202. package/lib/server/ssr/htmlGenerator.ts +120 -85
  203. package/lib/server/ssr/imageMetadata.test.ts +3 -1
  204. package/lib/server/ssr/imageMetadata.ts +25 -19
  205. package/lib/server/ssr/jsCollector.test.ts +3 -13
  206. package/lib/server/ssr/jsCollector.ts +3 -8
  207. package/lib/server/ssr/liveReloadIntegration.test.ts +69 -28
  208. package/lib/server/ssr/metaTagGenerator.ts +2 -2
  209. package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
  210. package/lib/server/ssr/ssrRenderer.test.ts +196 -246
  211. package/lib/server/ssr/ssrRenderer.ts +609 -225
  212. package/lib/server/ssrRenderer.test.ts +1044 -950
  213. package/lib/server/utils/jsonLineMapper.test.ts +28 -28
  214. package/lib/server/utils/jsonLineMapper.ts +1 -1
  215. package/lib/server/validateStyleCoverage.ts +18 -20
  216. package/lib/server/webflow/buildWebflow.ts +41 -53
  217. package/lib/server/webflow/nodeToWebflow.test.ts +150 -218
  218. package/lib/server/webflow/nodeToWebflow.ts +195 -258
  219. package/lib/server/webflow/styleMapper.test.ts +15 -56
  220. package/lib/server/webflow/styleMapper.ts +33 -41
  221. package/lib/server/webflow/types.ts +1 -8
  222. package/lib/server/websocketManager.ts +16 -19
  223. package/lib/shared/attributeNodeUtils.test.ts +15 -15
  224. package/lib/shared/attributeNodeUtils.ts +5 -12
  225. package/lib/shared/breakpoints.ts +4 -11
  226. package/lib/shared/cmsQueryParser.test.ts +50 -42
  227. package/lib/shared/cmsQueryParser.ts +49 -31
  228. package/lib/shared/colorVariableUtils.test.ts +5 -5
  229. package/lib/shared/colorVariableUtils.ts +3 -8
  230. package/lib/shared/componentRefs.ts +1 -5
  231. package/lib/shared/constants.test.ts +3 -3
  232. package/lib/shared/constants.ts +4 -8
  233. package/lib/shared/cssGeneration.test.ts +262 -144
  234. package/lib/shared/cssGeneration.ts +189 -516
  235. package/lib/shared/cssNamedColors.ts +152 -30
  236. package/lib/shared/cssProperties.test.ts +5 -6
  237. package/lib/shared/cssProperties.ts +296 -112
  238. package/lib/shared/elementClassName.test.ts +109 -109
  239. package/lib/shared/elementClassName.ts +1 -1
  240. package/lib/shared/elementUtils.ts +12 -16
  241. package/lib/shared/errorLogger.ts +2 -10
  242. package/lib/shared/errors.test.ts +2 -13
  243. package/lib/shared/errors.ts +2 -8
  244. package/lib/shared/expressionEvaluator.test.ts +119 -0
  245. package/lib/shared/expressionEvaluator.ts +43 -24
  246. package/lib/shared/fontLoader.test.ts +19 -5
  247. package/lib/shared/friendlyError.ts +2 -3
  248. package/lib/shared/gradientUtils.test.ts +1 -5
  249. package/lib/shared/gradientUtils.ts +2 -6
  250. package/lib/shared/hrefRefs.ts +2 -10
  251. package/lib/shared/i18n.test.ts +1 -1
  252. package/lib/shared/i18n.ts +13 -34
  253. package/lib/shared/index.ts +4 -0
  254. package/lib/shared/inlineSvgStyleRules.ts +2 -4
  255. package/lib/shared/interactiveStyleMappings.test.ts +11 -33
  256. package/lib/shared/interactiveStyleMappings.ts +9 -16
  257. package/lib/shared/interactiveStyles.test.ts +165 -188
  258. package/lib/shared/interfaces/contentProvider.ts +1 -1
  259. package/lib/shared/itemTemplateUtils.test.ts +6 -12
  260. package/lib/shared/itemTemplateUtils.ts +19 -35
  261. package/lib/shared/jsonRepair.ts +8 -2
  262. package/lib/shared/libraryLoader.test.ts +15 -49
  263. package/lib/shared/libraryLoader.ts +7 -22
  264. package/lib/shared/netlifyLocale404.test.ts +179 -0
  265. package/lib/shared/netlifyLocale404.ts +110 -0
  266. package/lib/shared/nodeUtils.test.ts +24 -16
  267. package/lib/shared/nodeUtils.ts +49 -19
  268. package/lib/shared/pathArrayUtils.test.ts +1 -2
  269. package/lib/shared/pathArrayUtils.ts +1 -1
  270. package/lib/shared/pathSecurity.ts +1 -1
  271. package/lib/shared/pathUtils.test.ts +4 -6
  272. package/lib/shared/pathUtils.ts +42 -48
  273. package/lib/shared/paths/Path.test.ts +2 -2
  274. package/lib/shared/paths/Path.ts +0 -1
  275. package/lib/shared/paths/PathConverter.test.ts +1 -1
  276. package/lib/shared/paths/PathConverter.ts +14 -17
  277. package/lib/shared/paths/PathUtils.ts +9 -10
  278. package/lib/shared/paths/PathValidator.test.ts +2 -15
  279. package/lib/shared/paths/PathValidator.ts +11 -9
  280. package/lib/shared/paths/index.ts +1 -2
  281. package/lib/shared/propResolver.test.ts +240 -244
  282. package/lib/shared/propResolver.ts +14 -25
  283. package/lib/shared/pxToRem.test.ts +7 -6
  284. package/lib/shared/pxToRem.ts +2 -5
  285. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +9 -5
  286. package/lib/shared/registry/ClientRegistry.ts +0 -1
  287. package/lib/shared/registry/ComponentRegistry.test.ts +43 -29
  288. package/lib/shared/registry/ComponentRegistry.ts +9 -11
  289. package/lib/shared/registry/NodeTypeDefinition.ts +15 -8
  290. package/lib/shared/registry/RegistryManager.ts +1 -2
  291. package/lib/shared/registry/SSRRegistry.ts +0 -1
  292. package/lib/shared/registry/createNodeType.ts +7 -9
  293. package/lib/shared/registry/defineNodeType.ts +2 -6
  294. package/lib/shared/registry/index.ts +0 -1
  295. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
  296. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
  297. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +47 -18
  298. package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
  299. package/lib/shared/registry/nodeTypes/ListNodeType.ts +78 -74
  300. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
  301. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +6 -7
  302. package/lib/shared/registry/nodeTypes/index.ts +10 -2
  303. package/lib/shared/responsiveScaling.test.ts +15 -31
  304. package/lib/shared/responsiveScaling.ts +55 -37
  305. package/lib/shared/responsiveStyleUtils.ts +11 -13
  306. package/lib/shared/richtext/htmlToTiptap.test.ts +23 -14
  307. package/lib/shared/richtext/htmlToTiptap.ts +1 -3
  308. package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
  309. package/lib/shared/richtext/types.ts +1 -8
  310. package/lib/shared/slugTranslator.test.ts +13 -13
  311. package/lib/shared/slugTranslator.ts +12 -16
  312. package/lib/shared/slugify.ts +9 -15
  313. package/lib/shared/styleNodeUtils.test.ts +8 -8
  314. package/lib/shared/styleNodeUtils.ts +5 -10
  315. package/lib/shared/styleUtils.test.ts +87 -61
  316. package/lib/shared/styleUtils.ts +5 -6
  317. package/lib/shared/themeDefaults.test.ts +11 -11
  318. package/lib/shared/themeDefaults.ts +3 -4
  319. package/lib/shared/tree/PathBuilder.test.ts +62 -138
  320. package/lib/shared/tree/PathBuilder.ts +49 -39
  321. package/lib/shared/treePathUtils.test.ts +2 -10
  322. package/lib/shared/treePathUtils.ts +54 -59
  323. package/lib/shared/types/api.ts +1 -2
  324. package/lib/shared/types/cms.ts +25 -21
  325. package/lib/shared/types/comment.ts +49 -16
  326. package/lib/shared/types/components.ts +27 -25
  327. package/lib/shared/types/errors.test.ts +1 -6
  328. package/lib/shared/types/errors.ts +3 -7
  329. package/lib/shared/types/experiments.ts +28 -28
  330. package/lib/shared/types/index.ts +2 -2
  331. package/lib/shared/types/styles.ts +0 -1
  332. package/lib/shared/types/variables.test.ts +4 -13
  333. package/lib/shared/types/variables.ts +48 -27
  334. package/lib/shared/types.ts +1 -2
  335. package/lib/shared/utilityClassConfig.ts +648 -321
  336. package/lib/shared/utilityClassMapper.test.ts +203 -92
  337. package/lib/shared/utilityClassMapper.ts +188 -246
  338. package/lib/shared/utilityClassNames.ts +326 -0
  339. package/lib/shared/utils.test.ts +2 -10
  340. package/lib/shared/utils.ts +19 -10
  341. package/lib/shared/validation/cmsValidators.ts +2 -1
  342. package/lib/shared/validation/commentValidators.test.ts +53 -0
  343. package/lib/shared/validation/commentValidators.ts +12 -1
  344. package/lib/shared/validation/propValidator.test.ts +18 -20
  345. package/lib/shared/validation/propValidator.ts +12 -17
  346. package/lib/shared/validation/schemas.test.ts +24 -33
  347. package/lib/shared/validation/schemas.ts +469 -344
  348. package/lib/shared/validation/validators.test.ts +1 -6
  349. package/lib/shared/validation/validators.ts +89 -68
  350. package/lib/shared/viewportUnits.integration.test.ts +14 -10
  351. package/lib/shared/viewportUnits.test.ts +11 -23
  352. package/lib/test-utils/dom-setup.ts +1 -1
  353. package/lib/test-utils/factories/ConsoleMockFactory.ts +3 -7
  354. package/lib/test-utils/factories/DomMockFactory.ts +7 -19
  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 +5 -9
  358. package/lib/test-utils/factories/StoreMockFactory.ts +14 -25
  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 +12 -18
  365. package/lib/test-utils/mocks.ts +4 -2
  366. package/package.json +1 -1
  367. package/scripts/build-meno-filter.ts +1 -4
  368. package/vite.config.ts +4 -4
  369. package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
  370. package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
  371. package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
  372. package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
  373. package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
  374. package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
  375. package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
  376. package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
  377. package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
  378. package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
  379. package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
  380. package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
  381. package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
  382. package/dist/chunks/chunk-X754AHS5.js.map +0 -7
  383. package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
  384. package/dist/chunks/configService-R3OGU2UD.js +0 -13
  385. /package/dist/chunks/{configService-R3OGU2UD.js.map → configService-PRJZF7Y6.js.map} +0 -0
  386. /package/dist/chunks/{constants-STK2YBIW.js.map → constants-KIQEYMAM.js.map} +0 -0
  387. /package/dist/chunks/{fs-JGINUXGL.js.map → fs-ZI5JEU7V.js.map} +0 -0
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Tailwind-style utility class name parsing & value encoding.
3
+ *
4
+ * Shared by the forward mapper (utilityClassMapper.ts) and the CSS generator
5
+ * (cssGeneration.ts) so class names encode and decode identically everywhere.
6
+ *
7
+ * Class name grammar (after stripping an optional `breakpoint:` variant):
8
+ * - static utilities: `flex`, `hidden`, `justify-center`, `uppercase`, …
9
+ * - presets: `shadow-2`, `bg-gradient-1`, `border-preset-0`
10
+ * - arbitrary values: `p-[40px]`, `w-[50%]`, `m-[92px_0]` (spaces → `_`)
11
+ * - CSS variables: `bg-(--primary)`, `text-(length:--font-size-lg)`
12
+ * - named keywords: `w-full`, `h-screen`, `m-auto`, `rounded-full`
13
+ * - bare numbers: `z-10`, `order-2`, `opacity-50` (= 0.5)
14
+ * - arbitrary properties: `[clip-path:circle(50%)]`, `[background:…]`
15
+ * - hash fallback: `pt-h1y9pr6i` (value lives in the style-value registry)
16
+ */
17
+
18
+ import {
19
+ prefixToCSSProperty,
20
+ staticUtilityReverse,
21
+ presetClassReverse,
22
+ keywordValues,
23
+ genericKeywordRoots,
24
+ integerValueRoots,
25
+ propertyOrder,
26
+ } from './utilityClassConfig';
27
+ import { isCssNamedColor } from './cssNamedColors';
28
+
29
+ /** Class roots sorted longest-first so `border-t-…` wins over `border-…`. */
30
+ export const SORTED_ROOTS: readonly string[] = Object.keys(prefixToCSSProperty).sort((a, b) => b.length - a.length);
31
+
32
+ /** Convert a camelCase style property to its kebab-case CSS name. */
33
+ export function camelToKebab(prop: string): string {
34
+ return prop.replace(/([A-Z])/g, '-$1').toLowerCase();
35
+ }
36
+
37
+ // kebab-case CSS property → camelCase style property, for all known properties.
38
+ const kebabToCamelMap: Record<string, string> = (() => {
39
+ const map: Record<string, string> = {};
40
+ for (const prop of propertyOrder) {
41
+ map[camelToKebab(prop)] = prop;
42
+ }
43
+ return map;
44
+ })();
45
+
46
+ /** Convert a kebab-case CSS property back to its camelCase style property. */
47
+ export function cssPropertyToCamel(property: string): string {
48
+ return kebabToCamelMap[property] ?? property.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
49
+ }
50
+
51
+ /**
52
+ * Roots that decode to a different camelCase property than their kebab CSS
53
+ * property suggests (px → paddingInline, not padding).
54
+ */
55
+ export const ROOT_CAMEL_OVERRIDES: Record<string, string> = {
56
+ px: 'paddingInline',
57
+ py: 'paddingBlock',
58
+ mx: 'marginInline',
59
+ my: 'marginBlock',
60
+ };
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Arbitrary value encoding (Tailwind bracket convention: spaces → underscores)
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /**
67
+ * Apply `transform` to every part of `input` that is NOT inside a `url(...)`
68
+ * segment. Tailwind preserves underscores inside url() — so do we, in both
69
+ * encode and decode directions.
70
+ */
71
+ function transformOutsideUrl(input: string, transform: (segment: string) => string): string {
72
+ let result = '';
73
+ let i = 0;
74
+ const lower = input.toLowerCase();
75
+ while (i < input.length) {
76
+ const idx = lower.indexOf('url(', i);
77
+ if (idx === -1) {
78
+ result += transform(input.slice(i));
79
+ break;
80
+ }
81
+ result += transform(input.slice(i, idx));
82
+ // Copy the url(...) segment verbatim up to its matching close paren
83
+ let depth = 0;
84
+ let j = idx + 3; // position of '('
85
+ for (; j < input.length; j++) {
86
+ if (input[j] === '(') depth++;
87
+ else if (input[j] === ')') {
88
+ depth--;
89
+ if (depth === 0) {
90
+ j++;
91
+ break;
92
+ }
93
+ }
94
+ }
95
+ result += input.slice(idx, j);
96
+ i = j;
97
+ }
98
+ return result;
99
+ }
100
+
101
+ /**
102
+ * Encode a CSS value for a Tailwind arbitrary-value class: spaces become
103
+ * underscores (`92px 0` → `92px_0`). Returns null when the value can't be
104
+ * encoded reversibly (literal underscores outside url(), quotes, brackets,
105
+ * backslashes, or whitespace inside url()) — callers fall back to a
106
+ * hash class + style-value registry entry.
107
+ */
108
+ export function encodeArbitraryValue(value: string): string | null {
109
+ let ok = true;
110
+ const encoded = transformOutsideUrl(value, (segment) => {
111
+ if (/[_"'\\[\]]/.test(segment)) ok = false;
112
+ return segment.replace(/\s+/g, '_');
113
+ });
114
+ if (!ok) return null;
115
+ // Whitespace/quotes surviving here can only come from inside url(...) —
116
+ // they would break the class attribute or the decode round-trip.
117
+ if (/[\s"'\\[\]]/.test(encoded.replace(/^\[|\]$/g, ''))) return null;
118
+ return encoded;
119
+ }
120
+
121
+ /** Decode a Tailwind arbitrary value: underscores → spaces (except inside url()). */
122
+ export function decodeArbitraryValue(encoded: string): string {
123
+ return transformOutsideUrl(encoded, (segment) => segment.replace(/_/g, ' '));
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Value-type classification (for roots shared by several properties)
128
+ // ---------------------------------------------------------------------------
129
+
130
+ const FONT_SIZE_KEYWORDS = new Set([
131
+ 'xx-small',
132
+ 'x-small',
133
+ 'small',
134
+ 'medium',
135
+ 'large',
136
+ 'x-large',
137
+ 'xx-large',
138
+ 'xxx-large',
139
+ 'larger',
140
+ 'smaller',
141
+ ]);
142
+
143
+ /** True when a value reads as a color (hex, color function, named color, var()). */
144
+ export function isColorValue(value: string): boolean {
145
+ if (value.startsWith('#') || value.startsWith('var(')) return true;
146
+ if (/^(rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|hwb|color|color-mix)\(/i.test(value)) return true;
147
+ if (value === 'transparent' || value === 'currentColor' || value === 'currentcolor') return true;
148
+ return isCssNamedColor(value);
149
+ }
150
+
151
+ /** True when a `text-…` value reads as a font-size (length/number/keyword). */
152
+ function isFontSizeValue(value: string): boolean {
153
+ if (/^[\d.+-]/.test(value)) return true;
154
+ if (/^(calc|clamp|min|max)\(/i.test(value)) return true;
155
+ return FONT_SIZE_KEYWORDS.has(value);
156
+ }
157
+
158
+ /** True when a `font-…` value reads as a font-weight (numeric). */
159
+ function isFontWeightValue(value: string): boolean {
160
+ return /^\d+(\.\d+)?$/.test(value);
161
+ }
162
+
163
+ /** True when a `bg-…` value reads as a background-image (url()/gradients). */
164
+ function isImageValue(value: string): boolean {
165
+ return /^(url|linear-gradient|radial-gradient|conic-gradient|repeating-linear-gradient|repeating-radial-gradient|repeating-conic-gradient|image-set|cross-fade|element)\(/i.test(
166
+ value,
167
+ );
168
+ }
169
+
170
+ /** True for a single length token like `2px`, `0.5rem`, `-4px`. */
171
+ function isLengthValue(value: string): boolean {
172
+ return /^-?[\d.]+[a-z%]*$/i.test(value);
173
+ }
174
+
175
+ /**
176
+ * Resolve the CSS property for a class root, disambiguating Tailwind's shared
177
+ * roots by value type (`text-[14px]` → font-size, `text-[#fff]` → color, …).
178
+ */
179
+ export function resolveRootProperty(root: string, value: string): string {
180
+ switch (root) {
181
+ case 'text':
182
+ return isFontSizeValue(value) ? 'font-size' : 'color';
183
+ case 'font':
184
+ return isFontWeightValue(value) ? 'font-weight' : 'font-family';
185
+ case 'bg':
186
+ return isImageValue(value) ? 'background-image' : 'background-color';
187
+ case 'border':
188
+ return !value.includes(' ') && isColorValue(value) ? 'border-color' : 'border';
189
+ case 'outline': {
190
+ if (value.includes(' ') || value === 'none') return 'outline';
191
+ if (isColorValue(value)) return 'outline-color';
192
+ if (isLengthValue(value)) return 'outline-width';
193
+ return 'outline';
194
+ }
195
+ default:
196
+ return prefixToCSSProperty[root] ?? root;
197
+ }
198
+ }
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // Breakpoint variant prefix (`tablet:p-[24px]`)
202
+ // ---------------------------------------------------------------------------
203
+
204
+ /**
205
+ * Split an optional `breakpoint:` variant prefix off a class name.
206
+ * Arbitrary-property classes (`[grid-area:hero]`) never match — their colon
207
+ * sits inside brackets, and the prefix segment must be a bare identifier.
208
+ */
209
+ export function splitVariantPrefix(className: string): { breakpoint: string | null; base: string } {
210
+ const match = className.match(/^([a-z][a-z0-9_-]*):(.+)$/);
211
+ if (match) {
212
+ return { breakpoint: match[1], base: match[2] };
213
+ }
214
+ return { breakpoint: null, base: className };
215
+ }
216
+
217
+ /** Build the class variant prefix for a breakpoint name: `tablet` → `tablet:`. */
218
+ export function breakpointClassPrefix(breakpointName: string): string {
219
+ return `${breakpointName.toLowerCase()}:`;
220
+ }
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // Class name parsing
224
+ // ---------------------------------------------------------------------------
225
+
226
+ export interface ParsedUtilityClass {
227
+ /** kebab-case CSS property the class targets */
228
+ property: string;
229
+ /** Decoded CSS value; null for hash classes (resolve via style-value registry) */
230
+ value: string | null;
231
+ /** The class root (`p`, `text`, `bg`, …); null for statics/presets/arbitrary properties */
232
+ root: string | null;
233
+ kind: 'static' | 'preset' | 'arbitrary' | 'var' | 'keyword' | 'numeric' | 'hash' | 'arbitrary-property';
234
+ }
235
+
236
+ const HASH_VALUE_RE = /^h[0-9a-z]+$/;
237
+
238
+ /**
239
+ * Parse a single (unprefixed) utility class name into its CSS property and
240
+ * decoded value. Returns null for class names that aren't Meno utilities.
241
+ */
242
+ export function parseUtilityClass(className: string): ParsedUtilityClass | null {
243
+ const stat = staticUtilityReverse[className];
244
+ if (stat) {
245
+ return { property: camelToKebab(stat.prop), value: stat.value, root: null, kind: 'static' };
246
+ }
247
+
248
+ const preset = presetClassReverse[className];
249
+ if (preset) {
250
+ return { property: camelToKebab(preset.prop), value: preset.value, root: null, kind: 'preset' };
251
+ }
252
+
253
+ // Arbitrary property: [clip-path:circle(50%)], [background:linear-gradient(…)]
254
+ const arbProp = className.match(/^\[([a-z-]+):(.+)\]$/);
255
+ if (arbProp) {
256
+ return {
257
+ property: arbProp[1],
258
+ value: decodeArbitraryValue(arbProp[2]),
259
+ root: null,
260
+ kind: 'arbitrary-property',
261
+ };
262
+ }
263
+
264
+ for (const root of SORTED_ROOTS) {
265
+ if (!className.startsWith(`${root}-`)) continue;
266
+ const rest = className.slice(root.length + 1);
267
+ if (!rest) continue;
268
+ const parsed = parseRootValue(root, rest);
269
+ if (parsed) return parsed;
270
+ }
271
+
272
+ return null;
273
+ }
274
+
275
+ function parseRootValue(root: string, rest: string): ParsedUtilityClass | null {
276
+ // Arbitrary value: root-[value]
277
+ if (rest.startsWith('[') && rest.endsWith(']') && rest.length > 2) {
278
+ const value = decodeArbitraryValue(rest.slice(1, -1));
279
+ return { property: resolveRootProperty(root, value), value, root, kind: 'arbitrary' };
280
+ }
281
+
282
+ // CSS variable: root-(--name) / root-(length:--name)
283
+ const varMatch = rest.match(/^\((?:([a-z-]+):)?(--[\w-]+)\)$/);
284
+ if (varMatch) {
285
+ const hint = varMatch[1];
286
+ const value = `var(${varMatch[2]})`;
287
+ let property: string;
288
+ if (root === 'text') {
289
+ property = hint === 'length' ? 'font-size' : 'color';
290
+ } else if (root === 'font') {
291
+ // `font-*` is shared by font-weight and font-family; a var value is ambiguous, so the
292
+ // emitter tags weight vars with a `number:` hint (see utilityClassMapper). Default to
293
+ // font-family (the original behavior for an un-hinted var like `font-(--l-ff)`).
294
+ property = hint === 'number' ? 'font-weight' : 'font-family';
295
+ } else {
296
+ property = resolveRootProperty(root, value);
297
+ }
298
+ return { property, value, root, kind: 'var' };
299
+ }
300
+
301
+ // Named keyword: w-full, m-auto, rounded-full
302
+ const keyword = keywordValues[root]?.[rest];
303
+ if (keyword !== undefined) {
304
+ return { property: resolveRootProperty(root, keyword), value: keyword, root, kind: 'keyword' };
305
+ }
306
+
307
+ // Generic keyword roots: cursor-pointer, align-middle, origin-center, …
308
+ if (genericKeywordRoots.has(root) && /^[a-z][a-z-]*$/.test(rest)) {
309
+ return { property: resolveRootProperty(root, rest), value: rest, root, kind: 'keyword' };
310
+ }
311
+
312
+ // Bare numbers: z-10, order-2, flex-2; opacity uses Tailwind's 0–100 scale
313
+ if (integerValueRoots.has(root) && /^-?\d+$/.test(rest)) {
314
+ return { property: prefixToCSSProperty[root], value: rest, root, kind: 'numeric' };
315
+ }
316
+ if (root === 'opacity' && /^\d{1,3}$/.test(rest)) {
317
+ return { property: 'opacity', value: String(Number(rest) / 100), root, kind: 'numeric' };
318
+ }
319
+
320
+ // Hash fallback: value lives in the style-value registry
321
+ if (HASH_VALUE_RE.test(rest)) {
322
+ return { property: prefixToCSSProperty[root], value: null, root, kind: 'hash' };
323
+ }
324
+
325
+ return null;
326
+ }
@@ -244,18 +244,10 @@ describe('utils', () => {
244
244
 
245
245
  test('should handle complex children array', () => {
246
246
  const node = {
247
- children: [
248
- { element: 'div' },
249
- { element: 'span' },
250
- 'text',
251
- ],
247
+ children: [{ element: 'div' }, { element: 'span' }, 'text'],
252
248
  };
253
249
  normalizeChildrenArray(node);
254
- expect(node.children).toEqual([
255
- { element: 'div' },
256
- { element: 'span' },
257
- 'text',
258
- ]);
250
+ expect(node.children).toEqual([{ element: 'div' }, { element: 'span' }, 'text']);
259
251
  });
260
252
  });
261
253
  });
@@ -20,7 +20,7 @@ export function deepClone<T>(obj: T): T {
20
20
  * @param ms - Milliseconds to wait
21
21
  */
22
22
  export function wait(ms: number): Promise<void> {
23
- return new Promise(resolve => setTimeout(resolve, ms));
23
+ return new Promise((resolve) => setTimeout(resolve, ms));
24
24
  }
25
25
 
26
26
  /**
@@ -50,7 +50,20 @@ export function isEditableElement(): boolean {
50
50
  // For inputs, only block shortcuts for text-accepting types
51
51
  if (activeElement.tagName === 'INPUT') {
52
52
  const inputType = (activeElement as HTMLInputElement).type.toLowerCase();
53
- const textInputTypes = ['text', 'password', 'email', 'number', 'search', 'tel', 'url', 'date', 'time', 'datetime-local', 'month', 'week'];
53
+ const textInputTypes = [
54
+ 'text',
55
+ 'password',
56
+ 'email',
57
+ 'number',
58
+ 'search',
59
+ 'tel',
60
+ 'url',
61
+ 'date',
62
+ 'time',
63
+ 'datetime-local',
64
+ 'month',
65
+ 'week',
66
+ ];
54
67
  // Allow shortcuts for: checkbox, radio, button, submit, reset, color, file, range, hidden
55
68
  return textInputTypes.includes(inputType);
56
69
  }
@@ -59,10 +72,7 @@ export function isEditableElement(): boolean {
59
72
  // This handles rich text editors where focus may be on an internal element
60
73
  let element: Element | null = activeElement;
61
74
  while (element) {
62
- if (
63
- element.hasAttribute('contenteditable') ||
64
- (element as HTMLElement).isContentEditable
65
- ) {
75
+ if (element.hasAttribute('contenteditable') || (element as HTMLElement).isContentEditable) {
66
76
  return true;
67
77
  }
68
78
  element = element.parentElement;
@@ -75,7 +85,7 @@ export function isEditableElement(): boolean {
75
85
  * Extract the parent path from a tree path
76
86
  * Example: root_0_children_0 -> root_0, root_0_children_0_children_1 -> root_0_children_0
77
87
  * Also accepts array paths: [0,0] -> "root_0", [0,0,1] -> "root_0_children_0"
78
- *
88
+ *
79
89
  * @param treePath - The tree path (string or Path array)
80
90
  * @param rootPath - The root path constant (default: 'root_0')
81
91
  * @returns The parent path, or rootPath if no parent found
@@ -83,12 +93,12 @@ export function isEditableElement(): boolean {
83
93
  export function getParentPathFromTreePath(treePath: string | Path, rootPath: string = ROOT_0_STRING): string {
84
94
  // Convert to array if needed
85
95
  const pathArray = typeof treePath === 'string' ? stringToPath(treePath) : treePath;
86
-
96
+
87
97
  // Handle root paths
88
98
  if (isRootPathArray(pathArray)) {
89
99
  return rootPath;
90
100
  }
91
-
101
+
92
102
  // Use array operations to get parent path
93
103
  const parentArray = getParentPath(pathArray);
94
104
  return pathToLegacyString(parentArray);
@@ -109,4 +119,3 @@ export function normalizeChildrenArray(node: { children?: any[] | any }): void {
109
119
  }
110
120
  // If already an array, no change needed
111
121
  }
112
-
@@ -14,7 +14,8 @@ export const CMS_COLLECTION_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
14
14
  /**
15
15
  * Zod schema for CMS collection ID validation
16
16
  */
17
- export const CMSCollectionIdSchema = z.string()
17
+ export const CMSCollectionIdSchema = z
18
+ .string()
18
19
  .min(1, 'Collection ID is required')
19
20
  .regex(CMS_COLLECTION_ID_REGEX, 'Collection ID must contain only letters, numbers, underscores, and hyphens');
20
21
 
@@ -0,0 +1,53 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { validateComment } from './commentValidators';
3
+
4
+ /** A minimal valid comment; callers override `anchor` to exercise schema variants. */
5
+ function baseComment(anchor: Record<string, unknown>): Record<string, unknown> {
6
+ return {
7
+ _id: 'c1',
8
+ _filename: 'c1.json',
9
+ _pagePath: 'index',
10
+ _seq: 1,
11
+ _createdAt: '2026-01-01T00:00:00.000Z',
12
+ _updatedAt: '2026-01-01T00:00:00.000Z',
13
+ _commitSha: null,
14
+ anchor,
15
+ status: 'open',
16
+ thread: [
17
+ {
18
+ id: 't1',
19
+ author: { login: 'local' },
20
+ createdAt: '2026-01-01T00:00:00.000Z',
21
+ text: 'hi',
22
+ statusChange: null,
23
+ },
24
+ ],
25
+ };
26
+ }
27
+
28
+ const pageLevelAnchor = {
29
+ nodePath: [0, 1],
30
+ nodeIdentity: { kind: 'node', name: 'div' },
31
+ offsetXPercent: 0.5,
32
+ offsetYPercent: 0.5,
33
+ };
34
+
35
+ describe('validateComment — element anchor identity', () => {
36
+ test('accepts a comment WITHOUT a cmsItemIndexPath (element not in a list)', () => {
37
+ const result = validateComment(baseComment(pageLevelAnchor));
38
+ expect(result.valid).toBe(true);
39
+ if (result.valid) expect(result.data.anchor.cmsItemIndexPath).toBeUndefined();
40
+ });
41
+
42
+ test('accepts and round-trips a comment WITH a cmsItemIndexPath', () => {
43
+ const cmsItemIndexPath = [0, 2];
44
+ const result = validateComment(baseComment({ ...pageLevelAnchor, cmsItemIndexPath }));
45
+ expect(result.valid).toBe(true);
46
+ if (result.valid) expect(result.data.anchor.cmsItemIndexPath).toEqual(cmsItemIndexPath);
47
+ });
48
+
49
+ test('rejects a malformed cmsItemIndexPath (non-number entries)', () => {
50
+ const result = validateComment(baseComment({ ...pageLevelAnchor, cmsItemIndexPath: ['a', 'b'] }));
51
+ expect(result.valid).toBe(false);
52
+ });
53
+ });
@@ -23,8 +23,19 @@ export const CommentNodeIdentitySchema = z.object({
23
23
  label: z.string().optional(),
24
24
  });
25
25
 
26
+ export const CommentNodeRefSegmentSchema = z.object({
27
+ kind: z.enum(['component', 'node']),
28
+ name: z.string().min(1),
29
+ nth: z.number().int().nonnegative(),
30
+ });
31
+
26
32
  export const CommentAnchorSchema = z.object({
33
+ // Type + occurrence path (move-resilient source of truth). Must be declared
34
+ // (z.object strips unknown keys) so it round-trips to disk.
35
+ nodeRef: z.array(CommentNodeRefSegmentSchema).optional(),
27
36
  nodePath: z.array(z.number().int().nonnegative()),
37
+ // CMS-list item path disambiguating which list copy the element belongs to.
38
+ cmsItemIndexPath: z.array(z.number().int().nonnegative()).optional(),
28
39
  nodeIdentity: CommentNodeIdentitySchema,
29
40
  offsetXPercent: z.number().min(0).max(1),
30
41
  offsetYPercent: z.number().min(0).max(1),
@@ -53,7 +64,7 @@ export const CommentSchema = z.object({
53
64
  });
54
65
 
55
66
  function zodToValidationErrors(zodError: ZodError): ValidationError[] {
56
- return zodError.errors.map(err => ({
67
+ return zodError.errors.map((err) => ({
57
68
  path: err.path.join('.') || 'root',
58
69
  message: err.message,
59
70
  code: 'INVALID_TYPE' as const,
@@ -14,9 +14,9 @@ describe('Prop Validator', () => {
14
14
  title: { type: 'string', default: 'Hello' },
15
15
  };
16
16
  const passedProps = { title: 'World' };
17
-
17
+
18
18
  const result = validateComponentProps(propDefs, passedProps);
19
-
19
+
20
20
  expect(result.valid).toBe(true);
21
21
  expect(result.props.title).toBe('World');
22
22
  });
@@ -26,9 +26,9 @@ describe('Prop Validator', () => {
26
26
  count: { type: 'number', default: 0 },
27
27
  };
28
28
  const passedProps = { count: 42 };
29
-
29
+
30
30
  const result = validateComponentProps(propDefs, passedProps);
31
-
31
+
32
32
  expect(result.valid).toBe(true);
33
33
  expect(result.props.count).toBe(42);
34
34
  });
@@ -38,9 +38,9 @@ describe('Prop Validator', () => {
38
38
  enabled: { type: 'boolean', default: false },
39
39
  };
40
40
  const passedProps = { enabled: true };
41
-
41
+
42
42
  const result = validateComponentProps(propDefs, passedProps);
43
-
43
+
44
44
  expect(result.valid).toBe(true);
45
45
  expect(result.props.enabled).toBe(true);
46
46
  });
@@ -54,9 +54,9 @@ describe('Prop Validator', () => {
54
54
  },
55
55
  };
56
56
  const passedProps = { variant: 'secondary' };
57
-
57
+
58
58
  const result = validateComponentProps(propDefs, passedProps);
59
-
59
+
60
60
  expect(result.valid).toBe(true);
61
61
  expect(result.props.variant).toBe('secondary');
62
62
  });
@@ -68,9 +68,9 @@ describe('Prop Validator', () => {
68
68
  title: { type: 'string', default: 'Hello' },
69
69
  };
70
70
  const passedProps = { title: 123 };
71
-
71
+
72
72
  const result = validateComponentProps(propDefs, passedProps);
73
-
73
+
74
74
  // Coercion makes this valid (123 -> "123")
75
75
  expect(result.valid).toBe(true);
76
76
  expect(result.props.title).toBe('123');
@@ -121,9 +121,9 @@ describe('Prop Validator', () => {
121
121
  count: { type: 'number', default: 0 },
122
122
  };
123
123
  const passedProps = { count: '123' };
124
-
124
+
125
125
  const result = validateComponentProps(propDefs, passedProps);
126
-
126
+
127
127
  expect(result.valid).toBe(true);
128
128
  expect(result.props.count).toBe(123);
129
129
  expect(typeof result.props.count).toBe('number');
@@ -134,9 +134,9 @@ describe('Prop Validator', () => {
134
134
  enabled: { type: 'boolean', default: false },
135
135
  };
136
136
  const passedProps = { enabled: 'true' };
137
-
137
+
138
138
  const result = validateComponentProps(propDefs, passedProps);
139
-
139
+
140
140
  expect(result.valid).toBe(true);
141
141
  expect(result.props.enabled).toBe(true);
142
142
  expect(typeof result.props.enabled).toBe('boolean');
@@ -243,9 +243,9 @@ describe('Prop Validator', () => {
243
243
  unknownProp: 'allowed',
244
244
  anotherUnknown: 123,
245
245
  };
246
-
246
+
247
247
  const result = validateComponentProps(propDefs, passedProps);
248
-
248
+
249
249
  // Should be valid (unknown props don't fail validation)
250
250
  expect(result.valid).toBe(true);
251
251
  // Unknown props should be preserved
@@ -259,14 +259,12 @@ describe('Prop Validator', () => {
259
259
  count: { type: 'number', default: 42 },
260
260
  };
261
261
  const passedProps = {};
262
-
262
+
263
263
  const result = validateComponentProps(propDefs, passedProps);
264
-
264
+
265
265
  expect(result.valid).toBe(true);
266
266
  expect(result.props.title).toBe('Hello');
267
267
  expect(result.props.count).toBe(42);
268
268
  });
269
269
  });
270
270
  });
271
-
272
-