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
@@ -5,11 +5,33 @@
5
5
  * - renderPageSSR
6
6
  */
7
7
 
8
- import { describe, test, expect, mock, beforeEach } from 'bun:test';
8
+ import { describe, test, expect, mock, beforeEach, afterAll } from 'bun:test';
9
9
 
10
10
  // Mock modules that have side effects or need specific behavior
11
11
  // These MUST be called before importing the module under test
12
12
 
13
+ // bun's mock.module() patches are PROCESS-GLOBAL and persist across test
14
+ // files in the same run. Snapshot the real module exports here (imports are
15
+ // evaluated before this module body runs, so these capture the originals)
16
+ // and restore them in afterAll so later test files (e.g.
17
+ // services/configService.test.ts) see the real implementations.
18
+ import * as realJsonLoader from '../jsonLoader';
19
+ import * as realImageMetadata from './imageMetadata';
20
+ import * as realConfigServiceModule from '../services/configService';
21
+ import * as realValidateStyleCoverage from '../validateStyleCoverage';
22
+
23
+ const REAL_JSON_LOADER = { ...realJsonLoader };
24
+ const REAL_IMAGE_METADATA = { ...realImageMetadata };
25
+ const REAL_CONFIG_SERVICE_MODULE = { ...realConfigServiceModule };
26
+ const REAL_VALIDATE_STYLE_COVERAGE = { ...realValidateStyleCoverage };
27
+
28
+ afterAll(() => {
29
+ mock.module('../jsonLoader', () => REAL_JSON_LOADER);
30
+ mock.module('./imageMetadata', () => REAL_IMAGE_METADATA);
31
+ mock.module('../services/configService', () => REAL_CONFIG_SERVICE_MODULE);
32
+ mock.module('../validateStyleCoverage', () => REAL_VALIDATE_STYLE_COVERAGE);
33
+ });
34
+
13
35
  // We'll store reference to mocks so tests can change behavior
14
36
  let mockImageMetadataMap = new Map();
15
37
  let mockImageFormat: 'webp' | 'avif' = 'webp';
