meno-core 1.0.54 → 1.1.1

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 (298) hide show
  1. package/bin/cli.ts +19 -205
  2. package/build-astro.ts +31 -27
  3. package/dist/bin/cli.js +15 -154
  4. package/dist/bin/cli.js.map +2 -2
  5. package/dist/chunks/{chunk-3XER4E5W.js → chunk-4ZRU52J2.js} +6 -5
  6. package/dist/chunks/{chunk-3XER4E5W.js.map → chunk-4ZRU52J2.js.map} +2 -2
  7. package/dist/chunks/{chunk-NVRBTSQG.js → chunk-5X4HCN7J.js} +4 -4
  8. package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
  9. package/dist/chunks/{chunk-FZITJSSS.js → chunk-7CCVOL5M.js} +14 -14
  10. package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
  11. package/dist/chunks/{chunk-AE3QK5QW.js → chunk-7ZLF4NE5.js} +31 -26
  12. package/dist/chunks/chunk-7ZLF4NE5.js.map +7 -0
  13. package/dist/chunks/{chunk-HIZMY3EP.js → chunk-AR7LHIFY.js} +8 -2
  14. package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
  15. package/dist/chunks/{chunk-7HWQUVTU.js → chunk-J4IPTP5X.js} +1839 -443
  16. package/dist/chunks/chunk-J4IPTP5X.js.map +7 -0
  17. package/dist/chunks/{chunk-5ETZFREW.js → chunk-QWTQZHG3.js} +186 -79
  18. package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
  19. package/dist/chunks/{chunk-STDY3OVM.js → chunk-XTKNX4FW.js} +3 -3
  20. package/dist/chunks/{chunk-STDY3OVM.js.map → chunk-XTKNX4FW.js.map} +2 -2
  21. package/dist/chunks/{fs-ZI5JEU7V.js → fs-G3ANWEFP.js} +2 -2
  22. package/dist/lib/client/index.js +381 -159
  23. package/dist/lib/client/index.js.map +3 -3
  24. package/dist/lib/server/index.js +14365 -6276
  25. package/dist/lib/server/index.js.map +4 -4
  26. package/dist/lib/shared/index.js +296 -87
  27. package/dist/lib/shared/index.js.map +4 -4
  28. package/dist/lib/shared/richtext/index.js +1 -1
  29. package/dist/lib/test-utils/index.js +27 -21
  30. package/dist/lib/test-utils/index.js.map +2 -2
  31. package/lib/client/ErrorBoundary.test.tsx +147 -90
  32. package/lib/client/ErrorBoundary.tsx +8 -5
  33. package/lib/client/componentRegistry.ts +0 -1
  34. package/lib/client/core/ComponentBuilder.test.ts +150 -0
  35. package/lib/client/core/ComponentBuilder.ts +112 -12
  36. package/lib/client/core/ComponentRenderer.test.tsx +1 -2
  37. package/lib/client/core/builders/embedBuilder.ts +1 -1
  38. package/lib/client/core/builders/linkBuilder.ts +2 -2
  39. package/lib/client/core/builders/linkNodeBuilder.ts +1 -1
  40. package/lib/client/core/builders/listBuilder.ts +41 -4
  41. package/lib/client/core/builders/localeListBuilder.ts +2 -2
  42. package/lib/client/core/cmsTemplateProcessor.ts +1 -2
  43. package/lib/client/hmr/HMRManager.tsx +39 -30
  44. package/lib/client/hmrCssReload.ts +35 -6
  45. package/lib/client/hmrWebSocket.ts +5 -5
  46. package/lib/client/hooks/useColorVariables.ts +12 -9
  47. package/lib/client/hooks/usePropertyAutocomplete.ts +1 -1
  48. package/lib/client/hooks/useVariables.ts +3 -1
  49. package/lib/client/meno-filter/MenoFilter.test.ts +28 -28
  50. package/lib/client/meno-filter/MenoFilter.ts +9 -6
  51. package/lib/client/meno-filter/bindings.ts +3 -3
  52. package/lib/client/meno-filter/init.ts +3 -3
  53. package/lib/client/meno-filter/renderer.ts +8 -4
  54. package/lib/client/meno-filter/ui.ts +1 -1
  55. package/lib/client/meno-filter/updates.ts +3 -2
  56. package/lib/client/meno-filter/utils.ts +1 -1
  57. package/lib/client/navigation.test.ts +40 -44
  58. package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
  59. package/lib/client/responsiveStyleResolver.test.ts +5 -5
  60. package/lib/client/responsiveStyleResolver.ts +61 -4
  61. package/lib/client/routing/RouteLoader.test.ts +1 -1
  62. package/lib/client/routing/RouteLoader.ts +8 -3
  63. package/lib/client/routing/Router.tsx +181 -152
  64. package/lib/client/scripts/ScriptExecutor.ts +3 -3
  65. package/lib/client/services/PrefetchService.test.ts +1 -1
  66. package/lib/client/services/PrefetchService.ts +13 -5
  67. package/lib/client/styleProcessor.ts +9 -3
  68. package/lib/client/styles/StyleInjector.ts +1 -1
  69. package/lib/client/styles/UtilityClassCollector.ts +54 -13
  70. package/lib/client/templateEngine.test.ts +17 -23
  71. package/lib/client/templateEngine.ts +7 -13
  72. package/lib/client/theme.test.ts +1 -1
  73. package/lib/server/__integration__/server-lifecycle.test.ts +3 -1
  74. package/lib/server/__integration__/static-assets.test.ts +2 -2
  75. package/lib/server/__integration__/test-helpers.ts +3 -4
  76. package/lib/server/astro/cmsPageEmitter.ts +4 -5
  77. package/lib/server/astro/componentEmitter.ts +11 -8
  78. package/lib/server/astro/nodeToAstro.test.ts +4 -4
  79. package/lib/server/astro/nodeToAstro.ts +41 -36
  80. package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +11 -11
  81. package/lib/server/astro/normalizeOrphanTemplateProps.ts +7 -6
  82. package/lib/server/astro/pageEmitter.ts +1 -1
  83. package/lib/server/astro/templateTransformer.ts +3 -3
  84. package/lib/server/createServer.ts +2 -2
  85. package/lib/server/cssGenerator.ts +2 -2
  86. package/lib/server/fileWatcher.test.ts +41 -4
  87. package/lib/server/fileWatcher.ts +102 -23
  88. package/lib/server/index.ts +7 -16
  89. package/lib/server/jsonLoader.test.ts +3 -3
  90. package/lib/server/jsonLoader.ts +8 -5
  91. package/lib/server/middleware/cors.test.ts +1 -1
  92. package/lib/server/middleware/cors.ts +2 -2
  93. package/lib/server/middleware/errorHandler.test.ts +2 -2
  94. package/lib/server/middleware/logger.test.ts +3 -3
  95. package/lib/server/middleware/logger.ts +7 -7
  96. package/lib/server/migrateTemplates.ts +3 -3
  97. package/lib/server/projectContext.ts +1 -1
  98. package/lib/server/providers/fileSystemCMSProvider.test.ts +18 -4
  99. package/lib/server/providers/fileSystemCMSProvider.ts +27 -18
  100. package/lib/server/routes/api/cms.ts +1 -1
  101. package/lib/server/routes/api/colors.test.ts +2 -2
  102. package/lib/server/routes/api/components.ts +1 -1
  103. package/lib/server/routes/api/config.ts +3 -3
  104. package/lib/server/routes/api/core-routes.ts +2 -2
  105. package/lib/server/routes/api/enums.test.ts +2 -2
  106. package/lib/server/routes/api/functions.ts +1 -1
  107. package/lib/server/routes/api/variables.test.ts +2 -2
  108. package/lib/server/routes/index.ts +2 -2
  109. package/lib/server/routes/pages.ts +1 -2
  110. package/lib/server/routes/static.ts +1 -2
  111. package/lib/server/runtime/bundler.ts +65 -43
  112. package/lib/server/runtime/fs.ts +13 -13
  113. package/lib/server/runtime/httpServer.ts +3 -3
  114. package/lib/server/services/ColorService.test.ts +3 -3
  115. package/lib/server/services/ColorService.ts +5 -2
  116. package/lib/server/services/EnumService.test.ts +2 -2
  117. package/lib/server/services/EnumService.ts +4 -1
  118. package/lib/server/services/VariableService.test.ts +7 -7
  119. package/lib/server/services/VariableService.ts +5 -2
  120. package/lib/server/services/cmsService.test.ts +44 -44
  121. package/lib/server/services/cmsService.ts +17 -97
  122. package/lib/server/services/componentService.test.ts +10 -16
  123. package/lib/server/services/componentService.ts +20 -12
  124. package/lib/server/services/configService.test.ts +13 -14
  125. package/lib/server/services/configService.ts +30 -0
  126. package/lib/server/services/fileWatcherService.ts +2 -2
  127. package/lib/server/services/pageService.test.ts +7 -7
  128. package/lib/server/services/pageService.ts +18 -5
  129. package/lib/server/ssr/attributeBuilder.ts +2 -2
  130. package/lib/server/ssr/cmsSSRProcessor.ts +1 -2
  131. package/lib/server/ssr/errorOverlay.ts +1 -1
  132. package/lib/server/ssr/htmlGenerator.nonce.test.ts +6 -5
  133. package/lib/server/ssr/htmlGenerator.test.ts +0 -1
  134. package/lib/server/ssr/htmlGenerator.ts +25 -9
  135. package/lib/server/ssr/imageMetadata.ts +2 -2
  136. package/lib/server/ssr/jsCollector.test.ts +5 -5
  137. package/lib/server/ssr/liveReloadIntegration.test.ts +7 -8
  138. package/lib/server/ssr/ssrRenderer.branches.test.ts +9 -9
  139. package/lib/server/ssr/ssrRenderer.test.ts +150 -15
  140. package/lib/server/ssr/ssrRenderer.ts +218 -40
  141. package/lib/server/ssrRenderer.test.ts +6 -6
  142. package/lib/server/utils/jsonLineMapper.ts +7 -7
  143. package/lib/server/validateStyleCoverage.ts +0 -1
  144. package/lib/server/websocketManager.ts +12 -0
  145. package/lib/shared/breakpoints.test.ts +5 -5
  146. package/lib/shared/cmsQuery.test.ts +81 -0
  147. package/lib/shared/cmsQuery.ts +92 -0
  148. package/lib/shared/cmsQueryParser.ts +11 -7
  149. package/lib/shared/colorConversions.ts +9 -9
  150. package/lib/shared/colorVariableUtils.test.ts +22 -0
  151. package/lib/shared/colorVariableUtils.ts +19 -5
  152. package/lib/shared/constants.ts +4 -0
  153. package/lib/shared/cssGeneration.test.ts +33 -0
  154. package/lib/shared/cssGeneration.ts +50 -35
  155. package/lib/shared/cssProperties.test.ts +64 -64
  156. package/lib/shared/cssProperties.ts +2 -2
  157. package/lib/shared/elementClassName.ts +2 -2
  158. package/lib/shared/errorLogger.test.ts +12 -12
  159. package/lib/shared/expressionEvaluator.test.ts +27 -1
  160. package/lib/shared/expressionEvaluator.ts +9 -0
  161. package/lib/shared/fontLoader.test.ts +13 -13
  162. package/lib/shared/fontLoader.ts +2 -2
  163. package/lib/shared/gradientUtils.test.ts +2 -2
  164. package/lib/shared/gradientUtils.ts +15 -8
  165. package/lib/shared/hrefRefs.test.ts +2 -2
  166. package/lib/shared/hrefRefs.ts +1 -1
  167. package/lib/shared/i18n.test.ts +3 -3
  168. package/lib/shared/i18n.ts +5 -4
  169. package/lib/shared/index.ts +29 -0
  170. package/lib/shared/inlineSvgStyleRules.ts +12 -9
  171. package/lib/shared/interactiveStyleMappings.test.ts +21 -21
  172. package/lib/shared/interactiveStyleMappings.ts +2 -8
  173. package/lib/shared/interactiveStyles.test.ts +3 -1
  174. package/lib/shared/itemTemplateUtils.test.ts +5 -5
  175. package/lib/shared/itemTemplateUtils.ts +9 -5
  176. package/lib/shared/jsonRepair.ts +8 -8
  177. package/lib/shared/libraryLoader.test.ts +6 -6
  178. package/lib/shared/linkUtils.ts +1 -1
  179. package/lib/shared/logger.test.ts +66 -0
  180. package/lib/shared/logger.ts +94 -0
  181. package/lib/shared/markdown.ts +25 -0
  182. package/lib/shared/nodeUtils.test.ts +13 -9
  183. package/lib/shared/nodeUtils.ts +27 -4
  184. package/lib/shared/pathSecurity.ts +1 -1
  185. package/lib/shared/pathUtils.ts +1 -2
  186. package/lib/shared/paths/PathConverter.ts +3 -3
  187. package/lib/shared/paths/PathUtils.ts +4 -3
  188. package/lib/shared/paths/PathValidator.ts +1 -1
  189. package/lib/shared/permissions.test.ts +168 -0
  190. package/lib/shared/permissions.ts +162 -0
  191. package/lib/shared/propResolver.test.ts +1 -1
  192. package/lib/shared/propResolver.ts +1 -1
  193. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +5 -3
  194. package/lib/shared/registry/ClientRegistry.ts +0 -1
  195. package/lib/shared/registry/ComponentRegistry.test.ts +3 -3
  196. package/lib/shared/registry/ComponentRegistry.ts +6 -1
  197. package/lib/shared/registry/NodeTypeDefinition.ts +2 -2
  198. package/lib/shared/registry/SSRRegistry.ts +0 -1
  199. package/lib/shared/registry/fieldPresets.ts +3 -1
  200. package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
  201. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +1 -1
  202. package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
  203. package/lib/shared/registry/nodeTypes/ListNodeType.ts +43 -7
  204. package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
  205. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +6 -1
  206. package/lib/shared/registry/nodeTypes/index.ts +16 -1
  207. package/lib/shared/responsiveScaling.test.ts +1 -3
  208. package/lib/shared/responsiveScaling.ts +6 -5
  209. package/lib/shared/responsiveStyleUtils.test.ts +0 -1
  210. package/lib/shared/richtext/htmlToTiptap.test.ts +116 -116
  211. package/lib/shared/richtext/htmlToTiptap.ts +1 -1
  212. package/lib/shared/styleNodeUtils.ts +17 -14
  213. package/lib/shared/tailwindThemeScale.ts +256 -0
  214. package/lib/shared/themeDefaults.test.ts +0 -3
  215. package/lib/shared/tree/PathBuilder.test.ts +1 -7
  216. package/lib/shared/tree/PathBuilder.ts +15 -13
  217. package/lib/shared/treePathUtils.test.ts +49 -2
  218. package/lib/shared/treePathUtils.ts +40 -4
  219. package/lib/shared/types/api.ts +105 -7
  220. package/lib/shared/types/cms.test.ts +34 -0
  221. package/lib/shared/types/cms.ts +65 -13
  222. package/lib/shared/types/components.ts +24 -0
  223. package/lib/shared/types/errors.test.ts +1 -1
  224. package/lib/shared/types/index.ts +23 -0
  225. package/lib/shared/types/permissions.ts +139 -0
  226. package/lib/shared/types/variables.ts +3 -3
  227. package/lib/shared/utilityClassConfig.ts +219 -0
  228. package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
  229. package/lib/shared/utilityClassMapper.test.ts +401 -26
  230. package/lib/shared/utilityClassMapper.ts +193 -29
  231. package/lib/shared/utilityClassNames.test.ts +38 -0
  232. package/lib/shared/utilityClassNames.ts +301 -23
  233. package/lib/shared/validation/index.ts +1 -0
  234. package/lib/shared/validation/permissionsValidators.test.ts +49 -0
  235. package/lib/shared/validation/permissionsValidators.ts +49 -0
  236. package/lib/shared/validation/propValidator.test.ts +1 -1
  237. package/lib/shared/validation/propValidator.ts +1 -1
  238. package/lib/shared/validation/schemas.test.ts +41 -0
  239. package/lib/shared/validation/schemas.ts +159 -13
  240. package/lib/shared/validation/validators.test.ts +2 -2
  241. package/lib/shared/viewportUnits.test.ts +13 -0
  242. package/lib/shared/viewportUnits.ts +12 -5
  243. package/lib/test-utils/factories/ConsoleMockFactory.ts +7 -7
  244. package/lib/test-utils/factories/DomMockFactory.ts +10 -4
  245. package/lib/test-utils/factories/ServerMockFactory.ts +1 -1
  246. package/lib/test-utils/factories/StoreMockFactory.ts +5 -5
  247. package/lib/test-utils/mockFactories.ts +1 -1
  248. package/lib/test-utils/mocks.ts +3 -3
  249. package/package.json +3 -5
  250. package/scripts/build-for-publish.mjs +2 -4
  251. package/scripts/build-meno-filter.ts +3 -3
  252. package/tsconfig.json +1 -0
  253. package/vite.config.ts +1 -1
  254. package/build-next.ts +0 -1374
  255. package/build-static.test.ts +0 -453
  256. package/build-static.ts +0 -1072
  257. package/dist/build-static.js +0 -37
  258. package/dist/chunks/chunk-2FN4UOVO.js +0 -6465
  259. package/dist/chunks/chunk-2FN4UOVO.js.map +0 -7
  260. package/dist/chunks/chunk-5ETZFREW.js.map +0 -7
  261. package/dist/chunks/chunk-7E4IF5L7.js +0 -245
  262. package/dist/chunks/chunk-7E4IF5L7.js.map +0 -7
  263. package/dist/chunks/chunk-7HWQUVTU.js.map +0 -7
  264. package/dist/chunks/chunk-AE3QK5QW.js.map +0 -7
  265. package/dist/chunks/chunk-F6KTJYGV.js +0 -322
  266. package/dist/chunks/chunk-F6KTJYGV.js.map +0 -7
  267. package/dist/chunks/chunk-FZITJSSS.js.map +0 -7
  268. package/dist/chunks/chunk-GSYYA5GX.js +0 -77
  269. package/dist/chunks/chunk-GSYYA5GX.js.map +0 -7
  270. package/dist/chunks/chunk-HIZMY3EP.js.map +0 -7
  271. package/dist/chunks/chunk-I2WEGYA7.js +0 -424
  272. package/dist/chunks/chunk-I2WEGYA7.js.map +0 -7
  273. package/dist/chunks/chunk-JNO3CNLJ.js +0 -464
  274. package/dist/chunks/chunk-JNO3CNLJ.js.map +0 -7
  275. package/dist/chunks/chunk-NVRBTSQG.js.map +0 -7
  276. package/dist/chunks/chunk-Q4OBWKXG.js +0 -1143
  277. package/dist/chunks/chunk-Q4OBWKXG.js.map +0 -7
  278. package/dist/chunks/chunk-QTE32Y53.js +0 -3829
  279. package/dist/chunks/chunk-QTE32Y53.js.map +0 -7
  280. package/dist/chunks/configService-PRJZF7Y6.js +0 -14
  281. package/dist/chunks/configService-PRJZF7Y6.js.map +0 -7
  282. package/dist/chunks/constants-KIQEYMAM.js +0 -46
  283. package/dist/chunks/constants-KIQEYMAM.js.map +0 -7
  284. package/dist/chunks/fs-ZI5JEU7V.js.map +0 -7
  285. package/dist/entries/server-router.js +0 -62
  286. package/dist/entries/server-router.js.map +0 -7
  287. package/entries/server-router.tsx +0 -75
  288. package/lib/server/providers/fileSystemPageProvider.test.ts +0 -84
  289. package/lib/server/providers/fileSystemPageProvider.ts +0 -179
  290. package/lib/server/webflow/buildWebflow.ts +0 -623
  291. package/lib/server/webflow/index.ts +0 -23
  292. package/lib/server/webflow/nodeToWebflow.test.ts +0 -3102
  293. package/lib/server/webflow/nodeToWebflow.ts +0 -2372
  294. package/lib/server/webflow/styleMapper.test.ts +0 -348
  295. package/lib/server/webflow/styleMapper.ts +0 -687
  296. package/lib/server/webflow/templateWrapper.ts +0 -49
  297. package/lib/server/webflow/types.ts +0 -389
  298. /package/dist/{build-static.js.map → chunks/fs-G3ANWEFP.js.map} +0 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Permission resolver
