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
@@ -4,36 +4,59 @@
4
4
  * responsive styles, event handlers, and element registration.
5
5
  */
6
6
 
7
- import { createElement as h, Fragment } from "react";
8
- import type { ReactElement } from "react";
7
+ import { createElement as h, Fragment } from 'react';
8
+ import type { ReactElement } from 'react';
9
9
 
10
10
  // Component registry and utilities
11
- import { ComponentRegistry } from "../componentRegistry";
12
- import { processStructure } from "../templateEngine";
13
- import { NODE_TYPE } from "../../shared/constants";
14
- import { ErrorBoundary } from "../ErrorBoundary";
15
- import type { ComponentNode, StyleValue, StyleObject, ResponsiveStyleObject } from "../../shared/types";
16
- import { isComponentNode, extractNodeProperties, isSlotMarker, isEmbedNode, isLinkNode, isLocaleListNode, isListNode, markAsSlotContent, evaluateNodeIf, isBooleanMapping, hasIf, hasChildren, isSlotContent as isSlotContentNode } from "../../shared/nodeUtils";
17
- import { mergeNodeStyles } from "../../shared/styleNodeUtils";
18
- import { isCurrentLink } from "../../shared/linkUtils";
19
- import { extractAttributesFromNode, skipEmptyTemplateAttributes } from "../../shared/attributeNodeUtils";
20
- import { resolvePropsFromDefinition } from "../../shared/propResolver";
21
- import { ElementRegistry } from "../elementRegistry";
22
- import { InteractiveStylesRegistry } from "../InteractiveStylesRegistry";
23
- import type { Path } from "../../shared/pathArrayUtils";
24
- import type { I18nConfig } from "../../shared/types/components";
25
- import type { ItemContext, TemplateContext } from "../../shared/types/cms";
26
- import { processItemTemplate, processItemPropsTemplate, hasItemTemplates, type ValueResolver } from "../../shared/itemTemplateUtils";
27
- import { DEFAULT_I18N_CONFIG, isI18nValue, resolveI18nValue } from "../../shared/i18n";
28
- import { getChildPath, pathToString } from "../../shared/pathArrayUtils";
29
- import { responsiveStylesToClasses } from "../../shared/utilityClassMapper";
30
- import { getCachedResponsiveScalesConfig } from "../responsiveStyleResolver";
31
- import { UtilityClassCollector } from "../styles/UtilityClassCollector";
32
- import { processCMSTemplate, processCMSPropsTemplate, RAW_HTML_PREFIX } from "./cmsTemplateProcessor";
33
- import type { PrefetchService } from "../services/PrefetchService";
34
- import { generateElementClassName, type ElementClassContext } from "../../shared/elementClassName";
35
- import type { InteractiveStyles } from "../../shared/types/styles";
36
- import { extractInteractiveStyleMappings, resolveExtractedMappings, hasInteractiveStyleMappings } from "../../shared/interactiveStyleMappings";
11
+ import type { ComponentRegistry } from '../componentRegistry';
12
+ import { processStructure } from '../templateEngine';
13
+ import { NODE_TYPE } from '../../shared/constants';
14
+ import { ErrorBoundary } from '../ErrorBoundary';
15
+ import type { ComponentNode, StyleValue, StyleObject, ResponsiveStyleObject } from '../../shared/types';
16
+ import {
17
+ isComponentNode,
18
+ extractNodeProperties,
19
+ isSlotMarker,
20
+ isEmbedNode,
21
+ isLinkNode,
22
+ isLocaleListNode,
23
+ isListNode,
24
+ isIslandNode,
25
+ isCustomNode,
26
+ isValidNodeType,
27
+ markAsSlotContent,
28
+ isBooleanMapping,
29
+ hasIf,
30
+ hasChildren,
31
+ isSlotContent as isSlotContentNode,
32
+ } from '../../shared/nodeUtils';
33
+ import { mergeNodeStyles } from '../../shared/styleNodeUtils';
34
+ import { isCurrentLink } from '../../shared/linkUtils';
35
+ import { extractAttributesFromNode, skipEmptyTemplateAttributes } from '../../shared/attributeNodeUtils';
36
+ import { resolvePropsFromDefinition } from '../../shared/propResolver';
37
+ import type { ElementRegistry } from '../elementRegistry';
38
+ import { InteractiveStylesRegistry } from '../InteractiveStylesRegistry';
39
+ import type { Path } from '../../shared/pathArrayUtils';
40
+ import {
41
+ processItemTemplate,
42
+ processItemPropsTemplate,
43
+ hasItemTemplates,
44
+ type ValueResolver,
45
+ } from '../../shared/itemTemplateUtils';
46
+ import { DEFAULT_I18N_CONFIG, isI18nValue, resolveI18nValue } from '../../shared/i18n';
47
+ import { getChildPath, pathToString } from '../../shared/pathArrayUtils';
48
+ import { responsiveStylesToClasses } from '../../shared/utilityClassMapper';
49
+ import { getCachedResponsiveScalesConfig } from '../responsiveStyleResolver';
50
+ import { UtilityClassCollector } from '../styles/UtilityClassCollector';
51
+ import { processCMSTemplate, processCMSPropsTemplate, RAW_HTML_PREFIX } from './cmsTemplateProcessor';
52
+ import type { PrefetchService } from '../services/PrefetchService';
53
+ import { generateElementClassName, type ElementClassContext } from '../../shared/elementClassName';
54
+ import type { InteractiveStyles } from '../../shared/types/styles';
55
+ import {
56
+ extractInteractiveStyleMappings,
57
+ resolveExtractedMappings,
58
+ hasInteractiveStyleMappings,
59
+ } from '../../shared/interactiveStyleMappings';
37
60
 