@@ -44,6 +66,7 @@ mock.module('../services/configService', () => ({
44
66
  configService: {
45
67
  getImageFormat: () => mockImageFormat,
46
68
  getResponsiveScales: () => ({ enabled: false, baseReference: 16 }),
69
+ getSocial: () => ({}),
47
70
  load: async () => {},
48
71
  isLoaded: () => true,
49
72
  reset: () => {},
@@ -57,6 +80,7 @@ mock.module('../validateStyleCoverage', () => ({
57
80
  }));
58
81
 
59
82
  import { buildComponentHTML, renderPageSSR } from './ssrRenderer';
83
+ import { generateRuleForClass } from '../../shared/cssGeneration';
60
84
  import type { ComponentDefinition, JSONPage } from '../../shared/types';
61
85
 
62
86
  // ---------------------------------------------------------------------------
@@ -394,11 +418,7 @@ describe('ssrRenderer', () => {
394
418
  const node = {
395
419
  type: 'node',
396
420
  tag: 'p',
397
- children: [
398
- 'Hello ',
399
- { type: 'node', tag: 'strong', children: 'World' },
400
- '!',
401
- ],
421
+ children: ['Hello ', { type: 'node', tag: 'strong', children: 'World' }, '!'],
402
422
  };
403
423
  const html = await render(node);
404
424
  expect(html).toContain('Hello ');
@@ -472,11 +492,7 @@ describe('ssrRenderer', () => {
472
492
  const node = {
473
493
  type: 'node',
474
494
  tag: 'p',
475
- children: [
476
- en,
477
- ' literal ',
478
- { type: 'node', tag: 'span', children: 'static' },
479
- ],
495
+ children: [en, ' literal ', { type: 'node', tag: 'span', children: 'static' }],
480
496
  };
481
497
  const html = await render(node, { i18nConfig });
482
498
  expect(html).toContain('Hello');
@@ -819,7 +835,7 @@ describe('ssrRenderer', () => {
819
835
  {
820
836
  type: 'node',
821
837
  tag: 'span',
822
- if: { _mapping: true, prop: 'showBadge', values: { 'true': true, 'false': false } },
838
+ if: { _mapping: true, prop: 'showBadge', values: { true: true, false: false } },
823
839
  children: 'Badge',
824
840
  },
825
841
  ],
@@ -849,7 +865,7 @@ describe('ssrRenderer', () => {
849
865
  {
850
866
  type: 'node',
851
867
  tag: 'span',
852
- if: { _mapping: true, prop: 'showBadge', values: { 'true': true, 'false': false } },
868
+ if: { _mapping: true, prop: 'showBadge', values: { true: true, false: false } },
853
869
  children: 'Badge',
854
870
  },
855
871
  ],
@@ -879,7 +895,7 @@ describe('ssrRenderer', () => {
879
895
  {
880
896
  type: 'node',
881
897
  tag: 'span',
882
- if: { _mapping: true, prop: 'variant', values: { 'hidden': false } },
898
+ if: { _mapping: true, prop: 'variant', values: { hidden: false } },
883
899
  children: 'Visible',
884
900
  },
885
901
  ],
@@ -976,9 +992,7 @@ describe('ssrRenderer', () => {
976
992
  structure: {
977
993
  type: 'node',
978
994
  tag: 'div',
979
- children: [
980
- { type: 'node', tag: 'span', children: '{{title}}' },
981
- ],
995
+ children: [{ type: 'node', tag: 'span', children: '{{title}}' }],
982
996
  },
983
997
  interface: {
984
998
  title: { type: 'string', default: 'Default Title' },
@@ -1156,7 +1170,7 @@ describe('ssrRenderer', () => {
1156
1170
  // The data-props should be keyed by component name, with label but not count
1157
1171
  const propsMatch = html.match(/data-props="([^"]*)"/);
1158
1172
  if (propsMatch) {
1159
- const propsJson = propsMatch[1].replace(/"/g, '"');
1173
+ const propsJson = propsMatch[1]!.replace(/"/g, '"');
1160
1174
  const allProps = JSON.parse(propsJson);
1161
1175
  expect(allProps.VarsComp2).toBeDefined();
1162
1176
  expect(allProps.VarsComp2.label).toBe('Hello');
@@ -1251,27 +1265,34 @@ describe('ssrRenderer', () => {
1251
1265
  // data-component should contain both names (space-separated)
1252
1266
  const dcMatch = html.match(/data-component="([^"]*)"/);
1253
1267
  expect(dcMatch).toBeTruthy();
1254
- const names = dcMatch![1].split(' ');
1268
+ const names = dcMatch![1]!.split(' ');
1255
1269
  expect(names).toContain('OuterComp');
1256
1270
  expect(names).toContain('InnerComp');
1257
1271
 
1258
1272
  // data-props should be keyed by component name
1259
1273
  const dpMatch = html.match(/data-props="([^"]*)"/);
1260
1274
  expect(dpMatch).toBeTruthy();
1261
- const allProps = JSON.parse(dpMatch![1].replace(/"/g, '"'));
1275
+ const allProps = JSON.parse(dpMatch![1]!.replace(/"/g, '"'));
1262
1276
  expect(allProps.OuterComp).toEqual({ title: 'Hello' });
1263
1277
  expect(allProps.InnerComp).toEqual({ value: 'Hello' });
1264
1278
  });
1265
1279
 
1266
1280
  test('component where processStructure returns a string renders it', async () => {
1267
- const componentDef: ComponentDefinition = {
1268
- component: {
1269
- structure: '{{text}}',
1270
- interface: {
1271
- text: { type: 'string', default: 'Just text' },
1281
+ // Legacy data shape: old projects may store a bare template string as the
1282
+ // component structure. The current ComponentDefinition type only allows a
1283
+ // ComponentNode, but the renderer still supports the string fallback
1284
+ // (renderComponent's `typeof processedStructure === 'string'` branch), so
1285
+ // model the legacy value the way it actually arrives — parsed from JSON.
1286
+ const componentDef: ComponentDefinition = JSON.parse(
1287
+ JSON.stringify({
1288
+ component: {
1289
+ structure: '{{text}}',
1290
+ interface: {
1291
+ text: { type: 'string', default: 'Just text' },
1292
+ },
1272
1293
  },
1273
- },
1274
- };
1294
+ }),
1295
+ );
1275
1296
 
1276
1297
  const node = {
1277
1298
  type: 'component',
@@ -1380,10 +1401,7 @@ describe('ssrRenderer', () => {
1380
1401
  type: 'node',
1381
1402
  tag: 'Link',
1382
1403
  attributes: { to: '/multi' },
1383
- children: [
1384
- { type: 'node', tag: 'span', children: 'Icon' },
1385
- 'Text',
1386
- ],
1404
+ children: [{ type: 'node', tag: 'span', children: 'Icon' }, 'Text'],
1387
1405
  };
1388
1406
  const html = await render(node);
1389
1407
  expect(html).toContain('<a');
@@ -1609,10 +1627,14 @@ describe('ssrRenderer', () => {
1609
1627
  const result = await renderPageSSR(pageData, { Button: buttonDef });
1610
1628
  // The <a> should exist with the component's default class plus the instance override classes.
1611
1629
  expect(result.html).toMatch(/<a\s+href="\/partner"[^>]*class="[^"]*olink[^"]*"/);
1612
- // Instance style for backgroundColor "#252525" produces the bgc-#252525 utility class.
1613
- expect(result.html).toContain('bgc-#252525');
1614
- // Instance style for color "#ffffff" (the override) produces c-#ffffff.
1615
- expect(result.html).toContain('c-#ffffff');
1630
+ // Hex color values become Tailwind arbitrary-value classes. Verify the
1631
+ // INSTANCE overrides (#252525 / #ffffff), not the structure defaults, won.
1632
+ expect(result.html).toContain('bg-[#252525]');
1633
+ expect(result.html).not.toContain('bg-[#ffffff]');
1634
+ expect(generateRuleForClass('bg-[#252525]')).toBe('background-color: #252525;');
1635
+ expect(result.html).toContain('text-[#ffffff]');
1636
+ expect(result.html).not.toContain('text-[#000000]');
1637
+ expect(generateRuleForClass('text-[#ffffff]')).toBe('color: #ffffff;');
1616
1638
  });
1617
1639
 
1618
1640
  test('instance style overrides structure style on the same property', async () => {
@@ -1651,10 +1673,10 @@ describe('ssrRenderer', () => {
1651
1673
  };
1652
1674
 
1653
1675
  const result = await renderPageSSR(pageData, { Button: buttonDef });
1654
- expect(result.html).toContain('mb-32px');
1655
- expect(result.html).not.toContain('mb-0');
1676
+ expect(result.html).toContain('mb-8');
1677
+ expect(result.html).not.toContain('mb-[0]');
1656
1678
  // Untouched structure properties remain.
1657
- expect(result.html).toContain('p-8px');
1679
+ expect(result.html).toContain('p-2');
1658
1680
  });
1659
1681
 
1660
1682
  test('extracts locale from path when not explicitly provided', async () => {
@@ -1684,9 +1706,7 @@ describe('ssrRenderer', () => {
1684
1706
  type: 'list',
1685
1707
  sourceType: 'prop',
1686
1708
  source: ['Apple', 'Banana', 'Cherry'],
1687
- children: [
1688
- { type: 'node', tag: 'li', children: '{{item}}' },
1689
- ],
1709
+ children: [{ type: 'node', tag: 'li', children: '{{item}}' }],
1690
1710
  };
1691
1711
  const html = await render(node);
1692
1712
  expect(html).toContain('<li');
@@ -1699,9 +1719,7 @@ describe('ssrRenderer', () => {
1699
1719
  type: 'list',
1700
1720
  sourceType: 'prop',
1701
1721
  source: [],
1702
- children: [
1703
- { type: 'node', tag: 'li', children: '{{item}}' },
1704
- ],
1722
+ children: [{ type: 'node', tag: 'li', children: '{{item}}' }],
1705
1723
  };
1706
1724
  const html = await render(node);
1707
1725
  expect(html).toBe('');
@@ -1712,9 +1730,7 @@ describe('ssrRenderer', () => {
1712
1730
  type: 'list',
1713
1731
  sourceType: 'collection',
1714
1732
  source: 'posts',
1715
- children: [
1716
- { type: 'node', tag: 'div', children: '{{post.title}}' },
1717
- ],
1733
+ children: [{ type: 'node', tag: 'div', children: '{{post.title}}' }],
1718
1734
  };
1719
1735
  // Without cmsService, returns empty string
1720
1736
  const html = await render(node);
@@ -1801,9 +1817,7 @@ describe('ssrRenderer', () => {
1801
1817
  type: 'list',
1802
1818
  sourceType: 'collection',
1803
1819
  source: 'posts',
1804
- children: [
1805
- { type: 'node', tag: 'div', children: '{{post.title}}' },
1806
- ],
1820
+ children: [{ type: 'node', tag: 'div', children: '{{post.title}}' }],
1807
1821
  };
1808
1822
 
1809
1823
  const html = await render(node, { cmsService: mockCmsService });
@@ -1828,9 +1842,7 @@ describe('ssrRenderer', () => {
1828
1842
  sourceType: 'collection',
1829
1843
  source: 'authors',
1830
1844
  itemAs: 'author',
1831
- children: [
1832
- { type: 'node', tag: 'span', children: '{{author.name}}' },
1833
- ],
1845
+ children: [{ type: 'node', tag: 'span', children: '{{author.name}}' }],
1834
1846
  };
1835
1847
 
1836
1848
  const html = await render(node, { cmsService: mockCmsService });
@@ -1840,9 +1852,7 @@ describe('ssrRenderer', () => {
1840
1852
 
1841
1853
  test('list node with collection source and direct item IDs', async () => {
1842
1854
  const mockCmsService = {
1843
- getItemsByIds: async (collection: string, ids: string[]) => [
1844
- { _id: 'a1', title: 'Featured Post' },
1845
- ],
1855
+ getItemsByIds: async (_collection: string, _ids: string[]) => [{ _id: 'a1', title: 'Featured Post' }],
1846
1856
  getSchema: () => undefined,
1847
1857
  };
1848
1858
 
@@ -1851,9 +1861,7 @@ describe('ssrRenderer', () => {
1851
1861
  sourceType: 'collection',
1852
1862
  source: 'posts',
1853
1863
  items: ['a1'],
1854
- children: [
1855
- { type: 'node', tag: 'div', children: '{{post.title}}' },
1856
- ],
1864
+ children: [{ type: 'node', tag: 'div', children: '{{post.title}}' }],
1857
1865
  };
1858
1866
 
1859
1867
  const html = await render(node, { cmsService: mockCmsService });
@@ -1862,7 +1870,7 @@ describe('ssrRenderer', () => {
1862
1870
 
1863
1871
  test('list node with collection source and items as single string ID', async () => {
1864
1872
  const mockCmsService = {
1865
- getItemsByIds: async (collection: string, ids: string[]) => {
1873
+ getItemsByIds: async (_collection: string, ids: string[]) => {
1866
1874
  expect(ids).toEqual(['single-id']);
1867
1875
  return [{ _id: 'single-id', title: 'Single Post' }];
1868
1876
  },
@@ -1874,9 +1882,7 @@ describe('ssrRenderer', () => {
1874
1882
  sourceType: 'collection',
1875
1883
  source: 'posts',
1876
1884
  items: 'single-id',
1877
- children: [
1878
- { type: 'node', tag: 'div', children: '{{post.title}}' },
1879
- ],
1885
+ children: [{ type: 'node', tag: 'div', children: '{{post.title}}' }],
1880
1886
  };
1881
1887
 
1882
1888
  const html = await render(node, { cmsService: mockCmsService });
@@ -1897,9 +1903,7 @@ describe('ssrRenderer', () => {
1897
1903
  sourceType: 'collection',
1898
1904
  source: 'posts',
1899
1905
  excludeCurrentItem: true,
1900
- children: [
1901
- { type: 'node', tag: 'div', children: '{{post.title}}' },
1902
- ],
1906
+ children: [{ type: 'node', tag: 'div', children: '{{post.title}}' }],
1903
1907
  };
1904
1908
 
1905
1909
  const cmsContext = { cms: { _id: 'current-id', title: 'Current' } };
@@ -1912,14 +1916,10 @@ describe('ssrRenderer', () => {
1912
1916
  const node = {
1913
1917
  type: 'list',
1914
1918
  sourceType: 'prop',
1915
- source: [
1916
- { name: 'A' }, { name: 'B' }, { name: 'C' }, { name: 'D' }, { name: 'E' },
1917
- ],
1919
+ source: [{ name: 'A' }, { name: 'B' }, { name: 'C' }, { name: 'D' }, { name: 'E' }],
1918
1920
  offset: 1,
1919
1921
  limit: 2,
1920
- children: [
1921
- { type: 'node', tag: 'span', children: '{{item.name}}' },
1922
- ],
1922
+ children: [{ type: 'node', tag: 'span', children: '{{item.name}}' }],
1923
1923
  };
1924
1924
  const html = await render(node);
1925
1925
  // After offset 1, items are [B, C, D, E]; after limit 2, items are [B, C]
@@ -1931,9 +1931,7 @@ describe('ssrRenderer', () => {
1931
1931
 
1932
1932
  test('list node with emitTemplate emits a template element', async () => {
1933
1933
  const mockCmsService = {
1934
- queryItems: async () => [
1935
- { _id: '1', title: 'Post 1' },
1936
- ],
1934
+ queryItems: async () => [{ _id: '1', title: 'Post 1' }],
1937
1935
  getSchema: () => undefined,
1938
1936
  };
1939
1937
 
@@ -1942,9 +1940,7 @@ describe('ssrRenderer', () => {
1942
1940
  sourceType: 'collection',
1943
1941
  source: 'posts',
1944
1942
  emitTemplate: true,
1945
- children: [
1946
- { type: 'node', tag: 'div', children: '{{post.title}}' },
1947
- ],
1943
+ children: [{ type: 'node', tag: 'div', children: '{{post.title}}' }],
1948
1944
  };
1949
1945
 
1950
1946
  const html = await render(node, { cmsService: mockCmsService });
@@ -1955,9 +1951,7 @@ describe('ssrRenderer', () => {
1955
1951
 
1956
1952
  test('list node with collection source and schema adds _url to items', async () => {
1957
1953
  const mockCmsService = {
1958
- queryItems: async () => [
1959
- { _id: '1', title: 'Post 1', _slug: 'post-1' },
1960
- ],
1954
+ queryItems: async () => [{ _id: '1', title: 'Post 1', _slug: 'post-1' }],
1961
1955
  getSchema: () => ({
1962
1956
  id: 'posts',
1963
1957
  name: 'Posts',
@@ -1971,9 +1965,7 @@ describe('ssrRenderer', () => {
1971
1965
  type: 'list',
1972
1966
  sourceType: 'collection',
1973
1967
  source: 'posts',
1974
- children: [
1975
- { type: 'node', tag: 'a', children: '{{post.title}}' },
1976
- ],
1968
+ children: [{ type: 'node', tag: 'a', children: '{{post.title}}' }],
1977
1969
  };
1978
1970
 
1979
1971
  const html = await render(node, { cmsService: mockCmsService });
@@ -1985,9 +1977,7 @@ describe('ssrRenderer', () => {
1985
1977
  type: 'list',
1986
1978
  sourceType: 'prop',
1987
1979
  source: ['a', 'b'],
1988
- children: [
1989
- { type: 'node', tag: 'span', children: '{{item}}' },
1990
- ],
1980
+ children: [{ type: 'node', tag: 'span', children: '{{item}}' }],
1991
1981
  };
1992
1982
  const html = await render(node);
1993
1983
  // Should just be the repeated children, no wrapper
@@ -1998,18 +1988,14 @@ describe('ssrRenderer', () => {
1998
1988
 
1999
1989
  test('legacy cms-list type is treated as collection sourceType', async () => {
2000
1990
  const mockCmsService = {
2001
- queryItems: async () => [
2002
- { _id: '1', title: 'Legacy Post' },
2003
- ],
1991
+ queryItems: async () => [{ _id: '1', title: 'Legacy Post' }],
2004
1992
  getSchema: () => undefined,
2005
1993
  };
2006
1994
 
2007
1995
  const node = {
2008
1996
  type: 'cms-list',
2009
1997
  collection: 'posts',
2010
- children: [
2011
- { type: 'node', tag: 'div', children: '{{post.title}}' },
2012
- ],
1998
+ children: [{ type: 'node', tag: 'div', children: '{{post.title}}' }],
2013
1999
  };
2014
2000
 
2015
2001
  const html = await render(node, { cmsService: mockCmsService });
@@ -2020,7 +2006,7 @@ describe('ssrRenderer', () => {
2020
2006
 
2021
2007
  test('list node with items as CMS template resolves from cmsContext', async () => {
2022
2008
  const mockCmsService = {
2023
- getItemsByIds: async (collection: string, ids: string[]) => {
2009
+ getItemsByIds: async (_collection: string, ids: string[]) => {
2024
2010
  expect(ids).toEqual(['ref-1', 'ref-2']);
2025
2011
  return [
2026
2012
  { _id: 'ref-1', name: 'Referenced 1' },
@@ -2035,9 +2021,7 @@ describe('ssrRenderer', () => {
2035
2021
  sourceType: 'collection',
2036
2022
  source: 'authors',
2037
2023
  items: '{{cms.authorIds}}',
2038
- children: [
2039
- { type: 'node', tag: 'span', children: '{{author.name}}' },
2040
- ],
2024
+ children: [{ type: 'node', tag: 'span', children: '{{author.name}}' }],
2041
2025
  };
2042
2026
 
2043
2027
  const cmsContext = { cms: { authorIds: ['ref-1', 'ref-2'] } };
@@ -2057,9 +2041,7 @@ describe('ssrRenderer', () => {
2057
2041
  sourceType: 'collection',
2058
2042
  source: 'authors',
2059
2043
  items: '{{cms.nonexistent}}',
2060
- children: [
2061
- { type: 'node', tag: 'span', children: '{{author.name}}' },
2062
- ],
2044
+ children: [{ type: 'node', tag: 'span', children: '{{author.name}}' }],
2063
2045
  };
2064
2046
 
2065
2047
  const cmsContext = { cms: { title: 'exists' } };
@@ -2069,7 +2051,7 @@ describe('ssrRenderer', () => {
2069
2051
 
2070
2052
  test('list node with items as CMS template resolves single value', async () => {
2071
2053
  const mockCmsService = {
2072
- getItemsByIds: async (collection: string, ids: string[]) => {
2054
+ getItemsByIds: async (_collection: string, ids: string[]) => {
2073
2055
  expect(ids).toEqual(['single-ref']);
2074
2056
  return [{ _id: 'single-ref', name: 'Single' }];
2075
2057
  },
@@ -2081,9 +2063,7 @@ describe('ssrRenderer', () => {
2081
2063
  sourceType: 'collection',
2082
2064
  source: 'authors',
2083
2065
  items: '{{cms.authorId}}',
2084
- children: [
2085
- { type: 'node', tag: 'span', children: '{{author.name}}' },
2086
- ],
2066
+ children: [{ type: 'node', tag: 'span', children: '{{author.name}}' }],
2087
2067
  };
2088
2068
 
2089
2069
  const cmsContext = { cms: { authorId: 'single-ref' } };
@@ -2098,9 +2078,7 @@ describe('ssrRenderer', () => {
2098
2078
  type: 'list',
2099
2079
  sourceType: 'prop',
2100
2080
  source: 'tags',
2101
- children: [
2102
- { type: 'node', tag: 'span', children: '{{item.label}}' },
2103
- ],
2081
+ children: [{ type: 'node', tag: 'span', children: '{{item.label}}' }],
2104
2082
  };
2105
2083
 
2106
2084
  const cmsContext = { cms: { tags: [{ label: 'tag1' }, { label: 'tag2' }] } };
@@ -2114,9 +2092,7 @@ describe('ssrRenderer', () => {
2114
2092
  type: 'list',
2115
2093
  sourceType: 'prop',
2116
2094
  source: '',
2117
- children: [
2118
- { type: 'node', tag: 'span', children: '{{item}}' },
2119
- ],
2095
+ children: [{ type: 'node', tag: 'span', children: '{{item}}' }],
2120
2096
  };
2121
2097
  const html = await render(node);
2122
2098
  expect(html).toBe('');
@@ -2159,13 +2135,10 @@ describe('ssrRenderer', () => {
2159
2135
  tag: 'div',
2160
2136
  children: `${RAW_HTML_PREFIX}${rawSlice}`,
2161
2137
  };
2162
- const result = await buildComponentHTML(
2163
- node as any,
2164
- {},
2165
- {},
2166
- 'fr',
2167
- { defaultLocale: 'en', locales: [{ code: 'en' }, { code: 'fr' }] } as any,
2168
- );
2138
+ const result = await buildComponentHTML(node as any, {}, {}, 'fr', {
2139
+ defaultLocale: 'en',
2140
+ locales: [{ code: 'en' }, { code: 'fr' }],
2141
+ } as any);
2169
2142
  const processed = result.processedRawHtmlCollector.get(rawSlice);
2170
2143
  expect(processed).toContain('href="/fr/about"');
2171
2144
  });
@@ -2188,10 +2161,7 @@ describe('ssrRenderer', () => {
2188
2161
  });
2189
2162
 
2190
2163
  test('renders mixed array of strings and nodes', async () => {
2191
- const nodes = [
2192
- 'Plain text ',
2193
- { type: 'node', tag: 'b', children: 'bold' },
2194
- ];
2164
+ const nodes = ['Plain text ', { type: 'node', tag: 'b', children: 'bold' }];
2195
2165
  const result = await buildComponentHTML(nodes as any);
2196
2166
  expect(result.html).toContain('Plain text');
2197
2167
  expect(result.html).toContain('<b');
@@ -2248,11 +2218,7 @@ describe('ssrRenderer', () => {
2248
2218
  const node = {
2249
2219
  type: 'node',
2250
2220
  tag: 'div',
2251
- children: [
2252
- null,
2253
- { type: 'node', tag: 'span', children: 'valid' },
2254
- undefined,
2255
- ],
2221
+ children: [null, { type: 'node', tag: 'span', children: 'valid' }, undefined],
2256
2222
  };
2257
2223
  const html = await render(node);
2258
2224
  expect(html).toContain('valid');
@@ -2551,8 +2517,8 @@ describe('ssrRenderer', () => {
2551
2517
  };
2552
2518
  const result = await renderFull(node);
2553
2519
  expect(result.preloadImages.length).toBe(1);
2554
- expect(result.preloadImages[0].type).toBe('image/avif');
2555
- expect(result.preloadImages[0].srcset).toBe('/hero.avif 800w');
2520
+ expect(result.preloadImages[0]!.type).toBe('image/avif');
2521
+ expect(result.preloadImages[0]!.srcset).toBe('/hero.avif 800w');
2556
2522
  });
2557
2523
 
2558
2524
  test('collects high-priority images with WebP when no AVIF available', async () => {
@@ -2569,7 +2535,7 @@ describe('ssrRenderer', () => {
2569
2535
  };
2570
2536
  const result = await renderFull(node);
2571
2537
  expect(result.preloadImages.length).toBe(1);
2572
- expect(result.preloadImages[0].type).toBe('image/webp');
2538
+ expect(result.preloadImages[0]!.type).toBe('image/webp');
2573
2539
  });
2574
2540
 
2575
2541
  test('does not collect preload images when fetchpriority is not high', async () => {
@@ -2662,9 +2628,7 @@ describe('ssrRenderer', () => {
2662
2628
  { code: 'fr', name: 'French', nativeName: 'Francais', langTag: 'fr', icon: '/flags/fr.svg' },
2663
2629
  ],
2664
2630
  };
2665
- const slugMappings = [
2666
- { pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } },
2667
- ];
2631
+ const slugMappings = [{ pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } }];
2668
2632
 
2669
2633
  const node = {
2670
2634
  type: 'locale-list',
@@ -2690,9 +2654,7 @@ describe('ssrRenderer', () => {
2690
2654
  { code: 'fr', name: 'French', nativeName: 'Francais', langTag: 'fr' },
2691
2655
  ],
2692
2656
  };
2693
- const slugMappings = [
2694
- { pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } },
2695
- ];
2657
+ const slugMappings = [{ pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } }];
2696
2658
 
2697
2659
  const node = {
2698
2660
  type: 'locale-list',
@@ -2717,9 +2679,7 @@ describe('ssrRenderer', () => {
2717
2679
  { code: 'fr', name: 'French', nativeName: 'Francais', langTag: 'fr' },
2718
2680
  ],
2719
2681
  };
2720
- const slugMappings = [
2721
- { pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } },
2722
- ];
2682
+ const slugMappings = [{ pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } }];
2723
2683
 
2724
2684
  const node = {
2725
2685
  type: 'locale-list',
@@ -2744,9 +2704,7 @@ describe('ssrRenderer', () => {
2744
2704
  { code: 'fr', name: 'French', nativeName: 'Francais', langTag: 'fr' },
2745
2705
  ],
2746
2706
  };
2747
- const slugMappings = [
2748
- { pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } },
2749
- ];
2707
+ const slugMappings = [{ pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } }];
2750
2708
 
2751
2709
  const node = {
2752
2710
  type: 'locale-list',
@@ -2770,9 +2728,7 @@ describe('ssrRenderer', () => {
2770
2728
  { code: 'fr', name: 'French', nativeName: 'Francais', langTag: 'fr' },
2771
2729
  ],
2772
2730
  };
2773
- const slugMappings = [
2774
- { pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } },
2775
- ];
2731
+ const slugMappings = [{ pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } }];
2776
2732
 
2777
2733
  const node = {
2778
2734
  type: 'locale-list',
@@ -2798,9 +2754,7 @@ describe('ssrRenderer', () => {
2798
2754
  { code: 'fr', name: 'French', nativeName: 'Francais', langTag: 'fr', icon: '/flags/fr.svg' },
2799
2755
  ],
2800
2756
  };
2801
- const slugMappings = [
2802
- { pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } },
2803
- ];
2757
+ const slugMappings = [{ pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } }];
2804
2758
 
2805
2759
  const node = {
2806
2760
  type: 'locale-list',
@@ -2824,9 +2778,7 @@ describe('ssrRenderer', () => {
2824
2778
  { code: 'fr', name: 'French', nativeName: 'Francais', langTag: 'fr', icon: '/flags/fr.svg' },
2825
2779
  ],
2826
2780
  };
2827
- const slugMappings = [
2828
- { pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } },
2829
- ];
2781
+ const slugMappings = [{ pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } }];
2830
2782
 
2831
2783
  const node = {
2832
2784
  type: 'locale-list',
@@ -2851,9 +2803,7 @@ describe('ssrRenderer', () => {
2851
2803
  { code: 'fr', name: 'French', nativeName: 'Francais', langTag: 'fr' },
2852
2804
  ],
2853
2805
  };
2854
- const slugMappings = [
2855
- { pageId: 'index', slugs: { en: '', fr: '' } },
2856
- ];
2806
+ const slugMappings = [{ pageId: 'index', slugs: { en: '', fr: '' } }];
2857
2807
 
2858
2808
  const node = {
2859
2809
  type: 'locale-list',
@@ -2882,9 +2832,7 @@ describe('ssrRenderer', () => {
2882
2832
  { code: 'fr', name: 'French', nativeName: 'Francais', langTag: 'fr' },
2883
2833
  ],
2884
2834
  };
2885
- const slugMappings = [
2886
- { pageId: 'index', slugs: { en: '', fr: '' } },
2887
- ];
2835
+ const slugMappings = [{ pageId: 'index', slugs: { en: '', fr: '' } }];
2888
2836
 
2889
2837
  const node = {
2890
2838
  type: 'locale-list',
@@ -2999,9 +2947,7 @@ describe('ssrRenderer', () => {
2999
2947
  structure: {
3000
2948
  type: 'node',
3001
2949
  tag: 'div',
3002
- children: [
3003
- { type: 'slot' },
3004
- ],
2950
+ children: [{ type: 'slot' }],
3005
2951
  },
3006
2952
  interface: {},
3007
2953
  },
@@ -3010,9 +2956,7 @@ describe('ssrRenderer', () => {
3010
2956
  const node = {
3011
2957
  type: 'component',
3012
2958
  component: 'Wrapper',
3013
- children: [
3014
- { type: 'node', tag: 'p', children: 'Slotted Content' },
3015
- ],
2959
+ children: [{ type: 'node', tag: 'p', children: 'Slotted Content' }],
3016
2960
  };
3017
2961
 
3018
2962
  const html = await render(node, { globalComponents: { Wrapper: wrapperDef } });
@@ -3085,7 +3029,16 @@ describe('ssrRenderer', () => {
3085
3029
  };
3086
3030
 
3087
3031
  const cmsContext = { cms: { title: 'My Article', body: 'Article body content' } };
3088
- const result = await renderPageSSR(pageData, { Article: articleDef }, '/', '', undefined, undefined, undefined, cmsContext);
3032
+ const result = await renderPageSSR(
3033
+ pageData,
3034
+ { Article: articleDef },
3035
+ '/',
3036
+ '',
3037
+ undefined,
3038
+ undefined,
3039
+ undefined,
3040
+ cmsContext,
3041
+ );
3089
3042
 
3090
3043
  expect(result.html).toContain('<article');
3091
3044
  expect(result.html).toContain('My Article');
@@ -3102,9 +3055,7 @@ describe('ssrRenderer', () => {
3102
3055
  const node = {
3103
3056
  type: 'node',
3104
3057
  tag: 'div',
3105
- interactiveStyles: [
3106
- { trigger: 'hover', style: { opacity: '0.8' } },
3107
- ],
3058
+ interactiveStyles: [{ trigger: 'hover', style: { opacity: '0.8' } }],
3108
3059
  children: 'Interactive',
3109
3060
  };
3110
3061
  const result = await renderFull(node, { pagePath: '/test-page' });
@@ -3129,9 +3080,7 @@ describe('ssrRenderer', () => {
3129
3080
  structure: {
3130
3081
  type: 'node',
3131
3082
  tag: 'div',
3132
- interactiveStyles: [
3133
- { trigger: 'hover', style: { opacity: '0.5' } },
3134
- ],
3083
+ interactiveStyles: [{ postfix: ':hover', style: { opacity: '0.5' } }],
3135
3084
  children: 'hover me',
3136
3085
  },
3137
3086
  interface: {},
@@ -3166,9 +3115,9 @@ describe('ssrRenderer', () => {
3166
3115
  tag: 'div',
3167
3116
  interactiveStyles: [
3168
3117
  {
3169
- trigger: 'hover',
3118
+ postfix: ':hover',
3170
3119
  style: {
3171
- backgroundColor: { _mapping: true, prop: 'hoverColor', values: { 'red': 'red', 'blue': 'blue' } },
3120
+ backgroundColor: { _mapping: true, prop: 'hoverColor', values: { red: 'red', blue: 'blue' } },
3172
3121
  },
3173
3122
  },
3174
3123
  ],
@@ -3232,8 +3181,8 @@ describe('ssrRenderer', () => {
3232
3181
  // Since we can't directly set templateContext through buildComponentHTML,
3233
3182
  // we use a CMS context + items with {{cms.field}} template
3234
3183
  const mockCmsService = {
3235
- getItemsByIds: async (collection: string, ids: string[]) => {
3236
- return ids.map(id => ({ _id: id, name: `Author ${id}` }));
3184
+ getItemsByIds: async (_collection: string, ids: string[]) => {
3185
+ return ids.map((id) => ({ _id: id, name: `Author ${id}` }));
3237
3186
  },
3238
3187
  getSchema: () => undefined,
3239
3188
  };
@@ -3243,9 +3192,7 @@ describe('ssrRenderer', () => {
3243
3192
  sourceType: 'collection',
3244
3193
  source: 'authors',
3245
3194
  items: '{{cms.relatedAuthors}}',
3246
- children: [
3247
- { type: 'node', tag: 'span', children: '{{author.name}}' },
3248
- ],
3195
+ children: [{ type: 'node', tag: 'span', children: '{{author.name}}' }],
3249
3196
  };
3250
3197
 
3251
3198
  const cmsContext = { cms: { relatedAuthors: ['a1', 'a2'] } };
@@ -3262,7 +3209,7 @@ describe('ssrRenderer', () => {
3262
3209
  const mockCmsService = {
3263
3210
  getItemsByIds: async (_collection: string, ids: string[]) => {
3264
3211
  calledWith.push(ids);
3265
- return ids.map(id => ({ _id: id, name: `Tag ${id}` }));
3212
+ return ids.map((id) => ({ _id: id, name: `Tag ${id}` }));
3266
3213
  },
3267
3214
  getSchema: () => undefined,
3268
3215
  };
@@ -3270,7 +3217,7 @@ describe('ssrRenderer', () => {
3270
3217
  const Card: ComponentDefinition = {
3271
3218
  component: {
3272
3219
  interface: {
3273
- config: { type: 'list' as any, default: [] },
3220
+ config: { type: 'list' as const, itemSchema: {}, default: [] },
3274
3221
  },
3275
3222
  structure: {
3276
3223
  type: 'list',
@@ -3278,9 +3225,7 @@ describe('ssrRenderer', () => {
3278
3225
  source: 'tags',
3279
3226
  itemAs: 'tag',
3280
3227
  items: '{{config.0.tagIds}}',
3281
- children: [
3282
- { type: 'node', tag: 'span', children: '{{tag.name}}' },
3283
- ],
3228
+ children: [{ type: 'node', tag: 'span', children: '{{tag.name}}' }],
3284
3229
  },
3285
3230
  },
3286
3231
  };
@@ -3318,7 +3263,7 @@ describe('ssrRenderer', () => {
3318
3263
  const Card: ComponentDefinition = {
3319
3264
  component: {
3320
3265
  interface: {
3321
- config: { type: 'list' as any, default: [] },
3266
+ config: { type: 'list' as const, itemSchema: {}, default: [] },
3322
3267
  },
3323
3268
  structure: {
3324
3269
  type: 'list',
@@ -3326,9 +3271,7 @@ describe('ssrRenderer', () => {
3326
3271
  source: 'posts',
3327
3272
  itemAs: 'post',
3328
3273
  filter: { category: '{{config.0.collection}}' },
3329
- children: [
3330
- { type: 'node', tag: 'div', children: '{{post.title}}' },
3331
- ],
3274
+ children: [{ type: 'node', tag: 'div', children: '{{post.title}}' }],
3332
3275
  },
3333
3276
  },
3334
3277
  };
@@ -3366,7 +3309,7 @@ describe('ssrRenderer', () => {
3366
3309
  ],
3367
3310
  getItemsByIds: async (_collection: string, ids: string[]) => {
3368
3311
  calledWith.push(ids);
3369
- return ids.map(id => ({ _id: id, name: `Item ${id}` }));
3312
+ return ids.map((id) => ({ _id: id, name: `Item ${id}` }));
3370
3313
  },
3371
3314
  getSchema: () => undefined,
3372
3315
  };
@@ -3377,7 +3320,7 @@ describe('ssrRenderer', () => {
3377
3320
  // type: 'list' so the array prop value passes validation. The prop
3378
3321
  // `tag` will land in componentResolvedProps; the parent loop var
3379
3322
  // `tag` (set to the current outer item) will land in templateContext.
3380
- tag: { type: 'list' as any, default: [] },
3323
+ tag: { type: 'list' as const, itemSchema: {}, default: [] },
3381
3324
  },
3382
3325
  structure: {
3383
3326
  type: 'list',
@@ -3385,9 +3328,7 @@ describe('ssrRenderer', () => {
3385
3328
  source: 'tags',
3386
3329
  itemAs: 'inner',
3387
3330
  items: '{{tag.id}}',
3388
- children: [
3389
- { type: 'node', tag: 'span', children: '{{inner.name}}' },
3390
- ],
3331
+ children: [{ type: 'node', tag: 'span', children: '{{inner.name}}' }],
3391
3332
  },
3392
3333
  },
3393
3334
  };
@@ -3421,9 +3362,10 @@ describe('ssrRenderer', () => {
3421
3362
  test('page-root filter does not silently resolve {{cms.X}} (regression)', async () => {
3422
3363
  // Without a host component, ctx.componentResolvedProps is undefined and
3423
3364
  // ctx.templateContext is undefined → buildListResolutionScope returns
3424
- // undefined → resolveFilterTemplates leaves filter values literal.
3425
- // This pins down the documented behavior: the cmsContext namespace does
3426
- // NOT silently merge into filter scope.
3365
+ // undefined → the template cannot resolve. This pins down the documented
3366
+ // behavior: the cmsContext namespace does NOT silently merge into filter
3367
+ // scope the unresolvable condition is dropped from the query (and
3368
+ // surfaced via logRuntimeError) instead of resolving against cmsContext.
3427
3369
  const queries: any[] = [];
3428
3370
  const mockCmsService = {
3429
3371
  queryItems: async (q: any) => {
@@ -3448,7 +3390,8 @@ describe('ssrRenderer', () => {
3448
3390
  });
3449
3391
 
3450
3392
  expect(queries.length).toBe(1);
3451
- expect(queries[0].filter).toEqual({ category: '{{cms.something}}' });
3393
+ // The condition is dropped, NOT resolved to { category: 'shouldNotBeUsed' }.
3394
+ expect(queries[0].filter).toEqual({});
3452
3395
  });
3453
3396
  });
3454
3397
 
@@ -3495,9 +3438,7 @@ describe('ssrRenderer', () => {
3495
3438
  { code: 'fr', name: 'French', nativeName: 'Francais', langTag: 'fr' },
3496
3439
  ],
3497
3440
  };
3498
- const slugMappings = [
3499
- { pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } },
3500
- ];
3441
+ const slugMappings = [{ pageId: 'about', slugs: { en: 'about', fr: 'a-propos' } }];
3501
3442
 
3502
3443
  const node = {
3503
3444
  type: 'link',
@@ -3524,9 +3465,7 @@ describe('ssrRenderer', () => {
3524
3465
  type: 'list',
3525
3466
  sourceType: 'prop',
3526
3467
  source: [{ name: 'a' }, { name: 'b' }],
3527
- children: [
3528
- { type: 'node', tag: 'span', children: '{{item.name}}' },
3529
- ],
3468
+ children: [{ type: 'node', tag: 'span', children: '{{item.name}}' }],
3530
3469
  };
3531
3470
 
3532
3471
  const result = await renderFull(node, { pagePath: '/test' });
@@ -3546,9 +3485,7 @@ describe('ssrRenderer', () => {
3546
3485
  const node = {
3547
3486
  type: 'embed',
3548
3487
  html: '<b>test</b>',
3549
- interactiveStyles: [
3550
- { trigger: 'hover', style: { opacity: '0.5' } },
3551
- ],
3488
+ interactiveStyles: [{ trigger: 'hover', style: { opacity: '0.5' } }],
3552
3489
  };
3553
3490
 
3554
3491
  const result = await renderFull(node, { pagePath: '/test' });
@@ -3576,9 +3513,7 @@ describe('ssrRenderer', () => {
3576
3513
  const node = {
3577
3514
  type: 'link',
3578
3515
  href: '/test',
3579
- interactiveStyles: [
3580
- { trigger: 'hover', style: { color: 'blue' } },
3581
- ],
3516
+ interactiveStyles: [{ trigger: 'hover', style: { color: 'blue' } }],
3582
3517
  children: 'Hover Link',
3583
3518
  };
3584
3519
 
@@ -3657,9 +3592,7 @@ describe('ssrRenderer', () => {
3657
3592
  structure: {
3658
3593
  type: 'node',
3659
3594
  tag: 'div',
3660
- children: [
3661
- { type: 'slot' },
3662
- ],
3595
+ children: [{ type: 'slot' }],
3663
3596
  },
3664
3597
  interface: {},
3665
3598
  },
@@ -3672,9 +3605,7 @@ describe('ssrRenderer', () => {
3672
3605
  {
3673
3606
  type: 'node',
3674
3607
  tag: 'span',
3675
- interactiveStyles: [
3676
- { trigger: 'hover', style: { opacity: '0.5' } },
3677
- ],
3608
+ interactiveStyles: [{ trigger: 'hover', style: { opacity: '0.5' } }],
3678
3609
  children: 'Slotted',
3679
3610
  },
3680
3611
  ],
@@ -3688,8 +3619,8 @@ describe('ssrRenderer', () => {
3688
3619
  expect(result.interactiveStylesMap.size).toBeGreaterThan(0);
3689
3620
  // The class should use page context (p_ prefix) not component context (c_ prefix)
3690
3621
  const classNames = Array.from(result.interactiveStylesMap.keys());
3691
- const hasPageContext = classNames.some(cls => cls.startsWith('p_'));
3692
- const hasComponentContext = classNames.some(cls => cls.startsWith('c_'));
3622
+ const _hasPageContext = classNames.some((cls) => cls.startsWith('p_'));
3623
+ const _hasComponentContext = classNames.some((cls) => cls.startsWith('c_'));
3693
3624
  // At least check that the styles were registered
3694
3625
  expect(classNames.length).toBeGreaterThan(0);
3695
3626
  });
@@ -3789,7 +3720,7 @@ describe('ssrRenderer', () => {
3789
3720
  const FeatureGrid: ComponentDefinition = {
3790
3721
  component: {
3791
3722
  interface: {
3792
- features: { type: 'list' as any, default: [] },
3723
+ features: { type: 'list' as const, itemSchema: {}, default: [] },
3793
3724
  },
3794
3725
  structure: {
3795
3726
  type: 'node',
@@ -3871,7 +3802,7 @@ describe('ssrRenderer', () => {
3871
3802
  const CardGrid: ComponentDefinition = {
3872
3803
  component: {
3873
3804
  interface: {
3874
- cards: { type: 'list' as any, default: [] },
3805
+ cards: { type: 'list' as const, itemSchema: {}, default: [] },
3875
3806
  },
3876
3807
  structure: {
3877
3808
  type: 'list',
@@ -3947,7 +3878,7 @@ describe('ssrRenderer', () => {
3947
3878
  const FeatureGrid: ComponentDefinition = {
3948
3879
  component: {
3949
3880
  interface: {
3950
- features: { type: 'list' as any, default: [] },
3881
+ features: { type: 'list' as const, itemSchema: {}, default: [] },
3951
3882
  },
3952
3883
  structure: {
3953
3884
  type: 'node',
@@ -4026,7 +3957,7 @@ describe('ssrRenderer', () => {
4026
3957
  const SectionComp: ComponentDefinition = {
4027
3958
  component: {
4028
3959
  interface: {
4029
- features: { type: 'list' as any, default: [] },
3960
+ features: { type: 'list' as const, itemSchema: {}, default: [] },
4030
3961
  },
4031
3962
  structure: {
4032
3963
  type: 'list',
@@ -4109,15 +4040,13 @@ describe('ssrRenderer', () => {
4109
4040
  const ListGrid: ComponentDefinition = {
4110
4041
  component: {
4111
4042
  interface: {
4112
- items: { type: 'list' as any, default: [] },
4043
+ items: { type: 'list' as const, itemSchema: {}, default: [] },
4113
4044
  },
4114
4045
  structure: {
4115
4046
  type: 'list',
4116
4047
  sourceType: 'prop',
4117
4048
  source: 'items',
4118
- children: [
4119
- { type: 'slot' },
4120
- ],
4049
+ children: [{ type: 'slot' }],
4121
4050
  },
4122
4051
  },
4123
4052
  };
@@ -4159,7 +4088,7 @@ describe('ssrRenderer', () => {
4159
4088
  const Gallery: ComponentDefinition = {
4160
4089
  component: {
4161
4090
  interface: {
4162
- items: { type: 'list' as any, default: [] },
4091
+ items: { type: 'list' as const, itemSchema: {}, default: [] },
4163
4092
  },
4164
4093
  structure: {
4165
4094
  type: 'list',
@@ -4217,9 +4146,7 @@ describe('ssrRenderer', () => {
4217
4146
  structure: {
4218
4147
  type: 'node',
4219
4148
  tag: 'section',
4220
- children: [
4221
- { type: 'slot' },
4222
- ],
4149
+ children: [{ type: 'slot' }],
4223
4150
  },
4224
4151
  },
4225
4152
  };
@@ -4229,7 +4156,7 @@ describe('ssrRenderer', () => {
4229
4156
  const Host: ComponentDefinition = {
4230
4157
  component: {
4231
4158
  interface: {
4232
- testimonials: { type: 'list' as any, default: [] },
4159
+ testimonials: { type: 'list' as const, itemSchema: {}, default: [] },
4233
4160
  },
4234
4161
  structure: {
4235
4162
  type: 'node',
@@ -4244,9 +4171,7 @@ describe('ssrRenderer', () => {
4244
4171
  type: 'list',
4245
4172
  sourceType: 'prop',
4246
4173
  source: 'testimonials',
4247
- children: [
4248
- { type: 'node', tag: 'p', children: '{{item.name}}' },
4249
- ],
4174
+ children: [{ type: 'node', tag: 'p', children: '{{item.name}}' }],
4250
4175
  },
4251
4176
  ],
4252
4177
  },
@@ -4259,11 +4184,7 @@ describe('ssrRenderer', () => {
4259
4184
  type: 'component',
4260
4185
  component: 'Host',
4261
4186
  props: {
4262
- testimonials: [
4263
- { name: 'Alice' },
4264
- { name: 'Bob' },
4265
- { name: 'Carol' },
4266
- ],
4187
+ testimonials: [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Carol' }],
4267
4188
  },
4268
4189
  };
4269
4190
 
@@ -4289,7 +4210,7 @@ describe('ssrRenderer', () => {
4289
4210
  const Host: ComponentDefinition = {
4290
4211
  component: {
4291
4212
  interface: {
4292
- testimonials: { type: 'list' as any, default: [] },
4213
+ testimonials: { type: 'list' as const, itemSchema: {}, default: [] },
4293
4214
  },
4294
4215
  structure: {
4295
4216
  type: 'component',
@@ -4299,9 +4220,7 @@ describe('ssrRenderer', () => {
4299
4220
  type: 'list',
4300
4221
  sourceType: 'prop',
4301
4222
  source: '{{testimonials}}',
4302
- children: [
4303
- { type: 'node', tag: 'p', children: '{{item.name}}' },
4304
- ],
4223
+ children: [{ type: 'node', tag: 'p', children: '{{item.name}}' }],
4305
4224
  },
4306
4225
  ],
4307
4226
  },
@@ -4322,15 +4241,13 @@ describe('ssrRenderer', () => {
4322
4241
  const Direct: ComponentDefinition = {
4323
4242
  component: {
4324
4243
  interface: {
4325
- items: { type: 'list' as any, default: [] },
4244
+ items: { type: 'list' as const, itemSchema: {}, default: [] },
4326
4245
  },
4327
4246
  structure: {
4328
4247
  type: 'list',
4329
4248
  sourceType: 'prop',
4330
4249
  source: 'items',
4331
- children: [
4332
- { type: 'node', tag: 'li', children: '{{item.label}}' },
4333
- ],
4250
+ children: [{ type: 'node', tag: 'li', children: '{{item.label}}' }],
4334
4251
  },
4335
4252
  },
4336
4253
  };
@@ -4369,9 +4286,7 @@ describe('ssrRenderer', () => {
4369
4286
  type: 'list',
4370
4287
  sourceType: 'prop',
4371
4288
  source: 'missing',
4372
- children: [
4373
- { type: 'node', tag: 'p', children: '{{item}}' },
4374
- ],
4289
+ children: [{ type: 'node', tag: 'p', children: '{{item}}' }],
4375
4290
  },
4376
4291
  ],
4377
4292
  },
@@ -4565,7 +4480,16 @@ describe('ssrRenderer', () => {
4565
4480
  };
4566
4481
  const result = await buildComponentHTML(
4567
4482
  node as any,
4568
- {}, {}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
4483
+ {},
4484
+ {},
4485
+ undefined,
4486
+ undefined,
4487
+ undefined,
4488
+ undefined,
4489
+ undefined,
4490
+ undefined,
4491
+ undefined,
4492
+ true,
4569
4493
  );
4570
4494
  expect(result.html).toContain('data-element-path="0"');
4571
4495
  expect(result.html).toContain('data-element-path="0,0"');
@@ -4582,19 +4506,29 @@ describe('ssrRenderer', () => {
4582
4506
  structure: {
4583
4507
  type: 'node',
4584
4508
  tag: 'div',
4585
- children: [{
4586
- type: 'list',
4587
- sourceType: 'prop',
4588
- source: 'items',
4589
- children: [{ type: 'node', tag: 'p', children: '{{item.label}}' }],
4590
- }],
4509
+ children: [
4510
+ {
4511
+ type: 'list',
4512
+ sourceType: 'prop',
4513
+ source: 'items',
4514
+ children: [{ type: 'node', tag: 'p', children: '{{item.label}}' }],
4515
+ },
4516
+ ],
4591
4517
  },
4592
4518
  },
4593
4519
  };
4594
4520
  const result = await buildComponentHTML(
4595
4521
  { type: 'component', component: 'Card' } as any,
4596
4522
  { Card },
4597
- {}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
4523
+ {},
4524
+ undefined,
4525
+ undefined,
4526
+ undefined,
4527
+ undefined,
4528
+ undefined,
4529
+ undefined,
4530
+ undefined,
4531
+ true,
4598
4532
  );
4599
4533
  expect(result.html).toContain('data-cms-item-index="0"');
4600
4534
  expect(result.html).toContain('data-cms-item-index="1"');
@@ -4611,7 +4545,15 @@ describe('ssrRenderer', () => {
4611
4545
  const result = await buildComponentHTML(
4612
4546
  { type: 'component', component: 'Card' } as any,
4613
4547
  { Card },
4614
- {}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
4548
+ {},
4549
+ undefined,
4550
+ undefined,
4551
+ undefined,
4552
+ undefined,
4553
+ undefined,
4554
+ undefined,
4555
+ undefined,
4556
+ true,
4615
4557
  );
4616
4558
  expect(result.html).toContain('data-component-root="true"');
4617
4559
  expect(result.html).toContain('data-component-context="Card"');
@@ -4643,11 +4585,129 @@ describe('ssrRenderer', () => {
4643
4585
  const result = await buildComponentHTML(
4644
4586
  page as any,
4645
4587
  { Card },
4646
- {}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true,
4588
+ {},
4589
+ undefined,
4590
+ undefined,
4591
+ undefined,
4592
+ undefined,
4593
+ undefined,
4594
+ undefined,
4595
+ undefined,
4596
+ true,
4647
4597
  );
4648
4598
  expect(result.html).toContain('data-element-path="0"');
4649
4599
  expect(result.html).toContain('data-element-path="0,0"');
4650
4600
  expect(result.html).toContain('data-element-path="0,0,0"');
4651
4601
  });
4602
+
4603
+ // Island / Custom nodes are black boxes meno-core can't render — they show a
4604
+ // placeholder on the canvas. The wrapper must still carry data-element-path so
4605
+ // a click selects the node like any regular element (it didn't, before).
4606
+ test('emits data-element-path on an empty island placeholder wrapper', async () => {
4607
+ const page = {
4608
+ type: 'node',
4609
+ tag: 'div',
4610
+ children: [{ type: 'island', src: 'Counter.tsx', client: { directive: 'load' } }],
4611
+ };
4612
+ const result = await buildComponentHTML(
4613
+ page as any,
4614
+ {},
4615
+ {},
4616
+ undefined,
4617
+ undefined,
4618
+ undefined,
4619
+ undefined,
4620
+ undefined,
4621
+ undefined,
4622
+ undefined,
4623
+ true,
4624
+ );
4625
+ expect(result.html).toContain('data-island="Counter.tsx"');
4626
+ expect(result.html).toContain('data-element-path="0,0"');
4627
+ // Placeholder shows the island's basename when it has no slotted children.
4628
+ expect(result.html).toContain('Counter');
4629
+ });
4630
+
4631
+ test('emits data-element-path on a custom node wrapper and stamps its slotted children', async () => {
4632
+ const page = {
4633
+ type: 'node',
4634
+ tag: 'div',
4635
+ children: [
4636
+ {
4637
+ type: 'custom',
4638
+ src: 'Fancy.astro',
4639
+ children: [{ type: 'node', tag: 'span', children: ['slotted'] }],
4640
+ },
4641
+ ],
4642
+ };
4643
+ const result = await buildComponentHTML(
4644
+ page as any,
4645
+ {},
4646
+ {},
4647
+ undefined,
4648
+ undefined,
4649
+ undefined,
4650
+ undefined,
4651
+ undefined,
4652
+ undefined,
4653
+ undefined,
4654
+ true,
4655
+ );
4656
+ expect(result.html).toContain('data-custom="Fancy.astro"');
4657
+ // Wrapper is page-absolute [0,0]; the slotted child appends its index → [0,0,0].
4658
+ expect(result.html).toContain('data-element-path="0,0"');
4659
+ expect(result.html).toContain('data-element-path="0,0,0"');
4660
+ expect(result.html).toContain('slotted');
4661
+ });
4662
+ });
4663
+
4664
+ // Remote-data lists (sourceType: 'remote'): the SSR fetches the endpoint at render time and
4665
+ // applies the query, mirroring the meno-astro runtime's getRemoteData so the editor SSR matches
4666
+ // the real Astro build (previously processList had no 'remote' branch → always rendered empty).
4667
+ describe('processList — remote source', () => {
4668
+ const realFetch = globalThis.fetch;
4669
+ const mockFetch = (payload: unknown, ok = true) => {
4670
+ globalThis.fetch = mock(
4671
+ async () => ({ ok, json: async () => payload }) as unknown as Response,
4672
+ ) as unknown as typeof fetch;
4673
+ };
4674
+ afterAll(() => {
4675
+ globalThis.fetch = realFetch;
4676
+ });
4677
+
4678
+ const remoteList = (extra: Record<string, unknown> = {}) => ({
4679
+ type: 'node',
4680
+ tag: 'div',
4681
+ children: [
4682
+ {
4683
+ type: 'list',
4684
+ sourceType: 'remote',
4685
+ url: 'https://api.example.com/coins',
4686
+ children: [{ type: 'node', tag: 'span', children: ['{{item.name}}'] }],
4687
+ ...extra,
4688
+ },
4689
+ ],
4690
+ });
4691
+
4692
+ test('fetches the endpoint and renders an element per item', async () => {
4693
+ mockFetch([{ name: 'Bitcoin' }, { name: 'Ethereum' }]);
4694
+ const html = await render(remoteList());
4695
+ expect(html).toContain('Bitcoin');
4696
+ expect(html).toContain('Ethereum');
4697
+ });
4698
+
4699
+ test('navigates the dot-path and applies limit', async () => {
4700
+ mockFetch({ data: { items: [{ name: 'A' }, { name: 'B' }, { name: 'C' }] } });
4701
+ const html = await render(remoteList({ path: 'data.items', limit: 2 }));
4702
+ expect(html).toContain('A');
4703
+ expect(html).toContain('B');
4704
+ expect(html).not.toContain('C');
4705
+ });
4706
+
4707
+ test('renders empty (no crash) when the endpoint fails', async () => {
4708
+ mockFetch(null, false);
4709
+ const html = await render(remoteList());
4710
+ expect(html).not.toContain('span');
4711
+ });
4652
4712
  });
4653
4713
  });