3
+ *
4
+ * Pure functions that turn a {@link PermissionsPolicy} + a
5
+ * {@link PermissionIdentity} into a queryable {@link ResolvedPermissions}, and
6
+ * answer "can this identity edit this resource?". No IO — the policy is loaded
7
+ * and the identity gathered by the caller. Shared by the studio client (UI
8
+ * gating) and any future server-side mirror.
9
+ *
10
+ * See `./types/permissions.ts` for the data model and the SOFT-enforcement
11
+ * caveat.
12
+ */
13
+
14
+ import type {
15
+ PermissionGrant,
16
+ PermissionIdentity,
17
+ PermissionRole,
18
+ PermissionRule,
19
+ PermissionsPolicy,
20
+ ResolvedPermissions,
21
+ ResourceKind,
22
+ ResourceRef,
23
+ } from './types/permissions';
24
+
25
+ /** Role capability ordering, low → high. */
26
+ const ROLE_RANK: Record<PermissionRole, number> = { viewer: 0, editor: 1, admin: 2 };
27
+
28
+ /** Maps a {@link ResourceKind} to its {@link PermissionRule} key. */
29
+ const RULE_KEY: Record<ResourceKind, keyof PermissionRule> = {
30
+ page: 'pages',
31
+ component: 'components',
32
+ cms: 'cms',
33
+ config: 'config',
34
+ };
35
+
36
+ /** The empty (all-kinds-present) allow rule used as a merge seed. */
37
+ function emptyAllow(): Required<PermissionRule> {
38
+ return { pages: [], components: [], cms: [], config: [] };
39
+ }
40
+
41
+ function higherRole(a: PermissionRole, b: PermissionRole): PermissionRole {
42
+ return ROLE_RANK[b] > ROLE_RANK[a] ? b : a;
43
+ }
44
+
45
+ /** A grant's role: explicit, else `editor` when it carries an allow-list, else `viewer`. */
46
+ function grantRole(grant: PermissionGrant): PermissionRole {
47
+ if (grant.role) return grant.role;
48
+ return grant.allow ? 'editor' : 'viewer';
49
+ }
50
+
51
+ function mergeAllow(grants: PermissionGrant[]): Required<PermissionRule> {
52
+ const merged = emptyAllow();
53
+ for (const grant of grants) {
54
+ const allow = grant.allow;
55
+ if (!allow) continue;
56
+ for (const kind of Object.keys(merged) as (keyof PermissionRule)[]) {
57
+ const patterns = allow[kind];
58
+ if (patterns) merged[kind].push(...patterns);
59
+ }
60
+ }
61
+ // Dedupe each list while preserving order.
62
+ for (const kind of Object.keys(merged) as (keyof PermissionRule)[]) {
63
+ merged[kind] = [...new Set(merged[kind])];
64
+ }
65
+ return merged;
66
+ }
67
+
68
+ const REGEX_SPECIALS = /[.+?^${}()|[\]\\]/g;
69
+
70
+ /**
71
+ * Glob match for a single permission pattern against a value. `*` matches any
72
+ * run of characters (including `/`); every other character is literal. An exact
73
+ * (wildcard-free) pattern must equal the value.
74
+ */
75
+ export function matchesPermissionPattern(pattern: string, value: string): boolean {
76
+ if (pattern === value) return true;
77
+ if (!pattern.includes('*')) return false;
78
+ const source = pattern
79
+ .split('*')
80
+ .map((segment) => segment.replace(REGEX_SPECIALS, '\\$&'))
81
+ .join('.*');
82
+ return new RegExp(`^${source}$`).test(value);
83
+ }
84
+
85
+ /** True if `value` matches any pattern in `patterns`. */
86
+ export function matchesAnyPermissionPattern(patterns: string[] | undefined, value: string): boolean {
87
+ return !!patterns && patterns.some((pattern) => matchesPermissionPattern(pattern, value));
88
+ }
89
+
90
+ /**
91
+ * Resolve a policy + identity into a capability set.
92
+ *
93
+ * - `policy == null` → an UNMANAGED project: everyone edits everything (status
94
+ * quo). `canManagePolicy` (who may create the first policy) is gated to repo
95
+ * admins, or anyone on a local/unauthenticated run.
96
+ * - With a policy present, an identity not listed in `members`/`teams` gets
97
+ * `defaultRole` (default `viewer`). `login: null` is treated the same way —
98
+ * callers that want to bypass gating for local runs should skip resolution
99
+ * rather than pass a null login.
100
+ * - A GitHub repo admin (`identity.isRepoAdmin`) is ALWAYS an admin here,
101
+ * regardless of the policy (bootstrap safety against lockout).
102
+ */
103
+ export function resolvePermissions(
104
+ policy: PermissionsPolicy | null | undefined,
105
+ identity: PermissionIdentity,
106
+ ): ResolvedPermissions {
107
+ const login = identity.login ?? null;
108
+ const isRepoAdmin = identity.isRepoAdmin === true;
109
+
110
+ if (!policy) {
111
+ return {
112
+ login,
113
+ role: 'admin',
114
+ isAdmin: true,
115
+ canManagePolicy: login == null || isRepoAdmin,
116
+ allow: emptyAllow(),
117
+ };
118
+ }
119
+
120
+ const grants: PermissionGrant[] = [];
121
+
122
+ const lookupLogin = login?.toLowerCase() ?? null;
123
+ if (lookupLogin && policy.members) {
124
+ for (const [key, grant] of Object.entries(policy.members)) {
125
+ if (key.toLowerCase() === lookupLogin) grants.push(grant);
126
+ }
127
+ }
128
+
129
+ if (policy.teams && identity.teams && identity.teams.length > 0) {
130
+ const teamSet = new Set(identity.teams.map((team) => team.toLowerCase()));
131
+ for (const [key, grant] of Object.entries(policy.teams)) {
132
+ if (teamSet.has(key.toLowerCase())) grants.push(grant);
133
+ }
134
+ }
135
+
136
+ const defaultRole: PermissionRole = policy.defaultRole ?? 'viewer';
137
+ const baseRole =
138
+ grants.length === 0
139
+ ? defaultRole
140
+ : grants.reduce<PermissionRole>((acc, g) => higherRole(acc, grantRole(g)), 'viewer');
141
+
142
+ const isAdmin = isRepoAdmin || baseRole === 'admin';
143
+
144
+ return {
145
+ login,
146
+ role: isAdmin ? 'admin' : baseRole,
147
+ isAdmin,
148
+ canManagePolicy: isAdmin,
149
+ allow: mergeAllow(grants),
150
+ };
151
+ }
152
+
153
+ /**
154
+ * May this identity edit this resource? Admins edit everything; viewers edit
155
+ * nothing; editors edit only resources whose id matches the merged allow-list
156
+ * for the resource's kind.
157
+ */
158
+ export function canEdit(resolved: ResolvedPermissions, ref: ResourceRef): boolean {
159
+ if (resolved.isAdmin) return true;
160
+ if (resolved.role === 'viewer') return false;
161
+ return matchesAnyPermissionPattern(resolved.allow[RULE_KEY[ref.kind]], ref.id);
162
+ }
@@ -1,4 +1,4 @@
1
- import { test, expect, describe, beforeEach } from 'bun:test';
1
+ import { test, expect, describe } from 'bun:test';
2
2
  import { resolveComponentProps, resolvePropsFromDefinition, isRichTextMarker } from './propResolver';