38
61
  // Extracted builders
39
62
  import {
@@ -46,10 +69,20 @@ import {
46
69
  buildLocaleList,
47
70
  buildList,
48
71
  buildLink,
49
- } from "./builders";
72
+ } from './builders';
50
73
 
51
74
  type ComponentProps = Record<string, unknown>;
52
75
 
76
+ /**
77
+ * Narrow processStructure's object result to a renderable node.
78
+ * processStructure returns a plain Record<string, unknown> when the input was
79
+ * not a node (e.g. a props object) — real nodes always carry a valid `type`
80
+ * discriminant. Non-node objects can't be rendered, so callers bail out.
81
+ */
82
+ function isProcessedComponentNode(value: ComponentNode | Record<string, unknown>): value is ComponentNode {
83
+ return typeof value.type === 'string' && isValidNodeType(value.type);
84
+ }
85
+
53
86
  // Document-structure tags can't nest inside <div id="root">: the HTML5 parser
54
87
  // hoists a nested <body>'s attributes onto the outer <body> and drops the tag,
55
88
  // so SSR and client-side (React createElement) end up with divergent DOMs.
@@ -101,12 +134,11 @@ export class ComponentBuilder {
101
134
  let cached = this.styleClassCache.get(style);
102
135
  if (!cached) {
103
136
  const scales = getCachedResponsiveScalesConfig();
104
- const fluidActive =
105
- scales?.enabled === true && scales?.mode === 'fluid';
106
- cached = responsiveStylesToClasses(
107
- style as StyleObject | ResponsiveStyleObject,
108
- { fluidActive, responsiveScales: scales ?? undefined }
109
- );
137
+ const fluidActive = scales?.enabled === true && scales?.mode === 'fluid';
138
+ cached = responsiveStylesToClasses(style as StyleObject | ResponsiveStyleObject, {
139
+ fluidActive,
140
+ responsiveScales: scales ?? undefined,
141
+ });
110
142
  this.styleClassCache.set(style, cached);
111
143
  }
112
144
  UtilityClassCollector.collect(cached);
@@ -128,7 +160,7 @@ export class ComponentBuilder {
128
160
  private getParentComponentNameForNestedComponent(
129
161
  componentContext: string | null,
130
162
  parentComponentName: string | null,
131
- componentName: string | null
163
+ componentName: string | null,
132
164
  ): string | null {
133
165
  return componentContext || parentComponentName || componentName || null;
134
166
  }
@@ -138,7 +170,7 @@ export class ComponentBuilder {
138
170
  */
139
171
  private getEffectiveParentComponentName(
140
172
  componentContext: string | null,
141
- parentComponentName: string | null
173
+ parentComponentName: string | null,
142
174
  ): string | null {
143
175
  return componentContext || parentComponentName;
144
176
  }
@@ -235,12 +267,24 @@ export class ComponentBuilder {
235
267
  */
236
268
  buildChildren(
237
269
  children: (string | ComponentNode)[] | string | ComponentNode | undefined,
238
- ctx: BuildChildrenContext
270
+ ctx: BuildChildrenContext,
239
271
  ): ReactElement | ReactElement[] | string | number | null {
240
272
  const {
241
- elementPath, parentComponentName, viewportWidth, componentContext, componentRootPath,
242
- locale, i18nConfig, cmsContext, cmsLocale, collectionItemsMap,
243
- itemContext, cmsItemIndexPath, cmsListPaths, templateContext, componentResolvedProps
273
+ elementPath,
274
+ parentComponentName,
275
+ viewportWidth,
276
+ componentContext,
277
+ componentRootPath,
278
+ locale,
279
+ i18nConfig,
280
+ cmsContext,
281
+ cmsLocale,
282
+ collectionItemsMap,
283
+ itemContext,
284
+ cmsItemIndexPath,
285
+ cmsListPaths,
286
+ templateContext,
287
+ componentResolvedProps,
244
288
  } = ctx;
245
289
 
246
290
  if (Array.isArray(children)) {
@@ -264,8 +308,8 @@ export class ComponentBuilder {
264
308
  cmsItemIndexPath,
265
309
  cmsListPaths,
266
310
  templateContext,
267
- componentResolvedProps
268
- })
311
+ componentResolvedProps,
312
+ }),
269
313
  )
270
314
  .filter((item): item is ReactElement | string | number => item !== null) as ReactElement[];
271
315
  }
@@ -287,7 +331,7 @@ export class ComponentBuilder {
287
331
  cmsItemIndexPath,
288
332
  cmsListPaths,
289
333
  templateContext,
290
- componentResolvedProps
334
+ componentResolvedProps,
291
335
  });
292
336
  }
293
337
 
@@ -313,7 +357,7 @@ export class ComponentBuilder {
313
357
  templateContext = null,
314
358
  cmsItemIndexPath = null,
315
359
  cmsListPaths = null,
316
- componentResolvedProps = null
360
+ componentResolvedProps = null,
317
361
  } = options;
318
362
 
319
363
  if (!node) return null;