3
3
  import type { StructuredComponentDefinition, ComponentNode, I18nConfig, PropDefinition } from './types';
4
4
 
@@ -146,7 +146,7 @@ export function resolveComponentProps(options: ResolvePropsOptions): Record<stri
146
146
  // Wrap rich-text props in marker objects for HTML rendering
147
147
  // Must happen AFTER i18n resolution so HTML strings get properly wrapped
148
148
  return wrapRichTextProps(result, propDefs);
149
- } catch (error) {
149
+ } catch (_error) {
150
150
  // Fallback to original behavior if validation fails (backward compatibility)
151
151
  const resolvedProps: Record<string, unknown> = {};
152
152
 
@@ -4,7 +4,6 @@
4
4
 
5
5
  import { describe, it, expect, beforeEach } from 'bun:test';
6
6
  import { ClientNodeTypeRegistry } from './ClientNodeTypeRegistry';
7
- import { SSRNodeTypeRegistry } from './SSRNodeTypeRegistry';
8
7
  import { NodeTypeManager, globalNodeTypeManager } from './NodeTypeManager';
9
8
  import { defineNodeType } from './defineNodeType';
10
9
  import { registerBuiltInNodeTypes, builtInNodeTypes } from './nodeTypes';
@@ -42,10 +41,11 @@ describe('BaseNodeTypeRegistry', () => {
42
41
  describe('registerAll', () => {
43
42
  it('should register multiple node type definitions', () => {
44
43
  registry.registerAll(builtInNodeTypes);
45
- expect(registry.size).toBe(7); // 7 node types after cms-list merged into list
44
+ expect(registry.size).toBe(10); // node, component, slot, embed, link, locale-list, list, island, markdown, custom
46
45
  expect(registry.has(NODE_TYPE.NODE)).toBe(true);
47
46
  expect(registry.has(NODE_TYPE.COMPONENT)).toBe(true);
48
47
  expect(registry.has(NODE_TYPE.EMBED)).toBe(true);
48
+ expect(registry.has(NODE_TYPE.ISLAND)).toBe(true);
49
49
  });
50
50
  });
51
51
 
@@ -189,6 +189,8 @@ describe('globalNodeTypeManager', () => {
189
189
  registerBuiltInNodeTypes();
190
190
  expect(globalNodeTypeManager.has(NODE_TYPE.NODE)).toBe(true);
191
191
  expect(globalNodeTypeManager.has(NODE_TYPE.EMBED)).toBe(true);
192
- expect(globalNodeTypeManager.getAll().length).toBe(7); // 7 node types after cms-list merged into list
192
+ expect(globalNodeTypeManager.has(NODE_TYPE.ISLAND)).toBe(true);
193
+ expect(globalNodeTypeManager.has(NODE_TYPE.MARKDOWN)).toBe(true);
194
+ expect(globalNodeTypeManager.getAll().length).toBe(10); // node, component, slot, embed, link, locale-list, list, island, markdown, custom
193
195
  });
194
196
  });
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import { BaseComponentRegistry } from './ComponentRegistry';
7
- import type { ComponentDefinition } from '../types';
8
7
 
9
8
  export class ClientRegistry extends BaseComponentRegistry {
10
9
  /**
@@ -133,8 +133,8 @@ describe('BaseComponentRegistry', () => {
133
133
 
134
134
  const all = registry.getAll();
135
135
 
136
- expect(all['Button']).toEqual(component1);
137
- expect(all['Card']).toEqual(component2);
136
+ expect(all.Button).toEqual(component1);
137
+ expect(all.Card).toEqual(component2);
138
138
  expect(Object.keys(all).length).toBe(2);
139
139
  });
140
140
 
@@ -146,7 +146,7 @@ describe('BaseComponentRegistry', () => {
146
146
  registry.register('Button', component);
147
147
 
148
148
  const all = registry.getAll();
149
- delete all['Button'];
149
+ delete all.Button;
150
150
 
151
151
  expect(registry.has('Button')).toBe(true);
152
152
  });
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ComponentDefinition } from '../types';
8
+ import { logRuntimeError } from '../errorLogger';
8
9
 
9
10
  /**
10
11
  * Base registry class with common functionality
@@ -92,7 +93,11 @@ export abstract class BaseComponentRegistry {
92
93
  for (const listener of this.listeners) {
93
94
  try {
94
95
  listener();
95
- } catch (err) {}
96
+ } catch (err) {
97
+ // One throwing subscriber must not stop the rest from being notified, but
98
+ // a swallowed listener error hides real store-update bugs — surface it.
99
+ logRuntimeError('ComponentRegistry.notify', err);
100
+ }
96
101
  }
97
102
  }
98
103
  }
@@ -6,7 +6,6 @@
6
6
  import type { ReactElement } from 'react';
7
7
  import type { ZodType } from 'zod';
8
8
  import type { ComponentNode } from '../types/nodes';
9
- import type { StyleValue } from '../types/styles';
10
9
 
11
10
  /**
12
11
  * Tree icon types for node display
@@ -21,7 +20,8 @@ export type TreeIcon =
21
20
  | 'ARRAY'
22
21
  | 'UNKNOWN'
23
22
  | 'FORM'
24
- | 'LINK';
23
+ | 'LINK'
24
+ | 'CODE';
25
25
 
26
26
  /**
27
27
  * Node category for grouping in command palette
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import { BaseComponentRegistry } from './ComponentRegistry';
7
- import type { ComponentDefinition } from '../types';
8
7
 
9
8
  export class SSRRegistry extends BaseComponentRegistry {
10
9
  /**
@@ -33,7 +33,9 @@ interface TextFieldConfig extends BaseFieldConfig {
33
33
  }
34
34
 
35
35
  interface SelectFieldConfig extends BaseFieldConfig {
36
- options: string[];
36
+ // Bare strings are value-is-label options; the object form carries a distinct
37
+ // display label (e.g. an empty-value `{ value: '', label: 'None' }` entry).
38
+ options: (string | { value: string | boolean; label: string })[];
37
39
  }
38
40
 
39
41
  // ============================================
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Custom Node Type Definition
3
+ *
4
+ * Represents a hand-authored, opaque `.astro` component (under `src/custom/`) that Meno
5
+ * treats as a BLACK BOX: it renders server-side in a real `astro build` / play preview
6
+ * (the meno-astro codec emits `<Fancy … />` + the import), but Meno doesn't model its
7
+ * internals — you can place it, pass props, and slot children, but not edit what's inside.
8
+ * The native-Astro sibling of an island (no framework renderer, no hydration).
9
+ *
10
+ * meno-core can't execute a foreign `.astro` file, so the editor canvas renders a quiet
11
+ * placeholder (its slotted children, or an inline marker) — real rendering is play/build
12
+ * only. The on-disk `.astro` form and the round-trip codec live in `meno-astro`; this file
13
+ * only models the node so it validates, persists, and renders a placeholder.
14
+ */
15
+
16
+ import { z } from 'zod';
17
+ import { createElement as h } from 'react';
18
+ import type { ComponentNode } from '../../types/nodes';
19
+ import { IfConditionSchema } from '../../validation/schemas';
20
+ import { createNodeType } from '../createNodeType';
21
+ import { textField } from '../fieldPresets';
22
+
23
+ // Schema is the SINGLE source of truth
24
+ const CustomNodeSchemaInternal = z
25
+ .object({
26
+ type: z.literal('custom'),
27
+ // Path to the hand-authored .astro component relative to `src/custom/`
28
+ // (e.g. "Fancy.astro", "widgets/Banner.astro").
29
+ src: z.string(),
30
+ props: z.record(z.string(), z.any()).optional(),
31
+ if: IfConditionSchema.optional(), // Conditional rendering - skip node when false
32
+ // Slotted children (rendered server-side into the component's default <slot/>).
33
+ children: z.union([z.array(z.any()), z.string()]).optional(),
34
+ })
35
+ .passthrough();
36
+
37
+ // TypeScript type inferred from schema
38
+ export type CustomNode = z.infer<typeof CustomNodeSchemaInternal>;
39
+
40
+ // Export schema for validation/schemas.ts
41
+ export const CustomNodeSchema = CustomNodeSchemaInternal;
42
+
43
+ /** Basename of the custom source (drops directory + `.astro` extension) for display. */
44
+ function customName(src: string): string {
45
+ const base = src.split('/').pop() ?? src;
46
+ return base.replace(/\.astro$/i, '') || base;
47
+ }
48
+
49
+ /** Slotted children, normalized to the shape `buildChildren`/`renderChildren` accept. */
50
+ function customChildren(node: CustomNode): Array<ComponentNode | string> | string | undefined {
51
+ const { children } = node;
52
+ if (Array.isArray(children)) return children.length > 0 ? (children as Array<ComponentNode | string>) : undefined;
53
+ if (typeof children === 'string') return children.length > 0 ? children : undefined;
54
+ return undefined;
55
+ }
56
+
57
+ export const CustomNodeType = createNodeType({
58
+ type: 'custom',
59
+ displayName: 'Custom Component',
60
+ category: 'special',
61
+ schema: CustomNodeSchemaInternal,
62
+
63
+ defaultValues: {
64
+ src: '',
65
+ },
66
+
67
+ treeDisplay: {
68
+ icon: 'COMPONENT',
69
+ getLabel: (node) => `Custom: ${customName(node.src) || '?'}`,
70
+ },
71
+
72
+ editableFields: [textField({ name: 'src', label: 'Custom .astro file', required: true, placeholder: 'Fancy.astro' })],
73
+
74
+ // meno-core can't run a foreign `.astro` file. Render the slotted children as normal
75
+ // content and fall back to a quiet inline marker when there are none. The wrapper keeps
76
+ // `data-custom` for selection/identification only. Real rendering is play/build only.
77
+ clientRenderer: (node, context) => {
78
+ const children = customChildren(node);
79
+ const inner = children
80
+ ? context.buildChildren(children, context.elementPath)
81
+ : h('span', { style: { color: '#9ca3af', fontSize: '13px' } }, customName(node.src) || '?');
82
+ return h('div', { key: context.key, 'data-custom': node.src, style: { display: 'contents' } }, inner);
83
+ },
84
+
85
+ ssrRenderer: (node, context) => {
86
+ const children = customChildren(node);
87
+ const inner = children
88
+ ? context.renderChildren(children, context)
89
+ : `<span style="color:#9ca3af;font-size:13px">${escapeHtml(customName(node.src) || '?')}</span>`;
90
+ return `<div data-custom="${escapeHtml(node.src)}" style="display:contents">${inner}</div>`;
91
+ },
92
+
93
+ capabilities: {
94
+ canHaveChildren: true,
95
+ canHaveStyle: false,
96
+ canHaveAttributes: false,
97
+ requiresProps: ['src'],
98
+ },
99
+ });
100
+
101
+ function escapeHtml(value: string): string {
102
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
103
+ }
@@ -51,7 +51,7 @@ export const HtmlNodeType = createNodeType({
51
51
  },
52
52
 
53
53
  clientRenderer: (node, context) => {
54
- const props: Record<string, any> = { key: context.key };
54
+ const props: Record<string, unknown> = { key: context.key };
55
55
 
56
56
  // Prevent form submission in editor
57
57
  if (node.tag === 'form') {
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Island Node Type Definition
3
+ *
4
+ * Represents a BYO framework component (React/Preact/Vue/Svelte) rendered as an
5
+ * Astro island. The actual hydration only happens in a real `astro build` / play
6
+ * preview (the meno-astro codec emits `<Counter client:visible … />`). meno-core
7
+ * itself can't run a framework component, so the editor canvas renders a visible
8
+ * placeholder — design-time positioning works, interactivity is play/build only.
9
+ *
10
+ * The on-disk `.astro` form and the round-trip codec live in `meno-astro`; this
11
+ * file only models the node so it validates, persists, and renders a placeholder.
12
+ */
13
+
14
+ import { z } from 'zod';
15
+ import { createElement as h } from 'react';
16
+ import type { ComponentNode } from '../../types/nodes';
17
+ import { IfConditionSchema } from '../../validation/schemas';
18
+ import { createNodeType } from '../createNodeType';
19
+ import { textField, selectField } from '../fieldPresets';
20
+
21
+ /**
22
+ * Client (hydration) directive for an island.
23
+ * `directive` maps to Astro's `client:*` directive; `value` carries the media
24
+ * query (`client:media="(max-width: 50em)"`) or the framework name
25
+ * (`client:only="react"`). When omitted entirely, the island is server-rendered
26
+ * with no JS (a valid, zero-hydration Astro island).
27
+ */
28
+ export const IslandClientDirectiveSchema = z
29
+ .object({
30
+ directive: z.enum(['load', 'idle', 'visible', 'media', 'only']),
31
+ value: z.string().optional(),
32
+ })
33
+ .passthrough();
34
+
35
+ // Schema is the SINGLE source of truth
36
+ const IslandNodeSchemaInternal = z
37
+ .object({
38
+ type: z.literal('island'),
39
+ // Path to the framework component file relative to `src/islands/`
40
+ // (e.g. "Counter.tsx", "widgets/Chart.vue"). The extension derives the
41
+ // framework, which drives the Astro integration that gets provisioned.
42
+ src: z.string(),
43
+ client: IslandClientDirectiveSchema.optional(),
44
+ props: z.record(z.string(), z.any()).optional(),
45
+ if: IfConditionSchema.optional(), // Conditional rendering - skip node when false
46
+ // Slotted children (rendered server-side into the island's <slot/>).
47
+ children: z.union([z.array(z.any()), z.string()]).optional(),
48
+ })
49
+ .passthrough();
50
+
51
+ // TypeScript type inferred from schema
52
+ export type IslandNode = z.infer<typeof IslandNodeSchemaInternal>;
53
+
54
+ // Export schema for validation/schemas.ts
55
+ export const IslandNodeSchema = IslandNodeSchemaInternal;
56
+
57
+ /** Basename of the island source (drops directory + extension) for display. */
58
+ function islandName(src: string): string {
59
+ const base = src.split('/').pop() ?? src;
60
+ return base.replace(/\.(tsx|jsx|vue|svelte)$/i, '') || base;
61
+ }
62
+
63
+ /** Slotted children, normalized to the shape `buildChildren`/`renderChildren` accept. */
64
+ function islandChildren(node: IslandNode): Array<ComponentNode | string> | string | undefined {
65
+ const { children } = node;
66
+ if (Array.isArray(children)) return children.length > 0 ? (children as Array<ComponentNode | string>) : undefined;
67
+ if (typeof children === 'string') return children.length > 0 ? children : undefined;
68
+ return undefined;
69
+ }
70
+
71
+ export const IslandNodeType = createNodeType({
72
+ type: 'island',
73
+ displayName: 'Island',
74
+ category: 'special',
75
+ schema: IslandNodeSchemaInternal,
76
+
77
+ defaultValues: {
78
+ src: '',
79
+ client: { directive: 'load' },
80
+ },
81
+
82
+ treeDisplay: {
83
+ icon: 'COMPONENT',
84
+ getLabel: (node) => `Island: ${islandName(node.src) || '?'}`,
85
+ },
86
+
87
+ editableFields: [
88
+ textField({ name: 'src', label: 'Island file', required: true, placeholder: 'Counter.tsx' }),
89
+ selectField({
90
+ name: 'client.directive',
91
+ label: 'Hydration',
92
+ // The empty-value entry clears `client` entirely → a server-rendered island
93
+ // with zero JS (a valid Astro pattern); the rest map to `client:<directive>`.
94
+ options: [{ value: '', label: 'None (server-only, no JS)' }, 'load', 'idle', 'visible', 'media', 'only'],
95
+ }),
96
+ ],
97
+
98
+ // meno-core can't run a framework component. Instead of a loud highlight box
99
+ // (which made island-heavy pages unreadable), render the island's slotted
100
+ // children as normal content and fall back to a quiet inline marker when there
101
+ // are none. The wrapper keeps `data-island` for selection/identification only.
102
+ // Real hydration is play/build only.
103
+ clientRenderer: (node, context) => {
104
+ const children = islandChildren(node);
105
+ const inner = children
106
+ ? context.buildChildren(children, context.elementPath)
107
+ : h('span', { style: { color: '#9ca3af', fontSize: '13px' } }, islandName(node.src) || '?');
108
+ return h('div', { key: context.key, 'data-island': node.src, style: { display: 'contents' } }, inner);
109
+ },
110
+
111
+ ssrRenderer: (node, context) => {
112
+ const children = islandChildren(node);
113
+ const inner = children
114
+ ? context.renderChildren(children, context)
115
+ : `<span style="color:#9ca3af;font-size:13px">${escapeHtml(islandName(node.src) || '?')}</span>`;
116
+ return `<div data-island="${escapeHtml(node.src)}" style="display:contents">${inner}</div>`;
117
+ },
118
+
119
+ capabilities: {
120
+ canHaveChildren: true,
121
+ canHaveStyle: false,
122
+ canHaveAttributes: false,
123
+ requiresProps: ['src'],
124
+ },
125
+ });
126
+
127
+ function escapeHtml(value: string): string {
128
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
129
+ }
@@ -41,14 +41,38 @@ const ListNodeSchemaInternal = z
41
41
  * Data source type:
42
42
  * - 'prop': Read items from component props (default)
43
43
  * - 'collection': Query items from CMS collection
44
+ * - 'remote': Fetch items from a public HTTP/JSON endpoint at build/SSR time
45
+ * - 'sanity': Fetch a Sanity document type via GROQ at build/SSR time (read-only, public dataset)
46
+ * - 'expression': Iterate a verbatim JS expression (`source`) that yields an array — a
47
+ * `.map()` over arbitrary frontmatter data (e.g. an Astro Action / supabase query) the
48
+ * dialect can't model as a prop/collection. The item template (`children` + `itemAs`) IS
49
+ * editable; the source itself is a read-only black box (edit it in the .astro frontmatter).
50
+ * The editor has no data for it, so it renders empty in the design canvas (real Astro play
51
+ * renders it for real at build/SSR).
44
52
  */
45
- sourceType: z.enum(['prop', 'collection']).default('prop'),
53
+ sourceType: z.enum(['prop', 'collection', 'remote', 'sanity', 'expression']).default('prop'),
46
54
  /**
47
- * Source identifier:
55
+ * Source identifier (sourceType 'prop'/'collection'/'expression'; 'remote' uses `url`):
48
56
  * - For sourceType 'prop': Prop name (e.g., "items") or template expression (e.g., "{{category.items}}")
49
57
  * - For sourceType 'collection': Collection name (e.g., "posts", "authors")
58
+ * - For sourceType 'expression': the verbatim JS expression iterated by `.map()` (e.g. "notes?")
50
59
  */
51
- source: z.string(),
60
+ source: z.string().optional(),
61
+
62
+ // Remote-only options (sourceType: 'remote')
63
+ /** HTTP(S) endpoint returning JSON, fetched at build/SSR time (e.g. a public REST API). */
64
+ url: z.string().optional(),
65
+ /**
66
+ * Dot-path into the JSON response to reach the items array (e.g. "data.items").
67
+ * Empty/absent = the response body is itself the array.
68
+ */
69
+ path: z.string().optional(),
70
+
71
+ // Sanity-only options (sourceType: 'sanity')
72
+ /** Sanity document `_type` to fetch (`*[_type == "<documentType>"]`). projectId/dataset come from project config. */
73
+ documentType: z.string().optional(),
74
+ /** Field used as the URL slug (for synthesizing item links); defaults to 'slug'. */
75
+ slugField: z.string().optional(),
52
76
  label: z.string().optional(), // Custom label displayed in structure tree
53
77
  if: IfConditionSchema.optional(), // Conditional rendering - skip node when false
54
78
  /**
@@ -111,8 +135,19 @@ export const ListNodeType = createNodeType({
111
135
  getLabel: (node) => {
112
136
  const listNode = node as ListNode;
113
137
  const sourceType = listNode.sourceType || 'prop';
114
- const prefix = sourceType === 'collection' ? 'CMS List' : 'List';
115
- return listNode.source ? `${prefix}: ${listNode.source}` : prefix;
138
+ const prefix =
139
+ sourceType === 'collection'
140
+ ? 'CMS List'
141
+ : sourceType === 'remote'
142
+ ? 'Remote List'
143
+ : sourceType === 'sanity'
144
+ ? 'Sanity List'
145
+ : sourceType === 'expression'
146
+ ? 'Loop'
147
+ : 'List';
148
+ const src =
149
+ sourceType === 'remote' ? listNode.url : sourceType === 'sanity' ? listNode.documentType : listNode.source;
150
+ return src ? `${prefix}: ${src}` : prefix;
116
151
  },
117
152
  },
118
153
 
@@ -132,7 +167,8 @@ export const ListNodeType = createNodeType({
132
167
  key: context.key,
133
168
  'data-list': 'true',
134
169
  'data-source-type': sourceType,
135
- 'data-source': listNode.source,
170
+ 'data-source':
171
+ sourceType === 'remote' ? listNode.url : sourceType === 'sanity' ? listNode.documentType : listNode.source,
136
172
  style: {
137
173
  padding: '8px 12px',
138
174
  background: bgColor,
@@ -162,7 +198,7 @@ export const ListNodeType = createNodeType({
162
198
  label: 'Source Type',
163
199
  type: 'select',
164
200
  required: false,
165
- options: ['prop', 'collection'],
201
+ options: ['prop', 'collection', 'remote', 'sanity'],
166
202
  },
167
203
  {
168
204
  name: 'source',