@@ -340,9 +384,22 @@ export class ComponentBuilder {
340
384
 
341
385
  // Build context for specialized builders (needed for if condition evaluation)
342
386
  const ctx: BuilderContext = {
343
- key, elementPath, parentComponentName, viewportWidth, componentContext, componentRootPath,
344
- locale, i18nConfig, cmsContext, cmsLocale, collectionItemsMap,
345
- itemContext, cmsItemIndexPath, cmsListPaths, templateContext, componentResolvedProps
387
+ key,
388
+ elementPath,
389
+ parentComponentName,
390
+ viewportWidth,
391
+ componentContext,
392
+ componentRootPath,
393
+ locale,
394
+ i18nConfig,
395
+ cmsContext,
396
+ cmsLocale,
397
+ collectionItemsMap,
398
+ itemContext,
399
+ cmsItemIndexPath,
400
+ cmsListPaths,
401
+ templateContext,
402
+ componentResolvedProps,
346
403
  };
347
404
 
348
405
  // Dependencies for specialized builders
@@ -380,7 +437,7 @@ export class ComponentBuilder {
380
437
 
381
438
  // Get node type
382
439
  const nodeType = typeof node === 'object' && node !== null && 'type' in node ? node.type : undefined;
383
- const children = typeof node === 'object' && node !== null && 'children' in node ? (node.children || []) : [];
440
+ const children = typeof node === 'object' && node !== null && 'children' in node ? node.children || [] : [];
384
441
 
385
442
  // Handle specialized node types using extracted builders
386
443
  if (nodeType === NODE_TYPE.EMBED && isEmbedNode(node)) {
@@ -395,6 +452,20 @@ export class ComponentBuilder {
395
452
  return buildLocaleList(node, ctx, builderDeps);
396
453
  }
397
454
 
455
+ // Islands (BYO framework components) and Custom nodes (opaque hand-authored
456
+ // `.astro` black boxes) can't run in meno-core — real rendering is play/build
457
+ // only. On the canvas we render their slotted children as normal content (so
458
+ // such pages read cleanly) and fall back to a quiet inline marker when there
459
+ // are none. The wrapper carries `data-element-path` so a click selects the
460
+ // node itself (like any other), plus `data-island`/`data-custom` for
461
+ // identification — but draws no highlight box.
462
+ if (nodeType === NODE_TYPE.ISLAND && isIslandNode(node)) {
463
+ return this.buildBlackBoxNode(node, 'data-island', /\.(tsx|jsx|vue|svelte)$/i, options);
464
+ }
465
+ if (nodeType === NODE_TYPE.CUSTOM && isCustomNode(node)) {
466
+ return this.buildBlackBoxNode(node, 'data-custom', /\.astro$/i, options);
467
+ }
468
+
398
469
  // Handle List nodes (unified - handles both prop and collection source types)
399
470
  // isListNode() also matches legacy 'cms-list' type for migration
400
471
  if (isListNode(node)) {
@@ -429,23 +500,27 @@ export class ComponentBuilder {
429
500
  if (process.env.NODE_ENV !== 'production') {
430
501
  console.warn(
431
502
  `[Meno] Component "${componentName}" not found in registry. ` +
432
- `Registered: [${this.componentRegistry.getNames().join(', ')}]`
503
+ `Registered: [${this.componentRegistry.getNames().join(', ')}]`,
433
504
  );
434
505
  }
435
- return h('div', {
436
- key,
437
- style: {
438
- border: '1px dashed #e5a00d',
439
- padding: '8px 12px',
440
- margin: '4px 0',
441
- borderRadius: '4px',
442
- color: '#b8860b',
443
- fontSize: '13px',
444
- fontFamily: 'monospace',
445
- background: '#fffbe6',
506
+ return h(
507
+ 'div',
508
+ {
509
+ key,
510
+ style: {
511
+ border: '1px dashed #e5a00d',
512
+ padding: '8px 12px',
513
+ margin: '4px 0',
514
+ borderRadius: '4px',
515
+ color: '#b8860b',
516
+ fontSize: '13px',
517
+ fontFamily: 'monospace',
518
+ background: '#fffbe6',
519
+ },
520
+ 'data-missing-component': componentName,
446
521
  },
447
- 'data-missing-component': componentName,
448
- }, `Component not found: ${componentName}`);
522
+ `Component not found: ${componentName}`,
523
+ );
449
524
  }
450
525
 
451
526
  // Handle Link component
@@ -472,20 +547,25 @@ export class ComponentBuilder {
472
547
  const markerRegex = /<div\s+data-meno-component="([^"]+)"\s+data-meno-props="([^"]*)"[^>]*><\/div>/g;
473
548
  const segments: ReactElement[] = [];
474
549
  let lastIndex = 0;
475
- let match: RegExpExecArray | null;
550
+ let match: RegExpExecArray | null = markerRegex.exec(html);
476
551
  let segmentIndex = 0;
477
552
 
478
- while ((match = markerRegex.exec(html)) !== null) {
553
+ while (match !== null) {
554
+ const currentMatch = match;
555
+ match = markerRegex.exec(html);
479
556
  // Add HTML before this match
480
- if (match.index > lastIndex) {
481
- const chunk = html.slice(lastIndex, match.index);
557
+ if (currentMatch.index > lastIndex) {
558
+ const chunk = html.slice(lastIndex, currentMatch.index);
482
559
  segments.push(h('span', { key: `rt-${segmentIndex++}`, dangerouslySetInnerHTML: { __html: chunk } }));
483
560
  }
484
561
 
485
- const componentName = match[1];
562
+ const componentName = currentMatch[1];
563
+ if (componentName === undefined) {
564
+ continue;
565
+ }
486
566
  let props: Record<string, unknown> = {};
487
567
  try {
488
- const propsStr = match[2]
568
+ const propsStr = (currentMatch[2] ?? '')
489
569
  .replace(/&quot;/g, '"')
490
570
  .replace(/&#039;/g, "'")
491
571
  .replace(/&amp;/g, '&');
@@ -527,16 +607,18 @@ export class ComponentBuilder {
527
607
  }
528
608
  } else {
529
609
  // Keep marker as-is for unregistered components
530
- segments.push(h('span', { key: `rt-${segmentIndex}`, dangerouslySetInnerHTML: { __html: match[0] } }));
610
+ segments.push(h('span', { key: `rt-${segmentIndex}`, dangerouslySetInnerHTML: { __html: currentMatch[0] } }));
531
611
  }
532
612
 
533
613
  segmentIndex++;
534
- lastIndex = match.index + match[0].length;
614
+ lastIndex = currentMatch.index + currentMatch[0].length;
535
615
  }
536
616
 
537
617
  // Add remaining HTML
538
618
  if (lastIndex < html.length) {
539
- segments.push(h('span', { key: `rt-${segmentIndex}`, dangerouslySetInnerHTML: { __html: html.slice(lastIndex) } }));
619
+ segments.push(
620
+ h('span', { key: `rt-${segmentIndex}`, dangerouslySetInnerHTML: { __html: html.slice(lastIndex) } }),
621
+ );
540
622
  }
541
623
 
542
624
  return h(Fragment, null, ...segments);
@@ -580,34 +662,47 @@ export class ComponentBuilder {
580
662
  */
581
663
  private buildNodeArray(nodes: ComponentNode[], options: BuildComponentOptions): ReactElement[] {
582
664
  const {
583
- customProps, elementPath = [0], parentComponentName = null, viewportWidth = 1920,
584
- componentContext = null, locale, i18nConfig, cmsContext = null, cmsLocale = null,
585
- collectionItemsMap = {}, itemContext = null, cmsItemIndexPath = null,
586
- cmsListPaths = null, templateContext = null, componentResolvedProps = null
665
+ customProps,
666
+ elementPath = [0],
667
+ parentComponentName = null,
668
+ viewportWidth = 1920,
669
+ componentContext = null,
670
+ locale,
671
+ i18nConfig,
672
+ cmsContext = null,
673
+ cmsLocale = null,
674
+ collectionItemsMap = {},
675
+ itemContext = null,
676
+ cmsItemIndexPath = null,
677
+ cmsListPaths = null,
678
+ templateContext = null,
679
+ componentResolvedProps = null,
587
680
  } = options;
588
681
 
589
- return nodes.map((child, index) => {
590
- const childPath = getChildPath(elementPath, index);
591
- return this.buildComponent({
592
- node: child,
593
- key: index,
594
- customProps,
595
- elementPath: childPath,
596
- parentComponentName,
597
- viewportWidth,
598
- componentContext,
599
- locale,
600
- i18nConfig,
601
- cmsContext,
602
- cmsLocale,
603
- collectionItemsMap,
604
- itemContext,
605
- cmsItemIndexPath,
606
- cmsListPaths,
607
- templateContext,
608
- componentResolvedProps
609
- });
610
- }).filter((item): item is ReactElement => item !== null && typeof item === 'object');
682
+ return nodes
683
+ .map((child, index) => {
684
+ const childPath = getChildPath(elementPath, index);
685
+ return this.buildComponent({
686
+ node: child,
687
+ key: index,
688
+ customProps,
689
+ elementPath: childPath,
690
+ parentComponentName,
691
+ viewportWidth,
692
+ componentContext,
693
+ locale,
694
+ i18nConfig,
695
+ cmsContext,
696
+ cmsLocale,
697
+ collectionItemsMap,
698
+ itemContext,
699
+ cmsItemIndexPath,
700
+ cmsListPaths,
701
+ templateContext,
702
+ componentResolvedProps,
703
+ });
704
+ })
705
+ .filter((item): item is ReactElement => item !== null && typeof item === 'object');
611
706
  }
612
707
 
613
708
  /**
@@ -659,7 +754,11 @@ export class ComponentBuilder {
659
754
  /**
660
755
  * Apply style classes to props
661
756
  */
662
- private applyStyleClasses(props: Record<string, unknown>, node: ComponentNode, ctx: BuilderContext): Record<string, unknown> {
757
+ private applyStyleClasses(
758
+ props: Record<string, unknown>,
759
+ node: ComponentNode,
760
+ ctx: BuilderContext,
761
+ ): Record<string, unknown> {
663
762
  const nodeStyle = node.style || (isComponentNode(node) ? node.props?.style : undefined);
664
763
 
665
764
  if (nodeStyle && typeof nodeStyle === 'object') {
@@ -678,7 +777,11 @@ export class ComponentBuilder {
678
777
  if (utilityClasses.length > 0) {
679
778
  const existingClassName = (props.className || '') as string;
680
779
  const classArray = existingClassName ? existingClassName.split(/\s+/) : [];
681
- return { ...props, className: [...classArray, ...utilityClasses].filter(Boolean).join(' '), style: processedStyle };
780
+ return {
781
+ ...props,
782
+ className: [...classArray, ...utilityClasses].filter(Boolean).join(' '),
783
+ style: processedStyle,
784
+ };
682
785
  }
683
786
  }
684
787
 
@@ -688,10 +791,16 @@ export class ComponentBuilder {
688
791
  /**
689
792
  * Apply interactive styles and generate element class
690
793
  */
691
- private applyInteractiveStyles(props: Record<string, unknown>, node: ComponentNode, ctx: BuilderContext): Record<string, unknown> {
692
- const nodeInteractiveStyles = 'interactiveStyles' in node ? node.interactiveStyles as InteractiveStyles | undefined : undefined;
693
- const nodeGenerateElementClass = 'generateElementClass' in node ? node.generateElementClass as boolean | undefined : undefined;
694
- const nodeLabel = 'label' in node ? node.label as string | undefined : undefined;
794
+ private applyInteractiveStyles(
795
+ props: Record<string, unknown>,
796
+ node: ComponentNode,
797
+ ctx: BuilderContext,
798
+ ): Record<string, unknown> {
799
+ const nodeInteractiveStyles =
800
+ 'interactiveStyles' in node ? (node.interactiveStyles as InteractiveStyles | undefined) : undefined;
801
+ const nodeGenerateElementClass =
802
+ 'generateElementClass' in node ? (node.generateElementClass as boolean | undefined) : undefined;
803
+ const nodeLabel = 'label' in node ? (node.label as string | undefined) : undefined;
695
804
 
696
805
  if (!((nodeInteractiveStyles && nodeInteractiveStyles.length > 0) || nodeGenerateElementClass)) {
697
806
  return props;
@@ -700,11 +809,16 @@ export class ComponentBuilder {
700
809
  // Check if slot content
701
810
  const isSlotContent = isSlotContentNode(node);
702
811
  const useComponentContext = ctx.componentContext && !isSlotContent;
703
- const fileType = useComponentContext ? 'component' : (this.getCurrentFileType?.() || 'page');
704
- const fileName = useComponentContext ? ctx.componentContext! : (this.getCurrentPageName ? this.getCurrentPageName() : 'page');
705
- const pathForClass = useComponentContext && ctx.componentRootPath
706
- ? ctx.elementPath.slice(ctx.componentRootPath.length)
707
- : ctx.elementPath;
812
+ const fileType = useComponentContext ? 'component' : this.getCurrentFileType?.() || 'page';
813
+ const fileName = useComponentContext
814
+ ? ctx.componentContext!
815
+ : this.getCurrentPageName
816
+ ? this.getCurrentPageName()
817
+ : 'page';
818
+ const pathForClass =
819
+ useComponentContext && ctx.componentRootPath
820
+ ? ctx.elementPath.slice(ctx.componentRootPath.length)
821
+ : ctx.elementPath;
708
822
 
709
823
  const elementClassCtx: ElementClassContext = {
710
824
  fileType,
@@ -755,7 +869,11 @@ export class ComponentBuilder {
755
869
  * objects on attribute values to the active locale's string before
756
870
  * downstream template / CMS processing, mirroring the SSR path.
757
871
  */
758
- private mergeAttributes(props: Record<string, unknown>, node: ComponentNode, ctx: BuilderContext): Record<string, unknown> {
872
+ private mergeAttributes(
873
+ props: Record<string, unknown>,
874
+ node: ComponentNode,
875
+ ctx: BuilderContext,
876
+ ): Record<string, unknown> {
759
877
  let extractedAttributes = extractAttributesFromNode(node) as Record<string, unknown>;
760
878
  const attrI18nConfig = ctx.i18nConfig ?? DEFAULT_I18N_CONFIG;
761
879
  const attrEffectiveLocale = ctx.locale || attrI18nConfig.defaultLocale;
@@ -772,7 +890,7 @@ export class ComponentBuilder {
772
890
  extractedAttributes = processCMSPropsTemplate(
773
891
  extractedAttributes as Record<string, unknown>,
774
892
  ctx.cmsContext,
775
- ctx.cmsLocale || ctx.locale
893
+ ctx.cmsLocale || ctx.locale,
776
894
  ) as Record<string, string | number | boolean>;
777
895
  }
778
896
 
@@ -784,25 +902,38 @@ export class ComponentBuilder {
784
902
  const i18nResolver: ValueResolver | undefined = effectiveLocale
785
903
  ? (value: unknown) => resolveI18nValue(value, effectiveLocale, config)
786
904
  : undefined;
787
- extractedAttributes = processItemPropsTemplate(extractedAttributes, effectiveItemContext, i18nResolver) as Record<string, string | number | boolean>;
905
+ extractedAttributes = processItemPropsTemplate(extractedAttributes, effectiveItemContext, i18nResolver) as Record<
906
+ string,
907
+ string | number | boolean
908
+ >;
788
909
  }
789
910
 
790
911
  // Auto-skip attributes that were entirely template expressions and resolved to ""
791
912
  if (Object.keys(extractedAttributes).length > 0) {
792
- extractedAttributes = skipEmptyTemplateAttributes(originalAttributes, extractedAttributes) as Record<string, string | number | boolean>;
913
+ extractedAttributes = skipEmptyTemplateAttributes(originalAttributes, extractedAttributes) as Record<
914
+ string,
915
+ string | number | boolean
916
+ >;
793
917
  }
794
918
 
795
919
  if (Object.keys(extractedAttributes).length === 0) {
796
920
  return props;
797
921
  }
798
922
 
799
- let result = { ...props };
923
+ const result = { ...props };
800
924
 
801
925
  // Handle class/className specially - check key existence, not truthiness (empty string is valid)
802
926
  if ('class' in extractedAttributes || 'className' in extractedAttributes) {
803
927
  const attrClass = (extractedAttributes.class ?? extractedAttributes.className ?? '') as string;
804
928
  const existingClassName = (result.className || '') as string;
805
929
  result.className = [existingClassName, attrClass].filter(Boolean).join(' ');
930
+ // Class-string styling: utility tokens stored in attributes.class (e.g. p-[24px],
931
+ // tablet:p-[16px]) must inject their CSS into the live canvas, exactly like the
932
+ // node.style → getCachedStyleClasses path does. Without this the class lands on the
933
+ // element but renders unstyled. collect() skips foreign tokens (swiper, prose) that
934
+ // generate no rule, so passing the whole token list is safe.
935
+ const classTokens = attrClass.split(/\s+/).filter(Boolean);
936
+ if (classTokens.length > 0) UtilityClassCollector.collect(classTokens);
806
937
  delete extractedAttributes.class;
807
938
  delete extractedAttributes.className;
808
939
  }
@@ -818,6 +949,81 @@ export class ComponentBuilder {
818
949
  return { ...result, ...normalizedAttributes };
819
950
  }
820
951
 
952
+ /**
953
+ * Build the canvas placeholder for a "black box" node (Island / Custom) that
954
+ * meno-core can't actually run. Renders the node's slotted children as normal
955
+ * content, or a quiet inline marker when there are none. The wrapper carries
956
+ * `data-element-path` (and CMS-list context, when inside one) via a ref so a
957
+ * click resolves to this node and selects it like any regular element — even
958
+ * though the `display:contents` wrapper itself draws no box.
959
+ */
960
+ private buildBlackBoxNode(
961
+ node: ComponentNode,
962
+ dataAttr: 'data-island' | 'data-custom',
963
+ extPattern: RegExp,
964
+ options: BuildComponentOptions,
965
+ ): ReactElement {
966
+ const {
967
+ key = 0,
968
+ elementPath = [0],
969
+ parentComponentName = null,
970
+ viewportWidth = 1920,
971
+ componentContext = null,
972
+ componentRootPath,
973
+ locale,
974
+ i18nConfig,
975
+ cmsContext = null,
976
+ cmsLocale = null,
977
+ collectionItemsMap = {},
978
+ itemContext = null,
979
+ cmsItemIndexPath = null,
980
+ cmsListPaths = null,
981
+ templateContext = null,
982
+ componentResolvedProps = null,
983
+ } = options;
984
+
985
+ const src = 'src' in node && typeof node.src === 'string' ? node.src : '';
986
+ const name = src.split('/').pop()?.replace(extPattern, '') || '?';
987
+ const childNodes = 'children' in node ? node.children : undefined;
988
+ const hasChildNodes =
989
+ (Array.isArray(childNodes) && childNodes.length > 0) || (typeof childNodes === 'string' && childNodes.length > 0);
990
+ const inner = hasChildNodes
991
+ ? this.buildChildren(childNodes as (string | ComponentNode)[] | string, {
992
+ elementPath,
993
+ parentComponentName,
994
+ viewportWidth,
995
+ componentContext,
996
+ componentRootPath,
997
+ locale,
998
+ i18nConfig,
999
+ cmsContext,
1000
+ cmsLocale,
1001
+ collectionItemsMap,
1002
+ itemContext,
1003
+ cmsItemIndexPath,
1004
+ cmsListPaths,
1005
+ templateContext,
1006
+ componentResolvedProps,
1007
+ })
1008
+ : h('span', { style: { color: '#9ca3af', fontSize: '13px' } }, name);
1009
+
1010
+ const ref = (el: HTMLElement | null) => {
1011
+ if (!el) return;
1012
+ el.setAttribute('data-element-path', pathToString(elementPath));
1013
+ if (cmsItemIndexPath && cmsItemIndexPath.length > 0) {
1014
+ el.setAttribute('data-cms-item-index', cmsItemIndexPath.join('.'));
1015
+ if (cmsListPaths && cmsListPaths.length === cmsItemIndexPath.length) {
1016
+ el.setAttribute(
1017
+ 'data-cms-context',
1018
+ JSON.stringify({ itemIndexPath: cmsItemIndexPath, listPaths: cmsListPaths }),
1019
+ );
1020
+ }
1021
+ }
1022
+ };
1023
+
1024
+ return h('div', { key, [dataAttr]: src, style: { display: 'contents' }, ref }, inner);
1025
+ }
1026
+
821
1027
  /**
822
1028
  * Build a custom component
823
1029
  */
@@ -826,18 +1032,28 @@ export class ComponentBuilder {
826
1032
  nodeProps: Record<string, unknown>,
827
1033
  children: unknown,
828
1034
  finalProps: Record<string, unknown>,
829
- options: BuildComponentOptions
1035
+ options: BuildComponentOptions,
830
1036
  ): ReactElement | null {
831
1037
  const {
832
- key = 0, elementPath = [0], parentComponentName = null, viewportWidth = 1920,
833
- componentContext = null, locale, i18nConfig, cmsContext = null, cmsLocale = null,
834
- collectionItemsMap = {}, itemContext = null, cmsItemIndexPath = null,
835
- cmsListPaths = null, templateContext = null,
1038
+ key = 0,
1039
+ elementPath = [0],
1040
+ parentComponentName = null,
1041
+ viewportWidth = 1920,
1042
+ componentContext = null,
1043
+ locale,
1044
+ i18nConfig,
1045
+ cmsContext = null,
1046
+ cmsLocale = null,
1047
+ collectionItemsMap = {},
1048
+ itemContext = null,
1049
+ cmsItemIndexPath = null,
1050
+ cmsListPaths = null,
1051
+ templateContext = null,
836
1052
  // The host (outer) component's resolved props. Forwarded to
837
1053
  // `processStructure` as `parentProps` so a `{{x}}` in this
838
1054
  // component's structure falls back to the host's `x` when this
839
1055
  // component's interface doesn't declare it.
840
- componentResolvedProps: parentResolvedProps = null
1056
+ componentResolvedProps: parentResolvedProps = null,
841
1057
  } = options;
842
1058
 
843
1059
  const componentDef = this.componentRegistry.get(componentName);
@@ -870,12 +1086,12 @@ export class ComponentBuilder {
870
1086
  }
871
1087
 
872
1088
  // Resolve props (validate, coerce types, apply defaults) AFTER templates are resolved
873
- let resolvedProps = resolvePropsFromDefinition(
1089
+ const resolvedProps = resolvePropsFromDefinition(
874
1090
  structuredComponentDef,
875
1091
  propsToResolve,
876
1092
  children as ComponentNode | string | (ComponentNode | string)[] | null | undefined,
877
1093
  locale,
878
- i18nConfig
1094
+ i18nConfig,
879
1095
  );
880
1096
 
881
1097
  // Process structure
@@ -889,10 +1105,16 @@ export class ComponentBuilder {
889
1105
  parentProps: parentResolvedProps ?? undefined,
890
1106
  },
891
1107
  viewportWidth,
892
- markedChildren
1108
+ markedChildren,
893
1109
  );
894
1110
 
895
- if (!processedStructure || typeof processedStructure === 'string' || typeof processedStructure === 'number' || Array.isArray(processedStructure)) {
1111
+ if (
1112
+ !processedStructure ||
1113
+ typeof processedStructure === 'string' ||
1114
+ typeof processedStructure === 'number' ||
1115
+ Array.isArray(processedStructure) ||
1116
+ !isProcessedComponentNode(processedStructure)
1117
+ ) {
896
1118
  return null;
897
1119
  }
898
1120
 
@@ -910,13 +1132,13 @@ export class ComponentBuilder {
910
1132
  const nestedParentComponentName = this.getParentComponentNameForNestedComponent(
911
1133
  componentContext,
912
1134
  parentComponentName,
913
- componentName
1135
+ componentName,
914
1136
  );
915
1137
 
916
1138
  const componentElement = this.buildComponent({
917
1139
  node: processedStructure,
918
1140
  key,
919
- customProps: { '__componentProps': resolvedProps },
1141
+ customProps: { __componentProps: resolvedProps },
920
1142
  elementPath,
921
1143
  parentComponentName: nestedParentComponentName,
922
1144
  viewportWidth,
@@ -931,12 +1153,11 @@ export class ComponentBuilder {
931
1153
  cmsItemIndexPath,
932
1154
  cmsListPaths,
933
1155
  templateContext,
934
- componentResolvedProps: resolvedProps
1156
+ componentResolvedProps: resolvedProps,
935
1157
  });
936
1158
 
937
1159
  return h(ErrorBoundary, { key, componentName, level: 'component' }, componentElement);
938
-
939
- } catch (error) {
1160
+ } catch (_error) {
940
1161
  return h(ErrorBoundary, { key, componentName, level: 'component' }, null);
941
1162
  }
942
1163
  }
@@ -949,19 +1170,30 @@ export class ComponentBuilder {
949
1170
  props: Record<string, unknown>,
950
1171
  children: unknown,
951
1172
  customProps: ComponentProps,
952
- options: BuildComponentOptions
1173
+ options: BuildComponentOptions,
953
1174
  ): ReactElement | null {
954
1175
  const {
955
- key = 0, elementPath = [0], parentComponentName = null, viewportWidth = 1920,
956
- componentContext = null, componentRootPath, locale, i18nConfig,
957
- cmsContext = null, cmsLocale = null, collectionItemsMap = {},
958
- itemContext = null, cmsItemIndexPath = null, cmsListPaths = null,
959
- templateContext = null, componentResolvedProps = null,
960
- isSlotContent = false
1176
+ key = 0,
1177
+ elementPath = [0],
1178
+ parentComponentName = null,
1179
+ viewportWidth = 1920,
1180
+ componentContext = null,
1181
+ componentRootPath,
1182
+ locale,
1183
+ i18nConfig,
1184
+ cmsContext = null,
1185
+ cmsLocale = null,
1186
+ collectionItemsMap = {},
1187
+ itemContext = null,
1188
+ cmsItemIndexPath = null,
1189
+ cmsListPaths = null,
1190
+ templateContext = null,
1191
+ componentResolvedProps = null,
1192
+ isSlotContent = false,
961
1193
  } = options;
962
1194
 
963
1195
  const requestedTag = (tag || 'div').toLowerCase();
964
- const finalTag = DOCUMENT_STRUCTURE_TAGS.has(requestedTag) ? 'div' : (tag || 'div');
1196
+ const finalTag = DOCUMENT_STRUCTURE_TAGS.has(requestedTag) ? 'div' : tag || 'div';
965
1197
  const effectiveParentComponentName = this.getEffectiveParentComponentName(componentContext, parentComponentName);
966
1198
 
967
1199
  // Remove __componentProps from customProps before spreading
@@ -975,8 +1207,11 @@ export class ComponentBuilder {
975
1207
  // (buildCustomComponent reads finalProps.style via mergeNodeStyles).
976
1208
  let propsForElement = props;
977
1209
  const nodeStyle = props.style;
978
- if (nodeStyle && typeof nodeStyle === 'object' &&
979
- ('base' in (nodeStyle as object) || 'tablet' in (nodeStyle as object) || 'mobile' in (nodeStyle as object))) {
1210
+ if (
1211
+ nodeStyle &&
1212
+ typeof nodeStyle === 'object' &&
1213
+ ('base' in (nodeStyle as object) || 'tablet' in (nodeStyle as object) || 'mobile' in (nodeStyle as object))
1214
+ ) {
980
1215
  const { style: _, ...rest } = props;
981
1216
  propsForElement = rest;
982
1217
  }
@@ -984,7 +1219,7 @@ export class ComponentBuilder {
984
1219
  const processedProps: Record<string, unknown> = {
985
1220
  ...propsForElement,
986
1221
  ...customPropsForDOM,
987
- key
1222
+ key,
988
1223
  };
989
1224
 
990
1225
  // Add is-current class for <a> tags when href matches current page path
@@ -1004,10 +1239,13 @@ export class ComponentBuilder {
1004
1239
  el.setAttribute('data-cms-item-index', cmsItemIndexPath.join('.'));
1005
1240
  // Set full CMS context as JSON for nested CMS list support
1006
1241
  if (cmsListPaths && cmsListPaths.length === cmsItemIndexPath.length) {
1007
- el.setAttribute('data-cms-context', JSON.stringify({
1008
- itemIndexPath: cmsItemIndexPath,
1009
- listPaths: cmsListPaths
1010
- }));
1242
+ el.setAttribute(
1243
+ 'data-cms-context',
1244
+ JSON.stringify({
1245
+ itemIndexPath: cmsItemIndexPath,
1246
+ listPaths: cmsListPaths,
1247
+ }),
1248
+ );
1011
1249
  }
1012
1250
  }
1013
1251
  if (isComponentRoot) el.setAttribute('data-component-root', 'true');
@@ -1021,9 +1259,7 @@ export class ComponentBuilder {
1021
1259
  const componentDef = this.componentRegistry.get(componentContext);
1022
1260
  const defineVars = componentDef?.component?.defineVars;
1023
1261
  if (defineVars && customProps.__componentProps) {
1024
- const varsToExpose = defineVars === true
1025
- ? Object.keys(componentDef.component.interface || {})
1026
- : defineVars;
1262
+ const varsToExpose = defineVars === true ? Object.keys(componentDef.component.interface || {}) : defineVars;
1027
1263
 
1028
1264
  const propsForJS: Record<string, unknown> = {};
1029
1265
  const resolvedProps = customProps.__componentProps as Record<string, unknown>;
@@ -1047,17 +1283,25 @@ export class ComponentBuilder {
1047
1283
  }
1048
1284
  }
1049
1285
 
1050
- const componentProps = isComponentRoot && customProps.__componentProps
1051
- ? customProps.__componentProps as Record<string, unknown>
1052
- : undefined;
1286
+ const componentProps =
1287
+ isComponentRoot && customProps.__componentProps
1288
+ ? (customProps.__componentProps as Record<string, unknown>)
1289
+ : undefined;
1053
1290
  const componentNameForRegistry = isComponentRoot ? componentContext : parentComponentName;
1054
1291
 
1055
1292
  const metadata = {
1056
1293
  parentComponentName: parentComponentName ?? null,
1057
- componentContext: componentContext ?? null
1294
+ componentContext: componentContext ?? null,
1058
1295
  };
1059
1296
 
1060
- this.elementRegistry.register(elementPath, el, componentNameForRegistry, isComponentRoot, componentProps, metadata);
1297
+ this.elementRegistry.register(
1298
+ elementPath,
1299
+ el,
1300
+ componentNameForRegistry,
1301
+ isComponentRoot,
1302
+ componentProps,
1303
+ metadata,
1304
+ );
1061
1305
  };
1062
1306
 
1063
1307
  // Add onClick handler
@@ -1066,7 +1310,22 @@ export class ComponentBuilder {
1066
1310
  }
1067
1311
 
1068
1312
  // HTML void elements
1069
- const VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
1313
+ const VOID_ELEMENTS = [
1314
+ 'area',
1315
+ 'base',
1316
+ 'br',
1317
+ 'col',
1318
+ 'embed',
1319
+ 'hr',
1320
+ 'img',
1321
+ 'input',
1322
+ 'link',
1323
+ 'meta',
1324
+ 'param',
1325
+ 'source',
1326
+ 'track',
1327
+ 'wbr',
1328
+ ];
1070
1329
  if (VOID_ELEMENTS.includes(finalTag.toLowerCase())) {
1071
1330
  const { children: _ignored, ...voidElementProps } = processedProps;
1072
1331
  return h(finalTag, voidElementProps);
@@ -1074,8 +1333,21 @@ export class ComponentBuilder {
1074
1333
 
1075
1334
  // Build children
1076
1335
  const childElements = this.buildChildren(children as (ComponentNode | string)[], {
1077
- elementPath, parentComponentName: effectiveParentComponentName, viewportWidth, componentContext, componentRootPath,
1078
- locale, i18nConfig, cmsContext, cmsLocale, collectionItemsMap, itemContext, cmsItemIndexPath, cmsListPaths, templateContext, componentResolvedProps
1336
+ elementPath,
1337
+ parentComponentName: effectiveParentComponentName,
1338
+ viewportWidth,
1339
+ componentContext,
1340
+ componentRootPath,
1341
+ locale,
1342
+ i18nConfig,
1343
+ cmsContext,
1344
+ cmsLocale,
1345
+ collectionItemsMap,
1346
+ itemContext,
1347
+ cmsItemIndexPath,
1348
+ cmsListPaths,
1349
+ templateContext,
1350
+ componentResolvedProps,
1079
1351
  });
1080
1352
 
1081
1353
  return h(finalTag, processedProps, childElements);