@elytracms/studio 0.0.93 → 0.0.95

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 (541) hide show
  1. package/convex/analytics-cost.test.ts +817 -0
  2. package/convex/asset-alt-migration.test.ts +2 -2
  3. package/convex/asset-meta-patch.test.ts +218 -0
  4. package/convex/asset-replace-delete-race.test.ts +127 -0
  5. package/convex/assets.ts +27 -2
  6. package/convex/auth.ts +5 -2
  7. package/convex/authority.ts +35 -11
  8. package/convex/contract-parity.test.ts +7 -0
  9. package/convex/delivery-redirects.test.ts +167 -0
  10. package/convex/delivery.ts +23 -10
  11. package/convex/guard.ts +9 -7
  12. package/convex/http-routes.test.ts +18 -8
  13. package/convex/http.ts +23 -17
  14. package/convex/ingest.test.ts +326 -0
  15. package/convex/ingest.ts +281 -0
  16. package/convex/invites.ts +13 -5
  17. package/convex/members.ts +10 -4
  18. package/convex/rail-write-parity.test.ts +6 -6
  19. package/convex/record-functions.ts +325 -0
  20. package/convex/records.test.ts +156 -0
  21. package/convex/redirect-migration.test.ts +205 -0
  22. package/convex/redirect-revalidate.test.ts +5 -1
  23. package/convex/redirects.ts +120 -79
  24. package/convex/role-policy.test.ts +315 -0
  25. package/convex/roles.ts +83 -0
  26. package/convex/schema.ts +36 -0
  27. package/convex/triggers.ts +21 -1
  28. package/dist/addon.d.ts +71 -0
  29. package/dist/addon.js +67 -0
  30. package/dist/addon.js.map +1 -0
  31. package/dist/components/account/account-settings.js +2 -2
  32. package/dist/components/account/account-settings.js.map +1 -1
  33. package/dist/components/account/credential-panel.js +2 -2
  34. package/dist/components/account/credential-panel.js.map +1 -1
  35. package/dist/components/account/self-account-schema.d.ts +6 -15
  36. package/dist/components/account/self-account-schema.js +52 -34
  37. package/dist/components/account/self-account-schema.js.map +1 -1
  38. package/dist/components/account/self-account-view-source.d.ts +1 -1
  39. package/dist/components/account/self-account-view-source.js +6 -4
  40. package/dist/components/account/self-account-view-source.js.map +1 -1
  41. package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
  42. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  43. package/dist/components/ai/ai-chat-panel.js +6 -6
  44. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  45. package/dist/components/ai/ai-cms-panel.js +2 -2
  46. package/dist/components/ai/ai-cms-panel.js.map +1 -1
  47. package/dist/components/ai/ai-panel-toggle.js +4 -4
  48. package/dist/components/ai/ai-panel-toggle.js.map +1 -1
  49. package/dist/components/ai/chat-ui.js +2 -2
  50. package/dist/components/ai/chat-ui.js.map +1 -1
  51. package/dist/components/ai/proposal-card.d.ts +2 -2
  52. package/dist/components/ai/proposal-card.js +2 -2
  53. package/dist/components/ai/proposal-card.js.map +1 -1
  54. package/dist/components/ai/proposal-issues.js +2 -2
  55. package/dist/components/ai/proposal-issues.js.map +1 -1
  56. package/dist/components/ai/translate-document-action.js +2 -2
  57. package/dist/components/ai/translate-document-action.js.map +1 -1
  58. package/dist/components/ai/translate-document-dialog.js +6 -6
  59. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  60. package/dist/components/ai/translate-skipped.d.ts +1 -1
  61. package/dist/components/ai/translate-skipped.js +2 -2
  62. package/dist/components/ai/translate-skipped.js.map +1 -1
  63. package/dist/components/app-views/default-modules.d.ts +50 -2
  64. package/dist/components/app-views/default-modules.js +65 -20
  65. package/dist/components/app-views/default-modules.js.map +1 -1
  66. package/dist/components/app-views/module.d.ts +53 -3
  67. package/dist/components/app-views/module.js +14 -7
  68. package/dist/components/app-views/module.js.map +1 -1
  69. package/dist/components/app-views/sidebar.js +1 -1
  70. package/dist/components/app-views/sidebar.js.map +1 -1
  71. package/dist/components/app-views/studio-config.d.ts +11 -2
  72. package/dist/components/app-views/studio-config.js.map +1 -1
  73. package/dist/components/assets/asset-detail.js +28 -13
  74. package/dist/components/assets/asset-detail.js.map +1 -1
  75. package/dist/components/assets/asset-grid.js +2 -2
  76. package/dist/components/assets/asset-grid.js.map +1 -1
  77. package/dist/components/assets/asset-locale.d.ts +9 -0
  78. package/dist/components/assets/asset-locale.js +16 -0
  79. package/dist/components/assets/asset-locale.js.map +1 -0
  80. package/dist/components/assets/asset-meta-label.d.ts +12 -0
  81. package/dist/components/assets/asset-meta-label.js +15 -0
  82. package/dist/components/assets/asset-meta-label.js.map +1 -0
  83. package/dist/components/assets/asset-metadata.d.ts +69 -15
  84. package/dist/components/assets/asset-metadata.js +99 -11
  85. package/dist/components/assets/asset-metadata.js.map +1 -1
  86. package/dist/components/assets/asset-search.js +8 -2
  87. package/dist/components/assets/asset-search.js.map +1 -1
  88. package/dist/components/assets/asset-thumb.js +9 -5
  89. package/dist/components/assets/asset-thumb.js.map +1 -1
  90. package/dist/components/assets/assets-collection-view-source.d.ts +25 -11
  91. package/dist/components/assets/assets-collection-view-source.js +78 -26
  92. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  93. package/dist/components/assets/assets-module.js +1 -1
  94. package/dist/components/assets/assets-module.js.map +1 -1
  95. package/dist/components/assets/assets-store.d.ts +2 -2
  96. package/dist/components/assets/assets-store.js +22 -9
  97. package/dist/components/assets/assets-store.js.map +1 -1
  98. package/dist/components/assets/assets-view-preference.js +1 -1
  99. package/dist/components/assets/assets-view-preference.js.map +1 -1
  100. package/dist/components/assets/replace-file.js +1 -1
  101. package/dist/components/assets/replace-file.js.map +1 -1
  102. package/dist/components/auth-context.d.ts +1 -1
  103. package/dist/components/auth-context.js +6 -6
  104. package/dist/components/auth-context.js.map +1 -1
  105. package/dist/components/builder/blocks-model.d.ts +1 -1
  106. package/dist/components/builder/blocks-model.js +3 -3
  107. package/dist/components/builder/blocks-model.js.map +1 -1
  108. package/dist/components/builder/blocks-panel.d.ts +1 -1
  109. package/dist/components/builder/blocks-panel.js +4 -4
  110. package/dist/components/builder/blocks-panel.js.map +1 -1
  111. package/dist/components/builder/builder-store.d.ts +21 -0
  112. package/dist/components/builder/builder-store.js +24 -2
  113. package/dist/components/builder/builder-store.js.map +1 -1
  114. package/dist/components/builder/canvas-breakpoints.js +1 -1
  115. package/dist/components/builder/canvas-breakpoints.js.map +1 -1
  116. package/dist/components/builder/canvas-commit-boundary.d.ts +22 -0
  117. package/dist/components/builder/canvas-commit-boundary.js +27 -0
  118. package/dist/components/builder/canvas-commit-boundary.js.map +1 -0
  119. package/dist/components/builder/canvas-dnd.d.ts +2 -2
  120. package/dist/components/builder/canvas-dnd.js +1 -1
  121. package/dist/components/builder/canvas-dnd.js.map +1 -1
  122. package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
  123. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  124. package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
  125. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
  126. package/dist/components/builder/canvas-rebase.d.ts +77 -0
  127. package/dist/components/builder/canvas-rebase.js +169 -0
  128. package/dist/components/builder/canvas-rebase.js.map +1 -0
  129. package/dist/components/builder/canvas-render-context.d.ts +7 -4
  130. package/dist/components/builder/canvas-render-context.js +21 -13
  131. package/dist/components/builder/canvas-render-context.js.map +1 -1
  132. package/dist/components/builder/canvas-theme-notice.js +2 -2
  133. package/dist/components/builder/canvas-theme-notice.js.map +1 -1
  134. package/dist/components/builder/canvas.js +3 -3
  135. package/dist/components/builder/canvas.js.map +1 -1
  136. package/dist/components/builder/clipboard-actions.d.ts +2 -2
  137. package/dist/components/builder/clipboard-actions.js +4 -4
  138. package/dist/components/builder/clipboard-actions.js.map +1 -1
  139. package/dist/components/builder/composition-field-editor-lazy.js +1 -1
  140. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
  141. package/dist/components/builder/composition-field-editor.d.ts +21 -3
  142. package/dist/components/builder/composition-field-editor.js +48 -30
  143. package/dist/components/builder/composition-field-editor.js.map +1 -1
  144. package/dist/components/builder/drag-session.d.ts +1 -1
  145. package/dist/components/builder/drag-session.js.map +1 -1
  146. package/dist/components/builder/fixture-thumbnail.js +1 -1
  147. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  148. package/dist/components/builder/insert-context.d.ts +1 -1
  149. package/dist/components/builder/insert-context.js +3 -3
  150. package/dist/components/builder/insert-context.js.map +1 -1
  151. package/dist/components/builder/layout-inspector.js +1 -1
  152. package/dist/components/builder/layout-inspector.js.map +1 -1
  153. package/dist/components/builder/node-factory.js +15 -3
  154. package/dist/components/builder/node-factory.js.map +1 -1
  155. package/dist/components/builder/node-title.js +1 -1
  156. package/dist/components/builder/node-title.js.map +1 -1
  157. package/dist/components/builder/page-tree.d.ts +1 -1
  158. package/dist/components/builder/page-tree.js +5 -5
  159. package/dist/components/builder/page-tree.js.map +1 -1
  160. package/dist/components/builder/placement-refusal.js +3 -3
  161. package/dist/components/builder/placement-refusal.js.map +1 -1
  162. package/dist/components/builder/property-inspector.js +7 -5
  163. package/dist/components/builder/property-inspector.js.map +1 -1
  164. package/dist/components/builder/reuse-context.js +4 -4
  165. package/dist/components/builder/reuse-context.js.map +1 -1
  166. package/dist/components/builder/reuse.js +1 -1
  167. package/dist/components/builder/reuse.js.map +1 -1
  168. package/dist/components/builder/rich-text-toolbar.js +1 -1
  169. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  170. package/dist/components/builder/run-inspector.js +11 -7
  171. package/dist/components/builder/run-inspector.js.map +1 -1
  172. package/dist/components/builder/selection-overlay.js +3 -3
  173. package/dist/components/builder/selection-overlay.js.map +1 -1
  174. package/dist/components/builder/spacing-handles.d.ts +2 -2
  175. package/dist/components/builder/spacing-handles.js +3 -3
  176. package/dist/components/builder/spacing-handles.js.map +1 -1
  177. package/dist/components/cms/asset-field.d.ts +5 -2
  178. package/dist/components/cms/asset-field.js +36 -19
  179. package/dist/components/cms/asset-field.js.map +1 -1
  180. package/dist/components/cms/asset-picker.js +5 -3
  181. package/dist/components/cms/asset-picker.js.map +1 -1
  182. package/dist/components/cms/bulk-actions.d.ts +1 -1
  183. package/dist/components/cms/bulk-actions.js +2 -2
  184. package/dist/components/cms/bulk-actions.js.map +1 -1
  185. package/dist/components/cms/bulk-bar.d.ts +2 -2
  186. package/dist/components/cms/bulk-bar.js +2 -2
  187. package/dist/components/cms/bulk-bar.js.map +1 -1
  188. package/dist/components/cms/canvas-commit.d.ts +56 -0
  189. package/dist/components/cms/canvas-commit.js +35 -0
  190. package/dist/components/cms/canvas-commit.js.map +1 -0
  191. package/dist/components/cms/cell-edit.d.ts +1 -1
  192. package/dist/components/cms/cell-edit.js +1 -1
  193. package/dist/components/cms/cell-edit.js.map +1 -1
  194. package/dist/components/cms/cell.d.ts +1 -1
  195. package/dist/components/cms/cell.js +3 -3
  196. package/dist/components/cms/cell.js.map +1 -1
  197. package/dist/components/cms/cms-index.js +2 -2
  198. package/dist/components/cms/cms-index.js.map +1 -1
  199. package/dist/components/cms/cms-labels.js +2 -2
  200. package/dist/components/cms/cms-labels.js.map +1 -1
  201. package/dist/components/cms/cms-store.d.ts +2 -2
  202. package/dist/components/cms/cms-store.js +10 -10
  203. package/dist/components/cms/cms-store.js.map +1 -1
  204. package/dist/components/cms/collection-table.js +30 -14
  205. package/dist/components/cms/collection-table.js.map +1 -1
  206. package/dist/components/cms/commit-policy.d.ts +6 -2
  207. package/dist/components/cms/commit-policy.js +1 -1
  208. package/dist/components/cms/commit-policy.js.map +1 -1
  209. package/dist/components/cms/detail-editor.d.ts +5 -2
  210. package/dist/components/cms/detail-editor.js +31 -15
  211. package/dist/components/cms/detail-editor.js.map +1 -1
  212. package/dist/components/cms/details-pane.js +1 -1
  213. package/dist/components/cms/details-pane.js.map +1 -1
  214. package/dist/components/cms/doc-state.js +1 -1
  215. package/dist/components/cms/doc-state.js.map +1 -1
  216. package/dist/components/cms/document-diff-view.js +5 -5
  217. package/dist/components/cms/document-diff-view.js.map +1 -1
  218. package/dist/components/cms/document-editor-context.js +1 -1
  219. package/dist/components/cms/document-editor-context.js.map +1 -1
  220. package/dist/components/cms/document-staging.js +1 -1
  221. package/dist/components/cms/document-staging.js.map +1 -1
  222. package/dist/components/cms/document-url-path.js +2 -2
  223. package/dist/components/cms/document-url-path.js.map +1 -1
  224. package/dist/components/cms/duplicate-document.d.ts +1 -1
  225. package/dist/components/cms/duplicate-document.js.map +1 -1
  226. package/dist/components/cms/editable-cell.js +3 -3
  227. package/dist/components/cms/editable-cell.js.map +1 -1
  228. package/dist/components/cms/field-collaboration.d.ts +1 -1
  229. package/dist/components/cms/field-collaboration.js +1 -1
  230. package/dist/components/cms/field-collaboration.js.map +1 -1
  231. package/dist/components/cms/field-connection.d.ts +1 -1
  232. package/dist/components/cms/field-connection.js +2 -2
  233. package/dist/components/cms/field-connection.js.map +1 -1
  234. package/dist/components/cms/field-control-lib.js +1 -1
  235. package/dist/components/cms/field-control-lib.js.map +1 -1
  236. package/dist/components/cms/field-control.js +4 -4
  237. package/dist/components/cms/field-control.js.map +1 -1
  238. package/dist/components/cms/field-renderer.d.ts +1 -1
  239. package/dist/components/cms/field-renderer.js +2 -2
  240. package/dist/components/cms/field-renderer.js.map +1 -1
  241. package/dist/components/cms/filter-builder.d.ts +1 -1
  242. package/dist/components/cms/filter-builder.js +3 -3
  243. package/dist/components/cms/filter-builder.js.map +1 -1
  244. package/dist/components/cms/gate-validation.js +1 -1
  245. package/dist/components/cms/gate-validation.js.map +1 -1
  246. package/dist/components/cms/import-export.js +2 -2
  247. package/dist/components/cms/import-export.js.map +1 -1
  248. package/dist/components/cms/issue-copy.js +1 -1
  249. package/dist/components/cms/issue-copy.js.map +1 -1
  250. package/dist/components/cms/issues-pane.js +3 -3
  251. package/dist/components/cms/issues-pane.js.map +1 -1
  252. package/dist/components/cms/object-array-field.js +2 -2
  253. package/dist/components/cms/object-array-field.js.map +1 -1
  254. package/dist/components/cms/prop-fields.js +2 -2
  255. package/dist/components/cms/prop-fields.js.map +1 -1
  256. package/dist/components/cms/reference-confirm.d.ts +1 -1
  257. package/dist/components/cms/reference-confirm.js +2 -2
  258. package/dist/components/cms/reference-confirm.js.map +1 -1
  259. package/dist/components/cms/relation-picker.js +2 -2
  260. package/dist/components/cms/relation-picker.js.map +1 -1
  261. package/dist/components/cms/rich-text/block-style.js +1 -1
  262. package/dist/components/cms/rich-text/block-style.js.map +1 -1
  263. package/dist/components/cms/rich-text/component-embed.js +4 -4
  264. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  265. package/dist/components/cms/rich-text/editor.js +3 -3
  266. package/dist/components/cms/rich-text/editor.js.map +1 -1
  267. package/dist/components/cms/rich-text/embed-picker.js +3 -3
  268. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  269. package/dist/components/cms/rich-text/embed-props.js +3 -3
  270. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  271. package/dist/components/cms/rich-text/extensions.js +1 -1
  272. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  273. package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
  274. package/dist/components/cms/rich-text/font-size-control.js +2 -2
  275. package/dist/components/cms/rich-text/font-size-control.js.map +1 -1
  276. package/dist/components/cms/rich-text/lazy.js +1 -1
  277. package/dist/components/cms/rich-text/lazy.js.map +1 -1
  278. package/dist/components/cms/rich-text/link-control.js +2 -2
  279. package/dist/components/cms/rich-text/link-control.js.map +1 -1
  280. package/dist/components/cms/rich-text/slash-command.js +3 -3
  281. package/dist/components/cms/rich-text/slash-command.js.map +1 -1
  282. package/dist/components/cms/rich-text/toolbar.js +1 -1
  283. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  284. package/dist/components/cms/rich-text/unknown-node.js +2 -2
  285. package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
  286. package/dist/components/cms/rich-text-preview.js +2 -2
  287. package/dist/components/cms/rich-text-preview.js.map +1 -1
  288. package/dist/components/cms/row-actions.d.ts +1 -1
  289. package/dist/components/cms/row-actions.js.map +1 -1
  290. package/dist/components/cms/scalar-value-editor.d.ts +1 -1
  291. package/dist/components/cms/scalar-value-editor.js +2 -2
  292. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  293. package/dist/components/cms/use-discard-controller.d.ts +1 -1
  294. package/dist/components/cms/use-discard-controller.js +1 -1
  295. package/dist/components/cms/use-discard-controller.js.map +1 -1
  296. package/dist/components/cms/use-document-draft.d.ts +69 -5
  297. package/dist/components/cms/use-document-draft.js +118 -34
  298. package/dist/components/cms/use-document-draft.js.map +1 -1
  299. package/dist/components/cms/use-document-versions.js +1 -1
  300. package/dist/components/cms/use-document-versions.js.map +1 -1
  301. package/dist/components/cms/use-field-focus.d.ts +1 -1
  302. package/dist/components/cms/use-field-focus.js +2 -2
  303. package/dist/components/cms/use-field-focus.js.map +1 -1
  304. package/dist/components/cms/use-live-url.d.ts +7 -2
  305. package/dist/components/cms/use-live-url.js +15 -12
  306. package/dist/components/cms/use-live-url.js.map +1 -1
  307. package/dist/components/cms/use-publish-controller.d.ts +1 -1
  308. package/dist/components/cms/use-publish-controller.js +1 -1
  309. package/dist/components/cms/use-publish-controller.js.map +1 -1
  310. package/dist/components/cms/use-published-perspective.d.ts +1 -1
  311. package/dist/components/cms/use-published-perspective.js.map +1 -1
  312. package/dist/components/cms/use-restore-version.d.ts +1 -1
  313. package/dist/components/cms/use-restore-version.js +1 -1
  314. package/dist/components/cms/use-restore-version.js.map +1 -1
  315. package/dist/components/cms/version-compare-lazy.js +1 -1
  316. package/dist/components/cms/version-compare-lazy.js.map +1 -1
  317. package/dist/components/cms/version-compare.js +1 -1
  318. package/dist/components/cms/version-compare.js.map +1 -1
  319. package/dist/components/cms/versions-pane.js +3 -3
  320. package/dist/components/cms/versions-pane.js.map +1 -1
  321. package/dist/components/cms/view-bar.js +3 -3
  322. package/dist/components/cms/view-bar.js.map +1 -1
  323. package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
  324. package/dist/components/collection-view/cms-collection-view-source.js +11 -7
  325. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  326. package/dist/components/collection-view/collection-view-context.d.ts +1 -1
  327. package/dist/components/collection-view/collection-view-context.js +1 -1
  328. package/dist/components/collection-view/collection-view-context.js.map +1 -1
  329. package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
  330. package/dist/components/collection-view/collection-view-surface.js +18 -0
  331. package/dist/components/collection-view/collection-view-surface.js.map +1 -0
  332. package/dist/components/components/component-detail.js +10 -10
  333. package/dist/components/components/component-detail.js.map +1 -1
  334. package/dist/components/components/component-preview.d.ts +2 -2
  335. package/dist/components/components/component-preview.js +5 -5
  336. package/dist/components/components/component-preview.js.map +1 -1
  337. package/dist/components/components/component-status.d.ts +1 -1
  338. package/dist/components/components/component-status.js +1 -1
  339. package/dist/components/components/component-status.js.map +1 -1
  340. package/dist/components/components/components-index.js +2 -2
  341. package/dist/components/components/components-index.js.map +1 -1
  342. package/dist/components/components/field-inspector.js +1 -1
  343. package/dist/components/components/field-inspector.js.map +1 -1
  344. package/dist/components/components/registry-browser.js +3 -3
  345. package/dist/components/components/registry-browser.js.map +1 -1
  346. package/dist/components/components/taxonomy.d.ts +1 -1
  347. package/dist/components/components/taxonomy.js.map +1 -1
  348. package/dist/components/components/transfer-dialog.js +3 -3
  349. package/dist/components/components/transfer-dialog.js.map +1 -1
  350. package/dist/components/current-user-menu.js +1 -1
  351. package/dist/components/current-user-menu.js.map +1 -1
  352. package/dist/components/delivery-health-banner.js +2 -2
  353. package/dist/components/delivery-health-banner.js.map +1 -1
  354. package/dist/components/deployment-setup-gate.js +13 -4
  355. package/dist/components/deployment-setup-gate.js.map +1 -1
  356. package/dist/components/dev-login-badge.js +2 -2
  357. package/dist/components/dev-login-badge.js.map +1 -1
  358. package/dist/components/guards.js +1 -1
  359. package/dist/components/guards.js.map +1 -1
  360. package/dist/components/inspector/document-panes.d.ts +54 -0
  361. package/dist/components/inspector/document-panes.js +41 -0
  362. package/dist/components/inspector/document-panes.js.map +1 -0
  363. package/dist/components/inspector/inspector-sidebar.js +1 -1
  364. package/dist/components/inspector/inspector-sidebar.js.map +1 -1
  365. package/dist/components/nav-issues-registry.d.ts +53 -0
  366. package/dist/components/nav-issues-registry.js +69 -0
  367. package/dist/components/nav-issues-registry.js.map +1 -0
  368. package/dist/components/nav-issues.js +4 -4
  369. package/dist/components/nav-issues.js.map +1 -1
  370. package/dist/components/operation-error.d.ts +1 -1
  371. package/dist/components/operation-error.js +4 -4
  372. package/dist/components/operation-error.js.map +1 -1
  373. package/dist/components/playground-badge.js +2 -2
  374. package/dist/components/playground-badge.js.map +1 -1
  375. package/dist/components/project-list.js +3 -3
  376. package/dist/components/project-list.js.map +1 -1
  377. package/dist/components/project-session.d.ts +2 -2
  378. package/dist/components/project-session.js +5 -5
  379. package/dist/components/project-session.js.map +1 -1
  380. package/dist/components/project-switcher.js +2 -2
  381. package/dist/components/project-switcher.js.map +1 -1
  382. package/dist/components/rail-store-status.js +1 -1
  383. package/dist/components/rail-store-status.js.map +1 -1
  384. package/dist/components/rail-view-source.d.ts +6 -5
  385. package/dist/components/rail-view-source.js +4 -3
  386. package/dist/components/rail-view-source.js.map +1 -1
  387. package/dist/components/records/record-lens.d.ts +51 -0
  388. package/dist/components/records/record-lens.js +151 -0
  389. package/dist/components/records/record-lens.js.map +1 -0
  390. package/dist/components/records/record-rail.d.ts +86 -0
  391. package/dist/components/records/record-rail.js +241 -0
  392. package/dist/components/records/record-rail.js.map +1 -0
  393. package/dist/components/redirects/redirects-schema.js +1 -1
  394. package/dist/components/redirects/redirects-schema.js.map +1 -1
  395. package/dist/components/references/references-panel.js +2 -2
  396. package/dist/components/references/references-panel.js.map +1 -1
  397. package/dist/components/section-nav.js +27 -11
  398. package/dist/components/section-nav.js.map +1 -1
  399. package/dist/components/section-state.js +2 -2
  400. package/dist/components/section-state.js.map +1 -1
  401. package/dist/components/section-surface.js +2 -2
  402. package/dist/components/section-surface.js.map +1 -1
  403. package/dist/components/session-expired-overlay.js +3 -3
  404. package/dist/components/session-expired-overlay.js.map +1 -1
  405. package/dist/components/settings-form.js +1 -1
  406. package/dist/components/settings-form.js.map +1 -1
  407. package/dist/components/sign-in-needed-badge.js +1 -1
  408. package/dist/components/sign-in-needed-badge.js.map +1 -1
  409. package/dist/components/sign-in-view.d.ts +2 -2
  410. package/dist/components/sign-in-view.js +5 -5
  411. package/dist/components/sign-in-view.js.map +1 -1
  412. package/dist/components/studio-context.js +3 -3
  413. package/dist/components/studio-context.js.map +1 -1
  414. package/dist/components/studio-project-screen.js +30 -3
  415. package/dist/components/studio-project-screen.js.map +1 -1
  416. package/dist/components/studio-shell.js +6 -5
  417. package/dist/components/studio-shell.js.map +1 -1
  418. package/dist/components/theme-context.d.ts +1 -1
  419. package/dist/components/theme-context.js +2 -2
  420. package/dist/components/theme-context.js.map +1 -1
  421. package/dist/components/use-hydration-phase.d.ts +1 -1
  422. package/dist/components/use-hydration-phase.js +1 -1
  423. package/dist/components/use-hydration-phase.js.map +1 -1
  424. package/dist/components/users/invite-user-bar.d.ts +27 -0
  425. package/dist/components/users/invite-user-bar.js +25 -9
  426. package/dist/components/users/invite-user-bar.js.map +1 -1
  427. package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
  428. package/dist/components/users/role-policy-drift-issues.js +71 -0
  429. package/dist/components/users/role-policy-drift-issues.js.map +1 -0
  430. package/dist/components/users/users-collection-view-source.d.ts +1 -1
  431. package/dist/components/users/users-collection-view-source.js +4 -2
  432. package/dist/components/users/users-collection-view-source.js.map +1 -1
  433. package/dist/components/users/users-module.js +1 -1
  434. package/dist/components/users/users-module.js.map +1 -1
  435. package/dist/components/users/users-schema.d.ts +10 -1
  436. package/dist/components/users/users-schema.js +45 -25
  437. package/dist/components/users/users-schema.js.map +1 -1
  438. package/dist/components/users/users-store.js +3 -3
  439. package/dist/components/users/users-store.js.map +1 -1
  440. package/dist/components/workspace-projects.d.ts +1 -1
  441. package/dist/components/workspace-projects.js +1 -1
  442. package/dist/components/workspace-projects.js.map +1 -1
  443. package/dist/lib/ai/index.d.ts +1 -1
  444. package/dist/lib/ai/index.js +1 -1
  445. package/dist/lib/ai/index.js.map +1 -1
  446. package/dist/lib/ai/node-handler.d.ts +1 -1
  447. package/dist/lib/ai/node-handler.js.map +1 -1
  448. package/dist/lib/ai/translate-document.js +1 -1
  449. package/dist/lib/ai/translate-document.js.map +1 -1
  450. package/dist/lib/assets/upload.d.ts +25 -7
  451. package/dist/lib/assets/upload.js +30 -21
  452. package/dist/lib/assets/upload.js.map +1 -1
  453. package/dist/lib/clipboard/paste-issues.js +1 -1
  454. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  455. package/dist/lib/clipboard/paste-target.js +2 -2
  456. package/dist/lib/clipboard/paste-target.js.map +1 -1
  457. package/dist/lib/cms/published-documents.d.ts +24 -6
  458. package/dist/lib/cms/published-documents.js +39 -6
  459. package/dist/lib/cms/published-documents.js.map +1 -1
  460. package/dist/lib/cms-views/view-model.d.ts +12 -99
  461. package/dist/lib/cms-views/view-model.js +22 -103
  462. package/dist/lib/cms-views/view-model.js.map +1 -1
  463. package/dist/lib/collection-view/collection-view-source.d.ts +13 -3
  464. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  465. package/dist/lib/data/content-session-refresh.d.ts +1 -1
  466. package/dist/lib/data/content-session-refresh.js.map +1 -1
  467. package/dist/lib/data/playground-unload-guard.d.ts +1 -1
  468. package/dist/lib/data/playground-unload-guard.js +1 -1
  469. package/dist/lib/data/playground-unload-guard.js.map +1 -1
  470. package/dist/lib/engine-version.d.ts +1 -1
  471. package/dist/lib/engine-version.js +1 -1
  472. package/dist/lib/engine-version.js.map +1 -1
  473. package/dist/lib/i18n/i18n.js +1 -1
  474. package/dist/lib/i18n/i18n.js.map +1 -1
  475. package/dist/lib/i18n/messages.de.js +11 -3
  476. package/dist/lib/i18n/messages.de.js.map +1 -1
  477. package/dist/lib/i18n/messages.en.d.ts +9 -1
  478. package/dist/lib/i18n/messages.en.js +16 -4
  479. package/dist/lib/i18n/messages.en.js.map +1 -1
  480. package/dist/lib/operations/config-manifest-lookup.d.ts +1 -1
  481. package/dist/lib/operations/config-manifest-lookup.js +3 -3
  482. package/dist/lib/operations/config-manifest-lookup.js.map +1 -1
  483. package/dist/lib/operations/project-runtime-config.d.ts +9 -0
  484. package/dist/lib/operations/project-runtime-config.js +13 -0
  485. package/dist/lib/operations/project-runtime-config.js.map +1 -1
  486. package/dist/lib/operations/studio-operations.d.ts +9 -1
  487. package/dist/lib/operations/studio-operations.js +6 -1
  488. package/dist/lib/operations/studio-operations.js.map +1 -1
  489. package/dist/lib/persistence/convex-adapter.d.ts +16 -3
  490. package/dist/lib/persistence/convex-adapter.js +78 -47
  491. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  492. package/dist/lib/persistence/deployment-readiness.js +14 -11
  493. package/dist/lib/persistence/deployment-readiness.js.map +1 -1
  494. package/dist/lib/persistence/index.js +14 -1
  495. package/dist/lib/persistence/index.js.map +1 -1
  496. package/dist/lib/persona/labels.js +2 -1
  497. package/dist/lib/persona/labels.js.map +1 -1
  498. package/dist/lib/query/index.d.ts +2 -2
  499. package/dist/lib/query/index.js +1 -1
  500. package/dist/lib/query/index.js.map +1 -1
  501. package/dist/lib/query/operations-query.d.ts +27 -1
  502. package/dist/lib/query/operations-query.js +13 -1
  503. package/dist/lib/query/operations-query.js.map +1 -1
  504. package/dist/lib/references/reference-model.js +1 -1
  505. package/dist/lib/references/reference-model.js.map +1 -1
  506. package/dist/lib/references/references-store.js +1 -1
  507. package/dist/lib/references/references-store.js.map +1 -1
  508. package/dist/lib/registry/index.js +2 -2
  509. package/dist/lib/registry/index.js.map +1 -1
  510. package/dist/lib/registry/preview.d.ts +10 -0
  511. package/dist/lib/registry/preview.js +13 -4
  512. package/dist/lib/registry/preview.js.map +1 -1
  513. package/dist/lib/routes/route-issues.js +6 -3
  514. package/dist/lib/routes/route-issues.js.map +1 -1
  515. package/dist/lib/workspace/index.d.ts +6 -0
  516. package/dist/lib/workspace/index.js +8 -0
  517. package/dist/lib/workspace/index.js.map +1 -1
  518. package/dist/lib/workspace/workspace-source.js +6 -7
  519. package/dist/lib/workspace/workspace-source.js.map +1 -1
  520. package/dist/mount.d.ts +28 -0
  521. package/dist/mount.js +27 -0
  522. package/dist/mount.js.map +1 -0
  523. package/dist/previews/entry-node.d.ts +14 -0
  524. package/dist/previews/entry-node.js +22 -0
  525. package/dist/previews/entry-node.js.map +1 -0
  526. package/dist/previews/harness.js +2 -16
  527. package/dist/previews/harness.js.map +1 -1
  528. package/dist/router/root.js +10 -10
  529. package/dist/router/root.js.map +1 -1
  530. package/dist/ui/badge.d.ts +1 -1
  531. package/dist/ui/button.d.ts +2 -2
  532. package/package.json +15 -11
  533. package/dist/components/redirects/redirects-collection-view-source.d.ts +0 -53
  534. package/dist/components/redirects/redirects-collection-view-source.js +0 -146
  535. package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
  536. package/dist/components/redirects/redirects-module.d.ts +0 -21
  537. package/dist/components/redirects/redirects-module.js +0 -40
  538. package/dist/components/redirects/redirects-module.js.map +0 -1
  539. package/dist/components/redirects/redirects-store.d.ts +0 -68
  540. package/dist/components/redirects/redirects-store.js +0 -132
  541. package/dist/components/redirects/redirects-store.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"composition-field-editor.js","sourceRoot":"","sources":["../../../src/components/builder/composition-field-editor.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { AlertTriangle, Plus, Redo2, SlidersHorizontal, Undo2 } from 'lucide-react'\nimport { DEFAULT_SLOT, mergeBlockRoots, splitBlockRoots } from '@elytracms/core/project-graph'\nimport type { ComponentNode, PreservedSequenceItem, SplitBlockRoots } from '@elytracms/core/project-graph'\nimport { DRAFT_PERSPECTIVE, fieldAllowList } from '@elytracms/core/cms-core'\nimport type { BlocksFieldDef } from '@elytracms/core/cms-core'\nimport type { ComponentRegistry } from '@elytracms/core/component-registry'\nimport type { BlockNode, BlockSequenceValue } from '@elytracms/core/rich-text'\nimport { Button, Panel, Row, Spacer, Text, Toolbar, ToolbarGroup, ToolbarSeparator } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport type { MessageKey } from '@elytracms/studio/lib/i18n'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { BuilderProvider, useBuilder } from './builder-store'\nimport type { CompositionFieldScope } from './builder-store'\nimport { useCanvasViewports } from './canvas-breakpoints'\nimport type { ViewportOption } from './canvas-breakpoints'\nimport { BuilderCanvas } from './canvas'\nimport { PageTree } from './page-tree'\nimport { PropertyInspector } from './property-inspector'\nimport { InspectorSidebar } from '../inspector/inspector-sidebar'\nimport type { InspectorPane } from '../inspector/inspector-sidebar'\nimport { InsertMenu } from './insert-menu'\nimport type { InsertContext } from './insert-menu'\nimport { insertablePositions, resolveInsertContext, resolveInsertContextAt } from './insert-context'\nimport type { InsertPosition } from './insert-context'\nimport { InsertAffordanceProvider } from './insert-affordance-context'\nimport type { InsertAffordanceApi } from './insert-affordance-context'\nimport { PropSyncProvider } from './prop-sync-context'\nimport { ReuseProvider } from './reuse-context'\nimport { PlacementRefusalNotice } from './placement-refusal'\nimport { fieldLabel } from '../cms/cms-labels'\n\n/**\n * EC-188 — the block-field surface: one canvas engine mounted on a composition\n * **field value** (AD-12), not the project graph.\n *\n * The composition tree is wrapped in a synthetic single-page graph and edited by\n * a SCOPED `BuilderProvider` with **no `projectId`** (so the graph persister is a\n * no-op — builder-store.tsx). Every committed edit is lifted out via `onChange`\n * into the CMS document draft; the document's own Save persists it (the tree is\n * stored as document data, `v.any()`).\n *\n * EC-547 (EC-500 D1–D3): the stored value is the converged v2 block sequence\n * `{ version: 2, content: BlockNode[] }`. The canvas edits the sequence's\n * `elytra/component` nodes; typography nodes survive the session byte-for-byte\n * and keep their order relative to components on write-back (see\n * {@link splitCompositionValue} / {@link mergeCompositionValue}). Legacy bare\n * node/array values are still ACCEPTED on read during the staging window, but\n * every write emits v2. This reuses the real canvas + inspector +\n * blocks panel unchanged — the same EC-158–164 mechanics — without touching the\n * project-wide builder store. Surfaced on a dedicated \"Canvas\" fieldset tab in\n * the CMS detail editor so the page builder gets full screen real estate rather\n * than being crammed into the field list.\n */\n\nconst SYNTH_ROOT_ID = 'composition.root'\n\n/**\n * A sequence item the canvas does not natively edit (EC-547 / EC-500 D3):\n * typography nodes, unknown/future node kinds, even entries the v2 schema would\n * reject — the editor is not a validator, so they are carried BYTE-FOR-BYTE and\n * re-merged on write-back rather than parsed, rewritten, or dropped.\n * `componentPrefix` records how many component nodes preceded the item in the\n * original sequence — the anchor {@link mergeCompositionValue} splices by.\n */\nexport type PreservedBlockNode = PreservedSequenceItem<BlockNode>\n\n/** A field value split for canvas editing: component roots + everything else. */\nexport type SplitCompositionValue = SplitBlockRoots<BlockNode>\n\n/**\n * Split a stored blocks-field value into the component nodes the canvas edits\n * and the sequence items it must preserve (EC-547, the EC-500 round-trip law:\n * a projection may only rewrite nodes it natively edits). Typography items are\n * deliberately NOT surfaced as editable canvas blocks in v1 (EC-500 D3) — they\n * survive the editing session and keep their order on write-back.\n *\n * EC-576: the logic is core's {@link splitBlockRoots} — the ONE reader every\n * blocks writer shares. This is the canvas's typed view of it: project-graph\n * sits below rich-text and cannot name {@link BlockNode}, so the carried items\n * arrive `unknown`; they are typed here, not checked (preservation over parsing).\n */\nexport function splitCompositionValue(value: unknown): SplitCompositionValue {\n return splitBlockRoots(value) as SplitCompositionValue\n}\n\n/**\n * Merge canvas-edited component roots back into the stored sequence (EC-547).\n * The merge rule — preserved items anchored to their ordinal position between\n * components, every root tagged, the write always v2 — is core's\n * {@link mergeBlockRoots} (EC-576: the same merge every blocks writer goes\n * through, via `editBlockRoots`). Typed here as the rich-text value.\n */\nexport function mergeCompositionValue(\n roots: readonly ComponentNode[],\n preserved: readonly PreservedBlockNode[],\n): BlockSequenceValue {\n return mergeBlockRoots(roots, preserved)\n}\n\n/**\n * The field scope the editor's store carries (EC-554): the field's label (named\n * by refusal notices) and its EFFECTIVE top-level vocabulary (EC-584 — read through\n * the per-FieldType definition, `fieldAllowList`: the declared `allow`, else the\n * `blocks` default, any canvas-eligible component).\n */\nexport function compositionFieldScope(field: BlocksFieldDef): CompositionFieldScope {\n // A blocks field always has a vocabulary (its type declares the default).\n return { label: fieldLabel(field), allow: fieldAllowList(field)! }\n}\n\n/**\n * Wrap the field value's component roots in a synthetic root container (EC-287).\n * The builder store edits this single root-bearing composition directly; its\n * children are the field's component blocks, lifted back out via `onChange`.\n * EC-591: the root is the component that fills the `layout.stack` role — the\n * project's own, from its component list — never a hardcoded engine id.\n */\nfunction buildSyntheticRoot(roots: readonly ComponentNode[], stackComponentId: string): ComponentNode {\n return {\n id: SYNTH_ROOT_ID,\n componentId: stackComponentId,\n slots: { [DEFAULT_SLOT]: [...roots] },\n }\n}\n\n/**\n * The canvas notices for roles nobody in the component list fills (EC-591) — each\n * names the gesture that is OFF because of it, so a missing role is visible, never\n * a silent dead affordance. Only gestures the canvas actually has are listed: the\n * `layout.stack` role is the canvas itself (see {@link CompositionFieldEditor}),\n * `layout.grid` the wrap-beside gesture, `text` the seeded layout presets.\n * (`richText` — the typography projection, EC-549 — and `image` — asset drop —\n * have no canvas gesture yet, so there is nothing to switch off.) Pure.\n */\nexport function canvasRoleNotices(registry: ComponentRegistry): MessageKey[] {\n const notices: MessageKey[] = []\n if (!registry.roleComponent('layout.grid')) notices.push('builder.roles.noGrid')\n if (!registry.roleComponent('text')) notices.push('builder.roles.noText')\n return notices\n}\n\n/** The role notices above the canvas (EC-591) — a standing truth about the component list. */\nexport function CanvasRoleNotices({ notices }: { notices: readonly MessageKey[] }) {\n if (notices.length === 0) return null\n return (\n <div data-testid=\"canvas-role-notices\" className=\"shrink-0 border-b border-border bg-muted/40\">\n {notices.map((key) => (\n <Row key={key} gap=\"2\" align=\"center\" className=\"px-3 py-1.5\">\n <AlertTriangle aria-hidden className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <Text size=\"xs\" tone=\"muted\">\n {translate(key)}\n </Text>\n </Row>\n ))}\n </div>\n )\n}\n\n/**\n * The canvas in place of the editor when nobody fills `layout.stack` (EC-591): the\n * blocks of a composition field compose inside that component, so there is no\n * canvas without it. The value is not touched — nothing mounts that could write.\n */\nexport function NoStackCanvasNotice() {\n return (\n <Panel data-testid=\"canvas-no-stack\" className=\"flex h-full min-h-0 items-center justify-center p-6\">\n <Row gap=\"2\" align=\"center\" className=\"max-w-prose\">\n <AlertTriangle aria-hidden className=\"size-4 shrink-0 text-muted-foreground\" />\n <Text size=\"sm\" tone=\"muted\">\n {translate('builder.roles.noStack')}\n </Text>\n </Row>\n </Panel>\n )\n}\n\nexport function CompositionFieldEditor({\n field,\n value,\n onChange,\n readOnly,\n trailingPanes,\n conflicts,\n}: {\n field: BlocksFieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n /**\n * EC-595 A2 — the field's per-field conflicts (a refused node patch: \"changed by X —\n * take theirs / keep mine\"), rendered above the canvas: the canvas IS the field.\n */\n conflicts?: ReactNode\n /** Inspector panes the document editor appends after the canvas-supplied\n * `Properties` pane (EC-224 — the frame-owned `Versions` pane). */\n trailingPanes?: readonly InspectorPane[]\n}) {\n // Split the value ONCE at mount (EC-547): component nodes become the canvas\n // roots; every other sequence item is preserved aside for write-back. The\n // editor is uncontrolled thereafter — it is the source of truth while open and\n // lifts changes out via onChange; re-opening the tab remounts from the current\n // draft. (BuilderProvider reads `composition` only in its lazy initial state,\n // so later prop changes are ignored regardless.)\n const initial = useMemo(() => splitCompositionValue(value), [])\n // EC-591: the roles come from the project's component list (the registry).\n const registry = getStudioRegistry().registry\n const stack = registry.roleComponent('layout.stack')\n const layout = registry.layout\n const notices = useMemo(() => canvasRoleNotices(registry), [registry])\n const initialRoot = useMemo(\n () => (stack ? buildSyntheticRoot(initial.roots, stack.id) : null),\n [initial, stack],\n )\n // EC-554: the field's `allow` travels WITH the store, so every placement\n // surface (canvas + tree DnD, paste, the insert offer) judges the same value.\n const fieldScope = useMemo(() => compositionFieldScope(field), [field])\n\n if (!initialRoot) return <NoStackCanvasNotice />\n\n return (\n <BuilderProvider\n composition={initialRoot}\n preserved={initial.preserved}\n field={fieldScope}\n components={registry.lookup}\n layout={layout}\n >\n <CompositionLift onChange={onChange} readOnly={readOnly} />\n <ReuseProvider>\n {/* EC-408: the field↔component selection-sync channel wraps BOTH the canvas\n and the inspector (they reach each other through nothing else). */}\n <PropSyncProvider>\n <CompositionEditorShell\n readOnly={readOnly}\n trailingPanes={trailingPanes}\n roleNotices={notices}\n conflicts={conflicts}\n />\n </PropSyncProvider>\n </ReuseProvider>\n </BuilderProvider>\n )\n}\n\n/**\n * Lifts committed edits out of the scoped store into the CMS draft. The first\n * render establishes the (container-normalized) baseline so simply opening the\n * tab never marks the document dirty; every subsequent change to the block list\n * OR to the preserved typography (EC-549: a run deleted or edited, or an undo of\n * either) emits the merged v2 sequence value (EC-547) — the canvas roots spliced\n * back between the preserved typography items, both read from the store.\n * Cardinality (`multiple`) no longer shapes the stored value: it is a validation\n * constraint over the one envelope (EC-500 D2).\n */\nfunction CompositionLift({\n onChange,\n readOnly,\n}: {\n onChange: (value: unknown) => void\n readOnly: boolean\n}) {\n const builder = useBuilder()\n const blocks = builder.present.slots?.[DEFAULT_SLOT] ?? EMPTY_BLOCKS\n const preserved = builder.preserved\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const baselineRef = useRef<{ blocks: readonly ComponentNode[]; preserved: readonly PreservedBlockNode[] } | null>(null)\n\n useEffect(() => {\n // EC-226: never lift edits out of a READ-ONLY canvas (the Published view).\n // The canvas's keyboard listeners (clipboard paste/cut) aren't blocked by the\n // visual `pointer-events-none`, so without this a Cmd+V could mutate the\n // graph; refusing to lift keeps the document draft intact regardless. The\n // editor remounts per perspective, so any transient in-canvas change is\n // discarded on switch.\n if (readOnly) return\n const baseline = baselineRef.current\n if (baseline === null) {\n baselineRef.current = { blocks, preserved: preserved as readonly PreservedBlockNode[] }\n return\n }\n if (blocks !== baseline.blocks || preserved !== baseline.preserved) {\n baselineRef.current = { blocks, preserved: preserved as readonly PreservedBlockNode[] }\n onChangeRef.current(mergeCompositionValue(blocks, preserved as readonly PreservedBlockNode[]))\n }\n }, [blocks, preserved, readOnly])\n\n return null\n}\n\nconst EMPTY_BLOCKS: readonly ComponentNode[] = []\n\n/** A breakpoint preview-width toggle (EC-211/EC-250). Switching the viewport\n * resizes only the preview frame (not an undoable edit, writes no graph command).\n * The label is the breakpoint name (`Full`/`sm`/`md`/…) with the real px width in\n * the tooltip — so the switcher reflects the site's actual screens, not a fixed\n * desktop/tablet/phone triple. */\nfunction ViewportButton({ option }: { option: ViewportOption }) {\n const builder = useBuilder()\n const active = builder.viewport === option.key\n return (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.viewportPreviewAria', { label: option.label })}\n aria-pressed={active}\n title={\n option.px !== undefined\n ? `${option.label} · ${option.px}px`\n : translate('builder.canvas.viewportWidthTitle', { label: option.label })\n }\n onClick={() => builder.dispatch({ type: 'viewport', viewport: option.key })}\n className={cn('px-2 text-xs', active && 'bg-accent text-accent-foreground')}\n >\n {option.label}\n </Button>\n )\n}\n\n/** The centered breakpoint switcher (EC-250): the resolved viewport set, plus a\n * visible validation issue when a project declared breakpoints but none are usable. */\nfunction BreakpointSwitcher() {\n const { viewports, issue } = useCanvasViewports()\n return (\n <Row gap=\"1\" align=\"center\">\n <ToolbarGroup>\n {viewports.map((v) => (\n <ViewportButton key={v.key} option={v} />\n ))}\n </ToolbarGroup>\n {issue ? (\n <Row gap=\"1\" align=\"center\" className=\"text-amber-600 dark:text-amber-400\" title={issue}>\n <AlertTriangle aria-hidden className=\"size-3.5\" />\n <Text size=\"xs\">{translate('builder.canvas.breakpointsIssueLabel')}</Text>\n </Row>\n ) : null}\n </Row>\n )\n}\n\n/**\n * The slim editor surface — the canvas + layers + inspector, plus Insert and\n * undo/redo. It deliberately drops the project-builder *lifecycle* chrome\n * (publish, versions, AI, page SEO): the document owns the lifecycle, not the\n * field. It keeps spatial/preview controls — including the breakpoint\n * preview-width switcher (EC-211), a canvas concern, not a lifecycle one.\n */\nfunction CompositionEditorShell({\n readOnly,\n trailingPanes,\n roleNotices,\n conflicts,\n}: {\n readOnly: boolean\n /** EC-595 A2 — the field's conflict notices (see {@link CompositionFieldEditor}). */\n conflicts?: ReactNode\n /** Document-level panes appended after `Properties` (EC-224). */\n trailingPanes?: readonly InspectorPane[]\n /** Notices for the roles nobody fills (EC-591) — see {@link canvasRoleNotices}. */\n roleNotices?: readonly MessageKey[]\n}) {\n const builder = useBuilder()\n const dispatch = builder.dispatch\n const [insertContext, setInsertContext] = useState<InsertContext | null>(null)\n const [insertOpen, setInsertOpen] = useState(false)\n const rootId = builder.present.id\n\n // The canvas SUPPLIES the `Properties` pane (EC-224); the document editor\n // appends its own (`Versions`). Properties dims under read-only just as the\n // whole inspector column used to — but the appended panes stay interactive.\n const propertiesPane = useMemo<InspectorPane>(\n () => ({\n id: 'properties',\n label: translate('builder.inspector.propertiesPane'),\n icon: <SlidersHorizontal className=\"size-3.5\" />,\n render: () => (\n <div className={cn('h-full min-h-0 overflow-hidden', readOnly && 'pointer-events-none opacity-60')}>\n <PropertyInspector />\n </div>\n ),\n }),\n [readOnly],\n )\n const panes = useMemo<InspectorPane[]>(\n () => [propertiesPane, ...(trailingPanes ?? [])],\n [propertiesPane, trailingPanes],\n )\n\n // The single insert entry point for BOTH the toolbar and the page tree's\n // \"Add child\" (EC-383). It selects its target first so `builder.selectedId`\n // is the one source of truth for where an insert lands: the toolbar resolves\n // `builder.selectedId ?? rootId` while Add-child passes an explicit row, and\n // without this sync clicking Add-child (which otherwise leaves selection put)\n // could target a different node than the toolbar — surfacing as a mismatched\n // (even empty \"No allowed components match\") insert dialog. Both now resolve\n // the identical target + allow set for the same selection.\n function openInsert(nodeId: string) {\n // EC-584: the offer reads the store's placement scope — the field's vocabulary at\n // the composition root, each slot's below — the same scope the reducer judges by.\n const ctx = resolveInsertContext(builder, nodeId)\n if (!ctx) return\n if (nodeId !== builder.selectedId) dispatch({ type: 'select', id: nodeId })\n setInsertContext(ctx)\n setInsertOpen(true)\n }\n\n // EC-405: the in-canvas \"+\" affordances resolve the SAME slot rules through the\n // shared position-carrying insert-context resolution, and open the panel at the\n // clicked edge (an explicit position that survives to the insertion, rather than\n // the panel re-deriving it from the selection). Re-memoized on every store change\n // so the closures read the live graph.\n const affordanceApi = useMemo<InsertAffordanceApi>(\n () => ({\n positionsFor: (nodeId: string) => insertablePositions(builder, nodeId),\n openAt: (nodeId: string, position: InsertPosition) => {\n const ctx = resolveInsertContextAt(builder, nodeId, position)\n if (!ctx) return\n if (nodeId !== builder.selectedId) dispatch({ type: 'select', id: nodeId })\n setInsertContext(ctx)\n setInsertOpen(true)\n },\n }),\n [builder, dispatch],\n )\n\n return (\n <InsertAffordanceProvider value={affordanceApi}>\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 border-b border-border\">\n <Toolbar>\n <ToolbarGroup>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.undo')}\n title={translate('builder.canvas.undo')}\n disabled={readOnly || !builder.canUndo}\n onClick={() => dispatch({ type: 'undo' })}\n >\n <Undo2 />\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.redo')}\n title={translate('builder.canvas.redo')}\n disabled={readOnly || !builder.canRedo}\n onClick={() => dispatch({ type: 'redo' })}\n >\n <Redo2 />\n </Button>\n </ToolbarGroup>\n <ToolbarSeparator />\n <Button\n size=\"sm\"\n variant=\"ghost\"\n disabled={readOnly}\n onClick={() => openInsert(builder.selectedId ?? rootId)}\n >\n <Plus /> {translate('builder.canvas.insert')}\n </Button>\n {/* Breakpoint preview-width switcher (EC-211/EC-250), centered over the\n canvas via flanking spacers. A preview control, not an edit — stays\n active even in read-only published preview. */}\n <Spacer />\n <BreakpointSwitcher />\n <Spacer />\n </Toolbar>\n </div>\n\n <div className=\"grid min-h-0 flex-1 grid-cols-[minmax(200px,240px)_1fr_minmax(280px,320px)] gap-px bg-border\">\n <Panel className={cn('rounded-none border-0', readOnly && 'pointer-events-none opacity-60')}>\n <PageTree onAddChild={openInsert} />\n </Panel>\n <div className={cn('flex min-h-0 flex-col bg-background', readOnly && 'pointer-events-none opacity-60')}>\n {conflicts ? <div className=\"shrink-0 border-b border-border p-2\">{conflicts}</div> : null}\n {/* EC-584: a placement the reducer refused, with its reason — never a silent no-op. */}\n <PlacementRefusalNotice\n refusal={builder.refusal}\n fieldLabel={builder.field.label}\n onDismiss={() => dispatch({ type: 'clearRefusal' })}\n />\n <CanvasRoleNotices notices={roleNotices ?? []} />\n {/* EC-300/AD-16: the read-only Published view resolves references against\n the published perspective (honest preview); the editable Draft view\n resolves drafts (a just-made reusable shows immediately). */}\n <BuilderCanvas perspective={readOnly ? 'published' : DRAFT_PERSPECTIVE} />\n </div>\n <InspectorSidebar panes={panes} />\n </div>\n\n <InsertMenu context={insertContext} open={insertOpen} onOpenChange={setInsertOpen} />\n </div>\n </InsertAffordanceProvider>\n )\n}\n"]}
1
+ {"version":3,"file":"composition-field-editor.js","sourceRoot":"","sources":["../../../src/components/builder/composition-field-editor.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE5D,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACnF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAE9F,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAI5E,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAChH,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAElD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAE/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AAEjE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE1C,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAEpG,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAA;AAEtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,aAAa,GAAG,kBAAkB,CAAA;AAexC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,OAAO,eAAe,CAAC,KAAK,CAA0B,CAAA;AACxD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAA+B,EAC/B,SAAwC;IAExC,OAAO,eAAe,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;AAC1C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAqB;IACzD,0EAA0E;IAC1E,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAE,EAAE,CAAA;AACpE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAC,KAA+B,EAAE,gBAAwB;IACnF,OAAO;QACL,EAAE,EAAE,aAAa;QACjB,WAAW,EAAE,gBAAgB;QAC7B,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE;KACtC,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAA2B;IAC3D,MAAM,OAAO,GAAiB,EAAE,CAAA;IAChC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;IAChF,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAA;IACzE,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,iBAAiB,CAAC,EAAE,OAAO,EAAsC;IAC/E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACrC,OAAO,CACL,6BAAiB,qBAAqB,EAAC,SAAS,EAAC,6CAA6C,YAC3F,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CACpB,MAAC,GAAG,IAAW,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,aAAa,aAC3D,KAAC,aAAa,yBAAa,SAAS,EAAC,yCAAyC,GAAG,EACjF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,GAAG,CAAC,GACV,KAJC,GAAG,CAKP,CACP,CAAC,GACE,CACP,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,CACL,KAAC,KAAK,mBAAa,iBAAiB,EAAC,SAAS,EAAC,qDAAqD,YAClG,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,aAAa,aACjD,KAAC,aAAa,yBAAa,SAAS,EAAC,uCAAuC,GAAG,EAC/E,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,uBAAuB,CAAC,GAC9B,IACH,GACA,CACT,CAAA;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,EACrC,KAAK,EACL,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,aAAa,EACb,SAAS,GA8BV;IACC,4EAA4E;IAC5E,gFAAgF;IAChF,gFAAgF;IAChF,4EAA4E;IAC5E,2EAA2E;IAC3E,wEAAwE;IACxE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;IAC/D,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC,QAAQ,CAAA;IAC7C,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,cAAc,CAAC,CAAA;IACpD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAA;IAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IACtE,MAAM,WAAW,GAAG,OAAO,CACzB,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAClE,CAAC,OAAO,EAAE,KAAK,CAAC,CACjB,CAAA;IACD,yEAAyE;IACzE,8EAA8E;IAC9E,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEvE,IAAI,CAAC,WAAW;QAAE,OAAO,KAAC,mBAAmB,KAAG,CAAA;IAEhD,OAAO,CACL,MAAC,eAAe,IACd,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,QAAQ,CAAC,MAAM,EAC3B,MAAM,EAAE,MAAM,aAEd,KAAC,eAAe,IAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAI,EAC3E,KAAC,oBAAoB,IAAC,KAAK,EAAE,OAAO,IAAI,IAAI,YAC1C,KAAC,aAAa,cAGZ,KAAC,gBAAgB,cACf,KAAC,sBAAsB,IACrB,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE,SAAS,GACpB,GACe,GACL,GACK,IACP,CACnB,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,eAAe,CAAC,EACvB,MAAM,EACN,QAAQ,EACR,QAAQ,GAKT;IACC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,YAAY,CAAC,IAAI,YAAY,CAAA;IACpE,MAAM,SAAS,GAAG,OAAO,CAAC,SAA0C,CAAA;IACpE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;IAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;IACjC,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IACpC,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAA;IAC9B,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;IAEtG,SAAS,CAAC,GAAG,EAAE;QACb,8EAA8E;QAC9E,+CAA+C;QAC/C,IAAI,QAAQ,IAAI,CAAC,MAAM;YAAE,OAAM;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QACnC,IAAI,MAAM;YAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9B,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE3B,SAAS,CAAC,GAAG,EAAE;QACb,2EAA2E;QAC3E,8EAA8E;QAC9E,yEAAyE;QACzE,0EAA0E;QAC1E,wEAAwE;QACxE,uBAAuB;QACvB,IAAI,QAAQ;YAAE,OAAM;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,CAAA;QAC3E,IAAI,IAAI;YAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IACtD,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE1C,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,YAAY,GAA6B,EAAE,CAAA;AAEjD;;;;kCAIkC;AAClC,SAAS,cAAc,CAAC,EAAE,MAAM,EAA8B;IAC5D,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,MAAM,CAAC,GAAG,CAAA;IAC9C,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAC,OAAO,gBACH,SAAS,CAAC,oCAAoC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,kBACtE,MAAM,EACpB,KAAK,EACH,MAAM,CAAC,EAAE,KAAK,SAAS;YACrB,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM,MAAM,CAAC,EAAE,IAAI;YACpC,CAAC,CAAC,SAAS,CAAC,mCAAmC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,EAE7E,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,EAC3E,SAAS,EAAE,EAAE,CAAC,cAAc,EAAE,MAAM,IAAI,kCAAkC,CAAC,YAE1E,MAAM,CAAC,KAAK,GACN,CACV,CAAA;AACH,CAAC;AAED;uFACuF;AACvF,SAAS,kBAAkB;IACzB,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,kBAAkB,EAAE,CAAA;IACjD,OAAO,CACL,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aACzB,KAAC,YAAY,cACV,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CACpB,KAAC,cAAc,IAAa,MAAM,EAAE,CAAC,IAAhB,CAAC,CAAC,GAAG,CAAe,CAC1C,CAAC,GACW,EACd,KAAK,CAAC,CAAC,CAAC,CACP,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,oCAAoC,EAAC,KAAK,EAAE,KAAK,aACrF,KAAC,aAAa,yBAAa,SAAS,EAAC,UAAU,GAAG,EAClD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,YAAE,SAAS,CAAC,sCAAsC,CAAC,GAAQ,IACtE,CACP,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,EAC9B,QAAQ,EACR,aAAa,EACb,WAAW,EACX,SAAS,GASV;IACC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;IACjC,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAuB,IAAI,CAAC,CAAA;IAC9E,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAA;IAEjC,0EAA0E;IAC1E,4EAA4E;IAC5E,4EAA4E;IAC5E,MAAM,cAAc,GAAG,OAAO,CAC5B,GAAG,EAAE,CAAC,CAAC;QACL,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,SAAS,CAAC,kCAAkC,CAAC;QACpD,IAAI,EAAE,KAAC,iBAAiB,IAAC,SAAS,EAAC,UAAU,GAAG;QAChD,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,cAAK,SAAS,EAAE,EAAE,CAAC,gCAAgC,EAAE,QAAQ,IAAI,gCAAgC,CAAC,YAChG,KAAC,iBAAiB,KAAG,GACjB,CACP;KACF,CAAC,EACF,CAAC,QAAQ,CAAC,CACX,CAAA;IACD,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,cAAc,EAAE,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,EAChD,CAAC,cAAc,EAAE,aAAa,CAAC,CAChC,CAAA;IAED,yEAAyE;IACzE,4EAA4E;IAC5E,6EAA6E;IAC7E,6EAA6E;IAC7E,8EAA8E;IAC9E,6EAA6E;IAC7E,6EAA6E;IAC7E,2DAA2D;IAC3D,SAAS,UAAU,CAAC,MAAc;QAChC,kFAAkF;QAClF,kFAAkF;QAClF,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QACjD,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,IAAI,MAAM,KAAK,OAAO,CAAC,UAAU;YAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;QAC3E,gBAAgB,CAAC,GAAG,CAAC,CAAA;QACrB,aAAa,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC;IAED,gFAAgF;IAChF,gFAAgF;IAChF,iFAAiF;IACjF,kFAAkF;IAClF,uCAAuC;IACvC,MAAM,aAAa,GAAG,OAAO,CAC3B,GAAG,EAAE,CAAC,CAAC;QACL,YAAY,EAAE,CAAC,MAAc,EAAE,EAAE,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC;QACtE,MAAM,EAAE,CAAC,MAAc,EAAE,QAAwB,EAAE,EAAE;YACnD,MAAM,GAAG,GAAG,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;YAC7D,IAAI,CAAC,GAAG;gBAAE,OAAM;YAChB,IAAI,MAAM,KAAK,OAAO,CAAC,UAAU;gBAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;YAC3E,gBAAgB,CAAC,GAAG,CAAC,CAAA;YACrB,aAAa,CAAC,IAAI,CAAC,CAAA;QACrB,CAAC;KACF,CAAC,EACF,CAAC,OAAO,EAAE,QAAQ,CAAC,CACpB,CAAA;IAED,OAAO,CACL,KAAC,wBAAwB,IAAC,KAAK,EAAE,aAAa,YAC5C,eAAK,SAAS,EAAC,8BAA8B,aAC3C,cAAK,SAAS,EAAC,iCAAiC,YAC9C,MAAC,OAAO,eACN,MAAC,YAAY,eACX,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,OAAO,EAAC,OAAO,gBACH,SAAS,CAAC,qBAAqB,CAAC,EAC5C,KAAK,EAAE,SAAS,CAAC,qBAAqB,CAAC,EACvC,QAAQ,EAAE,QAAQ,IAAI,CAAC,OAAO,CAAC,OAAO,EACtC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,YAEzC,KAAC,KAAK,KAAG,GACF,EACT,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,OAAO,EAAC,OAAO,gBACH,SAAS,CAAC,qBAAqB,CAAC,EAC5C,KAAK,EAAE,SAAS,CAAC,qBAAqB,CAAC,EACvC,QAAQ,EAAE,QAAQ,IAAI,CAAC,OAAO,CAAC,OAAO,EACtC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,YAEzC,KAAC,KAAK,KAAG,GACF,IACI,EACf,KAAC,gBAAgB,KAAG,EACpB,MAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAC,OAAO,EACf,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC,aAEvD,KAAC,IAAI,KAAG,OAAE,SAAS,CAAC,uBAAuB,CAAC,IACrC,EAIT,KAAC,MAAM,KAAG,EACV,KAAC,kBAAkB,KAAG,EACtB,KAAC,MAAM,KAAG,IACF,GACN,EAEN,eAAK,SAAS,EAAC,8FAA8F,aAC3G,KAAC,KAAK,IAAC,SAAS,EAAE,EAAE,CAAC,uBAAuB,EAAE,QAAQ,IAAI,gCAAgC,CAAC,YACzF,KAAC,QAAQ,IAAC,UAAU,EAAE,UAAU,GAAI,GAC9B,EACR,eAAK,SAAS,EAAE,EAAE,CAAC,qCAAqC,EAAE,QAAQ,IAAI,gCAAgC,CAAC,aACpG,SAAS,CAAC,CAAC,CAAC,cAAK,SAAS,EAAC,qCAAqC,YAAE,SAAS,GAAO,CAAC,CAAC,CAAC,IAAI,EAE1F,KAAC,sBAAsB,IACrB,OAAO,EAAE,OAAO,CAAC,OAAO,EACxB,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,EAC/B,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,GACnD,EACF,KAAC,iBAAiB,IAAC,OAAO,EAAE,WAAW,IAAI,EAAE,GAAI,EAIjD,KAAC,aAAa,IAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,iBAAiB,GAAI,IACtE,EACN,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,GAAI,IAC9B,EAEN,KAAC,UAAU,IAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,GAAI,IACjF,GACmB,CAC5B,CAAA;AACH,CAAC","sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { AlertTriangle, Plus, Redo2, SlidersHorizontal, Undo2 } from 'lucide-react'\nimport { DEFAULT_SLOT, mergeBlockRoots, splitBlockRoots } from '@elytracms/core/project-graph'\nimport type { ComponentNode, PreservedSequenceItem, SplitBlockRoots } from '@elytracms/core/project-graph'\nimport { DRAFT_PERSPECTIVE, fieldAllowList } from '@elytracms/core/cms-core'\nimport type { BlocksFieldDef } from '@elytracms/core/cms-core'\nimport type { ComponentRegistry } from '@elytracms/core/component-registry'\nimport type { BlockNode, BlockSequenceValue } from '@elytracms/core/rich-text'\nimport { Button, Panel, Row, Spacer, Text, Toolbar, ToolbarGroup, ToolbarSeparator } from '@elytracms/studio/ui'\nimport { cn } from '../../lib/utils'\nimport { translate } from '../../lib/i18n'\nimport type { MessageKey } from '../../lib/i18n'\nimport { getStudioRegistry } from '../../lib/registry'\nimport { BuilderProvider, useBuilder } from './builder-store'\nimport type { CompositionFieldScope } from './builder-store'\nimport { createCanvasSync } from './canvas-rebase'\nimport type { CanvasFieldChange } from './canvas-rebase'\nimport { CanvasCommitProvider } from './canvas-commit-boundary'\nimport type { CanvasCommitChannel } from './canvas-commit-boundary'\nimport { useCanvasViewports } from './canvas-breakpoints'\nimport type { ViewportOption } from './canvas-breakpoints'\nimport { BuilderCanvas } from './canvas'\nimport { PageTree } from './page-tree'\nimport { PropertyInspector } from './property-inspector'\nimport { InspectorSidebar } from '../inspector/inspector-sidebar'\nimport type { InspectorPane } from '../inspector/inspector-sidebar'\nimport { InsertMenu } from './insert-menu'\nimport type { InsertContext } from './insert-menu'\nimport { insertablePositions, resolveInsertContext, resolveInsertContextAt } from './insert-context'\nimport type { InsertPosition } from './insert-context'\nimport { InsertAffordanceProvider } from './insert-affordance-context'\nimport type { InsertAffordanceApi } from './insert-affordance-context'\nimport { PropSyncProvider } from './prop-sync-context'\nimport { ReuseProvider } from './reuse-context'\nimport { PlacementRefusalNotice } from './placement-refusal'\nimport { fieldLabel } from '../cms/cms-labels'\n\n/**\n * EC-188 — the block-field surface: one canvas engine mounted on a composition\n * **field value** (AD-12), not the project graph.\n *\n * The composition tree is wrapped in a synthetic single-page graph and edited by\n * a SCOPED `BuilderProvider` with **no `projectId`** (so the graph persister is a\n * no-op — builder-store.tsx). Every committed edit is lifted out via `onChange`\n * into the CMS document draft; the document's own Save persists it (the tree is\n * stored as document data, `v.any()`).\n *\n * EC-547 (EC-500 D1–D3): the stored value is the converged v2 block sequence\n * `{ version: 2, content: BlockNode[] }`. The canvas edits the sequence's\n * `elytra/component` nodes; typography nodes survive the session byte-for-byte\n * and keep their order relative to components on write-back (see\n * {@link splitCompositionValue} / {@link mergeCompositionValue}). Legacy bare\n * node/array values are still ACCEPTED on read during the staging window, but\n * every write emits v2. This reuses the real canvas + inspector +\n * blocks panel unchanged — the same EC-158–164 mechanics — without touching the\n * project-wide builder store. Surfaced on a dedicated \"Canvas\" fieldset tab in\n * the CMS detail editor so the page builder gets full screen real estate rather\n * than being crammed into the field list.\n */\n\nconst SYNTH_ROOT_ID = 'composition.root'\n\n/**\n * A sequence item the canvas does not natively edit (EC-547 / EC-500 D3):\n * typography nodes, unknown/future node kinds, even entries the v2 schema would\n * reject — the editor is not a validator, so they are carried BYTE-FOR-BYTE and\n * re-merged on write-back rather than parsed, rewritten, or dropped.\n * `componentPrefix` records how many component nodes preceded the item in the\n * original sequence — the anchor {@link mergeCompositionValue} splices by.\n */\nexport type PreservedBlockNode = PreservedSequenceItem<BlockNode>\n\n/** A field value split for canvas editing: component roots + everything else. */\nexport type SplitCompositionValue = SplitBlockRoots<BlockNode>\n\n/**\n * Split a stored blocks-field value into the component nodes the canvas edits\n * and the sequence items it must preserve (EC-547, the EC-500 round-trip law:\n * a projection may only rewrite nodes it natively edits). Typography items are\n * deliberately NOT surfaced as editable canvas blocks in v1 (EC-500 D3) — they\n * survive the editing session and keep their order on write-back.\n *\n * EC-576: the logic is core's {@link splitBlockRoots} — the ONE reader every\n * blocks writer shares. This is the canvas's typed view of it: project-graph\n * sits below rich-text and cannot name {@link BlockNode}, so the carried items\n * arrive `unknown`; they are typed here, not checked (preservation over parsing).\n */\nexport function splitCompositionValue(value: unknown): SplitCompositionValue {\n return splitBlockRoots(value) as SplitCompositionValue\n}\n\n/**\n * Merge canvas-edited component roots back into the stored sequence (EC-547).\n * The merge rule — preserved items anchored to their ordinal position between\n * components, every root tagged, the write always v2 — is core's\n * {@link mergeBlockRoots} (EC-576: the same merge every blocks writer goes\n * through, via `editBlockRoots`). Typed here as the rich-text value.\n */\nexport function mergeCompositionValue(\n roots: readonly ComponentNode[],\n preserved: readonly PreservedBlockNode[],\n): BlockSequenceValue {\n return mergeBlockRoots(roots, preserved)\n}\n\n/**\n * The field scope the editor's store carries (EC-554): the field's label (named\n * by refusal notices) and its EFFECTIVE top-level vocabulary (EC-584 — read through\n * the per-FieldType definition, `fieldAllowList`: the declared `allow`, else the\n * `blocks` default, any canvas-eligible component).\n */\nexport function compositionFieldScope(field: BlocksFieldDef): CompositionFieldScope {\n // A blocks field always has a vocabulary (its type declares the default).\n return { label: fieldLabel(field), allow: fieldAllowList(field)! }\n}\n\n/**\n * Wrap the field value's component roots in a synthetic root container (EC-287).\n * The builder store edits this single root-bearing composition directly; its\n * children are the field's component blocks, lifted back out via `onChange`.\n * EC-591: the root is the component that fills the `layout.stack` role — the\n * project's own, from its component list — never a hardcoded engine id.\n */\nfunction buildSyntheticRoot(roots: readonly ComponentNode[], stackComponentId: string): ComponentNode {\n return {\n id: SYNTH_ROOT_ID,\n componentId: stackComponentId,\n slots: { [DEFAULT_SLOT]: [...roots] },\n }\n}\n\n/**\n * The canvas notices for roles nobody in the component list fills (EC-591) — each\n * names the gesture that is OFF because of it, so a missing role is visible, never\n * a silent dead affordance. Only gestures the canvas actually has are listed: the\n * `layout.stack` role is the canvas itself (see {@link CompositionFieldEditor}),\n * `layout.grid` the wrap-beside gesture, `text` the seeded layout presets.\n * (`richText` — the typography projection, EC-549 — and `image` — asset drop —\n * have no canvas gesture yet, so there is nothing to switch off.) Pure.\n */\nexport function canvasRoleNotices(registry: ComponentRegistry): MessageKey[] {\n const notices: MessageKey[] = []\n if (!registry.roleComponent('layout.grid')) notices.push('builder.roles.noGrid')\n if (!registry.roleComponent('text')) notices.push('builder.roles.noText')\n return notices\n}\n\n/** The role notices above the canvas (EC-591) — a standing truth about the component list. */\nexport function CanvasRoleNotices({ notices }: { notices: readonly MessageKey[] }) {\n if (notices.length === 0) return null\n return (\n <div data-testid=\"canvas-role-notices\" className=\"shrink-0 border-b border-border bg-muted/40\">\n {notices.map((key) => (\n <Row key={key} gap=\"2\" align=\"center\" className=\"px-3 py-1.5\">\n <AlertTriangle aria-hidden className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <Text size=\"xs\" tone=\"muted\">\n {translate(key)}\n </Text>\n </Row>\n ))}\n </div>\n )\n}\n\n/**\n * The canvas in place of the editor when nobody fills `layout.stack` (EC-591): the\n * blocks of a composition field compose inside that component, so there is no\n * canvas without it. The value is not touched — nothing mounts that could write.\n */\nexport function NoStackCanvasNotice() {\n return (\n <Panel data-testid=\"canvas-no-stack\" className=\"flex h-full min-h-0 items-center justify-center p-6\">\n <Row gap=\"2\" align=\"center\" className=\"max-w-prose\">\n <AlertTriangle aria-hidden className=\"size-4 shrink-0 text-muted-foreground\" />\n <Text size=\"sm\" tone=\"muted\">\n {translate('builder.roles.noStack')}\n </Text>\n </Row>\n </Panel>\n )\n}\n\nexport function CompositionFieldEditor({\n field,\n value,\n change,\n onChange,\n commits,\n readOnly,\n trailingPanes,\n conflicts,\n}: {\n field: BlocksFieldDef\n /** The field's value at mount (the store is the source of truth while open). */\n value: unknown\n /**\n * EC-630 — the latest change of the field made UNDERNEATH the canvas (the document\n * draft reports it); the store rebases onto it.\n */\n change?: CanvasFieldChange\n /**\n * The store's value, lifted, and the value it was last agreed on with the draft — the\n * base the canvas writer's delta is measured from.\n */\n onChange: (value: unknown, base: unknown) => void\n /**\n * EC-630 — the document's committer, for the inspector controls' commit boundaries\n * (focus / blur / Enter at a position): a burst of typing commits on its control kind's\n * policy, never per keystroke. Absent → every edit commits as the canvas writes it.\n */\n commits?: CanvasCommitChannel\n readOnly: boolean\n /**\n * EC-595 A2 — the field's per-field conflicts (a refused node patch: \"changed by X —\n * take theirs / keep mine\"), rendered above the canvas: the canvas IS the field.\n */\n conflicts?: ReactNode\n /** Inspector panes the document editor appends after the canvas-supplied\n * `Properties` pane (EC-224 — the frame-owned `Versions` pane). */\n trailingPanes?: readonly InspectorPane[]\n}) {\n // Split the value ONCE at mount (EC-547): component nodes become the canvas\n // roots; every other sequence item is preserved aside for write-back. The store\n // is the source of truth while open: it lifts its edits out via onChange, and a\n // value that changes UNDERNEATH it (EC-630 — a colleague's patch, a write's\n // canonical row) is REBASED into it by `CompositionSync`, never remounted.\n // (BuilderProvider reads `composition` only in its lazy initial state.)\n const initial = useMemo(() => splitCompositionValue(value), [])\n // EC-591: the roles come from the project's component list (the registry).\n const registry = getStudioRegistry().registry\n const stack = registry.roleComponent('layout.stack')\n const layout = registry.layout\n const notices = useMemo(() => canvasRoleNotices(registry), [registry])\n const initialRoot = useMemo(\n () => (stack ? buildSyntheticRoot(initial.roots, stack.id) : null),\n [initial, stack],\n )\n // EC-554: the field's `allow` travels WITH the store, so every placement\n // surface (canvas + tree DnD, paste, the insert offer) judges the same value.\n const fieldScope = useMemo(() => compositionFieldScope(field), [field])\n\n if (!initialRoot) return <NoStackCanvasNotice />\n\n return (\n <BuilderProvider\n composition={initialRoot}\n preserved={initial.preserved}\n field={fieldScope}\n components={registry.lookup}\n layout={layout}\n >\n <CompositionSync change={change} onChange={onChange} readOnly={readOnly} />\n <CanvasCommitProvider value={commits ?? null}>\n <ReuseProvider>\n {/* EC-408: the field↔component selection-sync channel wraps BOTH the canvas\n and the inspector (they reach each other through nothing else). */}\n <PropSyncProvider>\n <CompositionEditorShell\n readOnly={readOnly}\n trailingPanes={trailingPanes}\n roleNotices={notices}\n conflicts={conflicts}\n />\n </PropSyncProvider>\n </ReuseProvider>\n </CanvasCommitProvider>\n </BuilderProvider>\n )\n}\n\n/**\n * Keeps the scoped store and the CMS draft on ONE value, in both directions — the\n * React shell of {@link createCanvasSync} (EC-630).\n *\n * **Lift** (the store → the draft): opening the tab lifts nothing (the\n * container-normalized mount value is the first agreed value, so simply opening\n * never marks the document dirty); every later change to the block list OR to the\n * preserved typography (EC-549: a run deleted or edited, or an undo of either)\n * emits the merged v2 sequence value (EC-547) — the canvas roots spliced back\n * between the preserved typography items — with the value it was last agreed on,\n * which the draft measures the canvas writer's delta from. Cardinality\n * (`multiple`) no longer shapes the stored value: it is a validation constraint\n * over the one envelope (EC-500 D2).\n *\n * **Rebase** (the draft → the store): a change of the field made UNDERNEATH the\n * canvas (a colleague's patch by subscription, a write's canonical row, a resolved\n * conflict), reported by the draft as `change`, is dispatched as `rebase` — the\n * change lands, undo history and selection survive, undo never reverts it. This\n * replaced the remount (`canvasNonce` on every change of the value), which reset\n * both. The rebase effect is declared first: in one commit it runs before the lift.\n */\nfunction CompositionSync({\n change,\n onChange,\n readOnly,\n}: {\n change: CanvasFieldChange | undefined\n onChange: (value: unknown, base: unknown) => void\n readOnly: boolean\n}) {\n const builder = useBuilder()\n const blocks = builder.present.slots?.[DEFAULT_SLOT] ?? EMPTY_BLOCKS\n const preserved = builder.preserved as readonly PreservedBlockNode[]\n const rebased = builder.rebased\n const dispatch = builder.dispatch\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const [sync] = useState(() => createCanvasSync(mergeCompositionValue(blocks, preserved), change?.seq))\n\n useEffect(() => {\n // EC-226: a READ-ONLY canvas (the Published view) shows another perspective's\n // value — the draft's changes are not its own.\n if (readOnly || !change) return\n const action = sync.changed(change)\n if (action) dispatch(action)\n }, [change?.seq, readOnly])\n\n useEffect(() => {\n // EC-226: never lift edits out of a READ-ONLY canvas (the Published view).\n // The canvas's keyboard listeners (clipboard paste/cut) aren't blocked by the\n // visual `pointer-events-none`, so without this a Cmd+V could mutate the\n // graph; refusing to lift keeps the document draft intact regardless. The\n // editor remounts per perspective, so any transient in-canvas change is\n // discarded on switch.\n if (readOnly) return\n const lift = sync.stored(mergeCompositionValue(blocks, preserved), rebased)\n if (lift) onChangeRef.current(lift.value, lift.base)\n }, [blocks, preserved, rebased, readOnly])\n\n return null\n}\n\nconst EMPTY_BLOCKS: readonly ComponentNode[] = []\n\n/** A breakpoint preview-width toggle (EC-211/EC-250). Switching the viewport\n * resizes only the preview frame (not an undoable edit, writes no graph command).\n * The label is the breakpoint name (`Full`/`sm`/`md`/…) with the real px width in\n * the tooltip — so the switcher reflects the site's actual screens, not a fixed\n * desktop/tablet/phone triple. */\nfunction ViewportButton({ option }: { option: ViewportOption }) {\n const builder = useBuilder()\n const active = builder.viewport === option.key\n return (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.viewportPreviewAria', { label: option.label })}\n aria-pressed={active}\n title={\n option.px !== undefined\n ? `${option.label} · ${option.px}px`\n : translate('builder.canvas.viewportWidthTitle', { label: option.label })\n }\n onClick={() => builder.dispatch({ type: 'viewport', viewport: option.key })}\n className={cn('px-2 text-xs', active && 'bg-accent text-accent-foreground')}\n >\n {option.label}\n </Button>\n )\n}\n\n/** The centered breakpoint switcher (EC-250): the resolved viewport set, plus a\n * visible validation issue when a project declared breakpoints but none are usable. */\nfunction BreakpointSwitcher() {\n const { viewports, issue } = useCanvasViewports()\n return (\n <Row gap=\"1\" align=\"center\">\n <ToolbarGroup>\n {viewports.map((v) => (\n <ViewportButton key={v.key} option={v} />\n ))}\n </ToolbarGroup>\n {issue ? (\n <Row gap=\"1\" align=\"center\" className=\"text-amber-600 dark:text-amber-400\" title={issue}>\n <AlertTriangle aria-hidden className=\"size-3.5\" />\n <Text size=\"xs\">{translate('builder.canvas.breakpointsIssueLabel')}</Text>\n </Row>\n ) : null}\n </Row>\n )\n}\n\n/**\n * The slim editor surface — the canvas + layers + inspector, plus Insert and\n * undo/redo. It deliberately drops the project-builder *lifecycle* chrome\n * (publish, versions, AI, page SEO): the document owns the lifecycle, not the\n * field. It keeps spatial/preview controls — including the breakpoint\n * preview-width switcher (EC-211), a canvas concern, not a lifecycle one.\n */\nfunction CompositionEditorShell({\n readOnly,\n trailingPanes,\n roleNotices,\n conflicts,\n}: {\n readOnly: boolean\n /** EC-595 A2 — the field's conflict notices (see {@link CompositionFieldEditor}). */\n conflicts?: ReactNode\n /** Document-level panes appended after `Properties` (EC-224). */\n trailingPanes?: readonly InspectorPane[]\n /** Notices for the roles nobody fills (EC-591) — see {@link canvasRoleNotices}. */\n roleNotices?: readonly MessageKey[]\n}) {\n const builder = useBuilder()\n const dispatch = builder.dispatch\n const [insertContext, setInsertContext] = useState<InsertContext | null>(null)\n const [insertOpen, setInsertOpen] = useState(false)\n const rootId = builder.present.id\n\n // The canvas SUPPLIES the `Properties` pane (EC-224); the document editor\n // appends its own (`Versions`). Properties dims under read-only just as the\n // whole inspector column used to — but the appended panes stay interactive.\n const propertiesPane = useMemo<InspectorPane>(\n () => ({\n id: 'properties',\n label: translate('builder.inspector.propertiesPane'),\n icon: <SlidersHorizontal className=\"size-3.5\" />,\n render: () => (\n <div className={cn('h-full min-h-0 overflow-hidden', readOnly && 'pointer-events-none opacity-60')}>\n <PropertyInspector />\n </div>\n ),\n }),\n [readOnly],\n )\n const panes = useMemo<InspectorPane[]>(\n () => [propertiesPane, ...(trailingPanes ?? [])],\n [propertiesPane, trailingPanes],\n )\n\n // The single insert entry point for BOTH the toolbar and the page tree's\n // \"Add child\" (EC-383). It selects its target first so `builder.selectedId`\n // is the one source of truth for where an insert lands: the toolbar resolves\n // `builder.selectedId ?? rootId` while Add-child passes an explicit row, and\n // without this sync clicking Add-child (which otherwise leaves selection put)\n // could target a different node than the toolbar — surfacing as a mismatched\n // (even empty \"No allowed components match\") insert dialog. Both now resolve\n // the identical target + allow set for the same selection.\n function openInsert(nodeId: string) {\n // EC-584: the offer reads the store's placement scope — the field's vocabulary at\n // the composition root, each slot's below — the same scope the reducer judges by.\n const ctx = resolveInsertContext(builder, nodeId)\n if (!ctx) return\n if (nodeId !== builder.selectedId) dispatch({ type: 'select', id: nodeId })\n setInsertContext(ctx)\n setInsertOpen(true)\n }\n\n // EC-405: the in-canvas \"+\" affordances resolve the SAME slot rules through the\n // shared position-carrying insert-context resolution, and open the panel at the\n // clicked edge (an explicit position that survives to the insertion, rather than\n // the panel re-deriving it from the selection). Re-memoized on every store change\n // so the closures read the live graph.\n const affordanceApi = useMemo<InsertAffordanceApi>(\n () => ({\n positionsFor: (nodeId: string) => insertablePositions(builder, nodeId),\n openAt: (nodeId: string, position: InsertPosition) => {\n const ctx = resolveInsertContextAt(builder, nodeId, position)\n if (!ctx) return\n if (nodeId !== builder.selectedId) dispatch({ type: 'select', id: nodeId })\n setInsertContext(ctx)\n setInsertOpen(true)\n },\n }),\n [builder, dispatch],\n )\n\n return (\n <InsertAffordanceProvider value={affordanceApi}>\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 border-b border-border\">\n <Toolbar>\n <ToolbarGroup>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.undo')}\n title={translate('builder.canvas.undo')}\n disabled={readOnly || !builder.canUndo}\n onClick={() => dispatch({ type: 'undo' })}\n >\n <Undo2 />\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.canvas.redo')}\n title={translate('builder.canvas.redo')}\n disabled={readOnly || !builder.canRedo}\n onClick={() => dispatch({ type: 'redo' })}\n >\n <Redo2 />\n </Button>\n </ToolbarGroup>\n <ToolbarSeparator />\n <Button\n size=\"sm\"\n variant=\"ghost\"\n disabled={readOnly}\n onClick={() => openInsert(builder.selectedId ?? rootId)}\n >\n <Plus /> {translate('builder.canvas.insert')}\n </Button>\n {/* Breakpoint preview-width switcher (EC-211/EC-250), centered over the\n canvas via flanking spacers. A preview control, not an edit — stays\n active even in read-only published preview. */}\n <Spacer />\n <BreakpointSwitcher />\n <Spacer />\n </Toolbar>\n </div>\n\n <div className=\"grid min-h-0 flex-1 grid-cols-[minmax(200px,240px)_1fr_minmax(280px,320px)] gap-px bg-border\">\n <Panel className={cn('rounded-none border-0', readOnly && 'pointer-events-none opacity-60')}>\n <PageTree onAddChild={openInsert} />\n </Panel>\n <div className={cn('flex min-h-0 flex-col bg-background', readOnly && 'pointer-events-none opacity-60')}>\n {conflicts ? <div className=\"shrink-0 border-b border-border p-2\">{conflicts}</div> : null}\n {/* EC-584: a placement the reducer refused, with its reason — never a silent no-op. */}\n <PlacementRefusalNotice\n refusal={builder.refusal}\n fieldLabel={builder.field.label}\n onDismiss={() => dispatch({ type: 'clearRefusal' })}\n />\n <CanvasRoleNotices notices={roleNotices ?? []} />\n {/* EC-300/AD-16: the read-only Published view resolves references against\n the published perspective (honest preview); the editable Draft view\n resolves drafts (a just-made reusable shows immediately). */}\n <BuilderCanvas perspective={readOnly ? 'published' : DRAFT_PERSPECTIVE} />\n </div>\n <InspectorSidebar panes={panes} />\n </div>\n\n <InsertMenu context={insertContext} open={insertOpen} onOpenChange={setInsertOpen} />\n </div>\n </InsertAffordanceProvider>\n )\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { ComponentNode } from '@elytracms/core/project-graph';
2
- import type { DragSubject } from '@elytracms/studio/lib/dnd/drop-rules';
2
+ import type { DragSubject } from '../../lib/dnd/drop-rules.js';
3
3
  /**
4
4
  * The one live drag session (EC-160). HTML5 `dataTransfer` payloads are
5
5
  * unreadable during `dragover`, so the canvas, page tree, and blocks panel
@@ -1 +1 @@
1
- {"version":3,"file":"drag-session.js","sourceRoot":"","sources":["../../../src/components/builder/drag-session.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AAEH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,eAAe,GAAG,6BAA6B,CAAA;AAiB5D,IAAI,MAAM,GAAuB,IAAI,CAAA;AACrC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAe,CAAA;AAC3C,MAAM,cAAc,GAAG,IAAI,GAAG,EAAiB,CAAA;AAE/C,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,OAAoB,EAAE,YAAiC;IAC/E,MAAM,GAAG,OAAO,CAAA;IAChB,IAAI,YAAY,EAAE,CAAC;QACjB,YAAY,CAAC,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;QACpD,YAAY,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;QACjD,YAAY,CAAC,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;IAChF,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,cAAc,CAAC;QAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;AAC/D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU;IACxB,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,OAAoB;IAC1C,IAAI,CAAC,MAAM;QAAE,OAAM;IACnB,MAAM,OAAO,GAAG,MAAM,CAAA;IACtB,MAAM,GAAG,IAAI,CAAA;IACb,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,YAAY,CAAC;QAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;AACtE,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,gBAAgB,CAAC,QAAqB;IACpD,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC1B,OAAO,GAAG,EAAE;QACV,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC/B,CAAC,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAuB;IACxD,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC5B,OAAO,GAAG,EAAE;QACV,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACjC,CAAC,CAAA;AACH,CAAC","sourcesContent":["import type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { DragSubject } from '@elytracms/studio/lib/dnd/drop-rules'\n\n/**\n * The one live drag session (EC-160). HTML5 `dataTransfer` payloads are\n * unreadable during `dragover`, so the canvas, page tree, and blocks panel\n * share this module-level session instead: the source begins it, every\n * surface reads it to resolve drop eligibility (via the pure rules in\n * `lib/dnd/drop-rules`), and exactly one place ends it — the drop handler\n * (`dropped`) or the source's `dragend` (`cancelled`). `endDrag` is\n * idempotent, so the source's trailing `dragend` after a successful drop\n * cannot double-commit or double-notify.\n *\n * The iframe canvas shares the parent window's JS realm (listeners are wired\n * from the parent), so one module instance covers all surfaces.\n */\n\n/** Custom MIME marking Elytra drags (lets surfaces ignore foreign drags). */\nexport const ELYTRA_DND_MIME = 'application/x-elytra-canvas'\n\nexport type DragSource = 'canvas' | 'tree' | 'blocks'\nexport type DragOutcome = 'dropped' | 'cancelled'\n\nexport interface DragSession {\n subject: DragSubject\n source: DragSource\n /** Human label (drag text fallback). */\n label: string\n /** For `component` subjects: builds the subtree to insert (ids reassigned by the store). */\n buildNode?: () => ComponentNode\n}\n\ntype EndListener = (outcome: DragOutcome, session: DragSession) => void\ntype StartListener = (session: DragSession) => void\n\nlet active: DragSession | null = null\nconst endListeners = new Set<EndListener>()\nconst startListeners = new Set<StartListener>()\n\n/** Begin a drag session and stamp the dataTransfer (Firefox requires data to drag). */\nexport function beginDrag(session: DragSession, dataTransfer: DataTransfer | null): void {\n active = session\n if (dataTransfer) {\n dataTransfer.setData(ELYTRA_DND_MIME, session.label)\n dataTransfer.setData('text/plain', session.label)\n dataTransfer.effectAllowed = session.subject.kind === 'node' ? 'move' : 'copy'\n }\n for (const listener of [...startListeners]) listener(session)\n}\n\n/** The live session, or null when nothing Elytra-owned is being dragged. */\nexport function activeDrag(): DragSession | null {\n return active\n}\n\n/**\n * End the live session (no-op when none is active) and notify subscribers.\n * Call with `dropped` from the drop handler that dispatched the action, and\n * with `cancelled` from the source's `dragend`.\n */\nexport function endDrag(outcome: DragOutcome): void {\n if (!active) return\n const session = active\n active = null\n for (const listener of [...endListeners]) listener(outcome, session)\n}\n\n/** Subscribe to session end (indicator cleanup, blocks-panel close-on-drop). */\nexport function onDragSessionEnd(listener: EndListener): () => void {\n endListeners.add(listener)\n return () => {\n endListeners.delete(listener)\n }\n}\n\n/**\n * Subscribe to session start — fired for EVERY drag source (canvas node, tree\n * row, blocks-panel tile). The canvas insertion affordances (EC-405) use this to\n * hide while a drag is live: the DnD before/after/into indicators own the \"where\n * it lands\" story during a drag, so a competing \"+\" would be noise.\n */\nexport function onDragSessionStart(listener: StartListener): () => void {\n startListeners.add(listener)\n return () => {\n startListeners.delete(listener)\n }\n}\n"]}
1
+ {"version":3,"file":"drag-session.js","sourceRoot":"","sources":["../../../src/components/builder/drag-session.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AAEH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,eAAe,GAAG,6BAA6B,CAAA;AAiB5D,IAAI,MAAM,GAAuB,IAAI,CAAA;AACrC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAe,CAAA;AAC3C,MAAM,cAAc,GAAG,IAAI,GAAG,EAAiB,CAAA;AAE/C,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,OAAoB,EAAE,YAAiC;IAC/E,MAAM,GAAG,OAAO,CAAA;IAChB,IAAI,YAAY,EAAE,CAAC;QACjB,YAAY,CAAC,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;QACpD,YAAY,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;QACjD,YAAY,CAAC,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;IAChF,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,cAAc,CAAC;QAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;AAC/D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU;IACxB,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,OAAoB;IAC1C,IAAI,CAAC,MAAM;QAAE,OAAM;IACnB,MAAM,OAAO,GAAG,MAAM,CAAA;IACtB,MAAM,GAAG,IAAI,CAAA;IACb,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,YAAY,CAAC;QAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;AACtE,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,gBAAgB,CAAC,QAAqB;IACpD,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC1B,OAAO,GAAG,EAAE;QACV,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC/B,CAAC,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAuB;IACxD,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC5B,OAAO,GAAG,EAAE;QACV,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACjC,CAAC,CAAA;AACH,CAAC","sourcesContent":["import type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { DragSubject } from '../../lib/dnd/drop-rules'\n\n/**\n * The one live drag session (EC-160). HTML5 `dataTransfer` payloads are\n * unreadable during `dragover`, so the canvas, page tree, and blocks panel\n * share this module-level session instead: the source begins it, every\n * surface reads it to resolve drop eligibility (via the pure rules in\n * `lib/dnd/drop-rules`), and exactly one place ends it — the drop handler\n * (`dropped`) or the source's `dragend` (`cancelled`). `endDrag` is\n * idempotent, so the source's trailing `dragend` after a successful drop\n * cannot double-commit or double-notify.\n *\n * The iframe canvas shares the parent window's JS realm (listeners are wired\n * from the parent), so one module instance covers all surfaces.\n */\n\n/** Custom MIME marking Elytra drags (lets surfaces ignore foreign drags). */\nexport const ELYTRA_DND_MIME = 'application/x-elytra-canvas'\n\nexport type DragSource = 'canvas' | 'tree' | 'blocks'\nexport type DragOutcome = 'dropped' | 'cancelled'\n\nexport interface DragSession {\n subject: DragSubject\n source: DragSource\n /** Human label (drag text fallback). */\n label: string\n /** For `component` subjects: builds the subtree to insert (ids reassigned by the store). */\n buildNode?: () => ComponentNode\n}\n\ntype EndListener = (outcome: DragOutcome, session: DragSession) => void\ntype StartListener = (session: DragSession) => void\n\nlet active: DragSession | null = null\nconst endListeners = new Set<EndListener>()\nconst startListeners = new Set<StartListener>()\n\n/** Begin a drag session and stamp the dataTransfer (Firefox requires data to drag). */\nexport function beginDrag(session: DragSession, dataTransfer: DataTransfer | null): void {\n active = session\n if (dataTransfer) {\n dataTransfer.setData(ELYTRA_DND_MIME, session.label)\n dataTransfer.setData('text/plain', session.label)\n dataTransfer.effectAllowed = session.subject.kind === 'node' ? 'move' : 'copy'\n }\n for (const listener of [...startListeners]) listener(session)\n}\n\n/** The live session, or null when nothing Elytra-owned is being dragged. */\nexport function activeDrag(): DragSession | null {\n return active\n}\n\n/**\n * End the live session (no-op when none is active) and notify subscribers.\n * Call with `dropped` from the drop handler that dispatched the action, and\n * with `cancelled` from the source's `dragend`.\n */\nexport function endDrag(outcome: DragOutcome): void {\n if (!active) return\n const session = active\n active = null\n for (const listener of [...endListeners]) listener(outcome, session)\n}\n\n/** Subscribe to session end (indicator cleanup, blocks-panel close-on-drop). */\nexport function onDragSessionEnd(listener: EndListener): () => void {\n endListeners.add(listener)\n return () => {\n endListeners.delete(listener)\n }\n}\n\n/**\n * Subscribe to session start — fired for EVERY drag source (canvas node, tree\n * row, blocks-panel tile). The canvas insertion affordances (EC-405) use this to\n * hide while a drag is live: the DnD before/after/into indicators own the \"where\n * it lands\" story during a drag, so a competing \"+\" would be noise.\n */\nexport function onDragSessionStart(listener: StartListener): () => void {\n startListeners.add(listener)\n return () => {\n startListeners.delete(listener)\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { cn } from '@elytracms/studio/lib/utils';
2
+ import { cn } from '../../lib/utils.js';
3
3
  import { tileMonogram } from './blocks-model.js';
4
4
  /**
5
5
  * Blocks-panel tile previews (EC-439 AD-2): previews are ARTIFACTS, never
@@ -1 +1 @@
1
- {"version":3,"file":"fixture-thumbnail.js","sourceRoot":"","sources":["../../../src/components/builder/fixture-thumbnail.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE7C;;;;;;;;;;;;GAYG;AAEH,4FAA4F;AAC5F,MAAM,aAAa,GAAG,wDAAwD,CAAA;AAE9E;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,GAAG,EACH,GAAG,EACH,SAAS,GAOV;IACC,OAAO,CACL,gFAGE,SAAS,EAAE,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC,EACvC,KAAK,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,YAKjC,cACE,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,GAAG;YACR,sEAAsE;YACtE,8BAA8B;YAC9B,SAAS,EAAE,KAAK,EAChB,SAAS,EAAC,4DAA4D,GACtE,GACE,CACP,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAyC;IAC9F,OAAO,CACL,2GAIE,SAAS,EAAE,EAAE,CAAC,aAAa,EAAE,yBAAyB,EAAE,SAAS,CAAC,EAClE,KAAK,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,YAEjC,eAAM,SAAS,EAAC,6HAA6H,YAC1I,YAAY,CAAC,KAAK,CAAC,GACf,GACH,CACP,CAAA;AACH,CAAC","sourcesContent":["import { cn } from '@elytracms/studio/lib/utils'\nimport { tileMonogram } from './blocks-model'\n\n/**\n * Blocks-panel tile previews (EC-439 AD-2): previews are ARTIFACTS, never\n * runtime renders. A tile shows a curated `manifest.previewImage` (EC-409) or\n * an EC-440-generated fixture image through {@link PreviewImageThumbnail}, and\n * falls back to the deliberate icon+name placeholder\n * ({@link PlaceholderThumbnail}) when neither exists. The in-document live\n * fixture render (`ScaledFixtureThumbnail`, EC-164) is gone: since EC-414 the\n * consumer theme lives only in the canvas iframe, so no in-studio-document\n * render of consumer components can ever be styled correctly again.\n *\n * One frame per tile (AD-3): the tile's outer border is the ONLY border — the\n * preview inset is a borderless, tinted, rounded area.\n */\n\n/** The shared borderless 16/10 inset both preview tiers and the placeholder render into. */\nconst INSET_CLASSES = 'relative w-full overflow-hidden rounded-md bg-muted/40'\n\n/**\n * Preview-image tile (EC-409 curated / EC-440 generated): renders the resolved\n * `previewUrl` in the shared 16/10 inset, object-fit cover. The image is never\n * a fetch the engine performs, just an `<img>` with the consumer's URL string;\n * `alt` is the component title (already-localized manifest data).\n */\nexport function PreviewImageThumbnail({\n src,\n alt,\n className,\n}: {\n /** The resolved preview URL (curated `manifest.previewImage` or a generated artifact), passed through verbatim. */\n src: string\n /** The component title (localized manifest data — no new catalog string). */\n alt: string\n className?: string\n}) {\n return (\n <div\n data-block-thumbnail\n data-block-thumbnail-image\n className={cn(INSET_CLASSES, className)}\n style={{ aspectRatio: '16 / 10' }}\n >\n {/* Deliberately NOT loading=\"lazy\": native lazy images need rendering\n frames to trigger (occluded/headless windows never load them), and a\n tile preview is a small asset. */}\n <img\n src={src}\n alt={alt}\n // The surrounding tile is the drag source; a natively draggable <img>\n // would hijack the tile drag.\n draggable={false}\n className=\"pointer-events-none h-full w-full select-none object-cover\"\n />\n </div>\n )\n}\n\n/**\n * The icon+name placeholder (EC-439 AD-2, tier 3): a deliberate glyph\n * treatment — a centered two-letter monogram derived from the component title\n * in a muted disc — never an empty box and never a lying render. Decorative\n * only (`aria-hidden`): the tile itself carries the accessible name.\n */\nexport function PlaceholderThumbnail({ title, className }: { title: string; className?: string }) {\n return (\n <div\n aria-hidden\n data-block-thumbnail\n data-block-thumbnail-placeholder\n className={cn(INSET_CLASSES, 'grid place-items-center', className)}\n style={{ aspectRatio: '16 / 10' }}\n >\n <span className=\"grid size-10 select-none place-items-center rounded-full bg-muted text-sm font-semibold tracking-wide text-muted-foreground\">\n {tileMonogram(title)}\n </span>\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"fixture-thumbnail.js","sourceRoot":"","sources":["../../../src/components/builder/fixture-thumbnail.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE7C;;;;;;;;;;;;GAYG;AAEH,4FAA4F;AAC5F,MAAM,aAAa,GAAG,wDAAwD,CAAA;AAE9E;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,GAAG,EACH,GAAG,EACH,SAAS,GAOV;IACC,OAAO,CACL,gFAGE,SAAS,EAAE,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC,EACvC,KAAK,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,YAKjC,cACE,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,GAAG;YACR,sEAAsE;YACtE,8BAA8B;YAC9B,SAAS,EAAE,KAAK,EAChB,SAAS,EAAC,4DAA4D,GACtE,GACE,CACP,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAyC;IAC9F,OAAO,CACL,2GAIE,SAAS,EAAE,EAAE,CAAC,aAAa,EAAE,yBAAyB,EAAE,SAAS,CAAC,EAClE,KAAK,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,YAEjC,eAAM,SAAS,EAAC,6HAA6H,YAC1I,YAAY,CAAC,KAAK,CAAC,GACf,GACH,CACP,CAAA;AACH,CAAC","sourcesContent":["import { cn } from '../../lib/utils'\nimport { tileMonogram } from './blocks-model'\n\n/**\n * Blocks-panel tile previews (EC-439 AD-2): previews are ARTIFACTS, never\n * runtime renders. A tile shows a curated `manifest.previewImage` (EC-409) or\n * an EC-440-generated fixture image through {@link PreviewImageThumbnail}, and\n * falls back to the deliberate icon+name placeholder\n * ({@link PlaceholderThumbnail}) when neither exists. The in-document live\n * fixture render (`ScaledFixtureThumbnail`, EC-164) is gone: since EC-414 the\n * consumer theme lives only in the canvas iframe, so no in-studio-document\n * render of consumer components can ever be styled correctly again.\n *\n * One frame per tile (AD-3): the tile's outer border is the ONLY border — the\n * preview inset is a borderless, tinted, rounded area.\n */\n\n/** The shared borderless 16/10 inset both preview tiers and the placeholder render into. */\nconst INSET_CLASSES = 'relative w-full overflow-hidden rounded-md bg-muted/40'\n\n/**\n * Preview-image tile (EC-409 curated / EC-440 generated): renders the resolved\n * `previewUrl` in the shared 16/10 inset, object-fit cover. The image is never\n * a fetch the engine performs, just an `<img>` with the consumer's URL string;\n * `alt` is the component title (already-localized manifest data).\n */\nexport function PreviewImageThumbnail({\n src,\n alt,\n className,\n}: {\n /** The resolved preview URL (curated `manifest.previewImage` or a generated artifact), passed through verbatim. */\n src: string\n /** The component title (localized manifest data — no new catalog string). */\n alt: string\n className?: string\n}) {\n return (\n <div\n data-block-thumbnail\n data-block-thumbnail-image\n className={cn(INSET_CLASSES, className)}\n style={{ aspectRatio: '16 / 10' }}\n >\n {/* Deliberately NOT loading=\"lazy\": native lazy images need rendering\n frames to trigger (occluded/headless windows never load them), and a\n tile preview is a small asset. */}\n <img\n src={src}\n alt={alt}\n // The surrounding tile is the drag source; a natively draggable <img>\n // would hijack the tile drag.\n draggable={false}\n className=\"pointer-events-none h-full w-full select-none object-cover\"\n />\n </div>\n )\n}\n\n/**\n * The icon+name placeholder (EC-439 AD-2, tier 3): a deliberate glyph\n * treatment — a centered two-letter monogram derived from the component title\n * in a muted disc — never an empty box and never a lying render. Decorative\n * only (`aria-hidden`): the tile itself carries the accessible name.\n */\nexport function PlaceholderThumbnail({ title, className }: { title: string; className?: string }) {\n return (\n <div\n aria-hidden\n data-block-thumbnail\n data-block-thumbnail-placeholder\n className={cn(INSET_CLASSES, 'grid place-items-center', className)}\n style={{ aspectRatio: '16 / 10' }}\n >\n <span className=\"grid size-10 select-none place-items-center rounded-full bg-muted text-sm font-semibold tracking-wide text-muted-foreground\">\n {tileMonogram(title)}\n </span>\n </div>\n )\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { ComponentNode, PlacementScope } from '@elytracms/core/project-graph';
2
- import type { StudioRegistry } from '@elytracms/studio/lib/registry';
2
+ import type { StudioRegistry } from '../../lib/registry/index.js';
3
3
  import type { InsertContext } from './insert-menu.js';
4
4
  /**
5
5
  * Where an insert lands: the canvas insert-context resolution, shared by every
@@ -1,7 +1,7 @@
1
1
  import { allowAt, getNode, getParentInfo } from '@elytracms/core/project-graph';
2
- import { getStudioRegistry } from '@elytracms/studio/lib/registry';
3
- import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
4
- import { manifestRules, primaryInsertSlot } from '@elytracms/studio/lib/dnd/drop-rules';
2
+ import { getStudioRegistry } from '../../lib/registry/index.js';
3
+ import { componentDisplayName } from '../../lib/persona/labels.js';
4
+ import { manifestRules, primaryInsertSlot } from '../../lib/dnd/drop-rules.js';
5
5
  import { hasInsertableComponent } from './blocks-model.js';
6
6
  /**
7
7
  * The offer at a target: its EFFECTIVE vocabulary, read through the core judge's own
@@ -1 +1 @@
1
- {"version":3,"file":"insert-context.js","sourceRoot":"","sources":["../../../src/components/builder/insert-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { allowAt, getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode, InsertTarget, PlacementScope } from '@elytracms/core/project-graph'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { manifestRules, primaryInsertSlot } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { hasInsertableComponent } from './blocks-model'\nimport type { InsertContext } from './insert-menu'\n\n/**\n * Where an insert lands: the canvas insert-context resolution, shared by every\n * surface that mounts the canvas engine.\n *\n * Extracted from the retired standalone `BuilderShell` (EC-210) so the in-CMS\n * Canvas editor (`composition-field-editor.tsx`, EC-188/AD-12) can own it without\n * importing the dead shell. No UI here — pure graph/registry resolution.\n */\n\n/** What the resolution reads from the builder store: the tree and its placement scope (EC-584). */\nexport interface InsertScope {\n present: ComponentNode\n placement: PlacementScope\n}\n\n/**\n * The offer at a target: its EFFECTIVE vocabulary, read through the core judge's own\n * reader (`allowAt` — the composition field's at the composition root, keyed on node\n * identity, mirroring the top-level leash in `validateCompositionValue`; each slot's\n * own `allow` below), so the offer is exactly what the reducer admits (EC-584).\n * EC-544: a closed set that admits nothing is the \"No allowed components match\" state.\n */\nfunction contextAt(builder: InsertScope, target: InsertTarget, destinationLabel: string): InsertContext {\n return {\n target,\n allow: allowAt(builder.present, target.parentId, target.slot, builder.placement),\n destinationLabel,\n }\n}\n\n/**\n * Resolve where an insert lands: into the node's child slot, else as a sibling. The\n * offered vocabulary is the landing position's (see {@link contextAt}) — the SAME\n * reader the reducer, drag-and-drop and paste judge with, so the offer and the\n * placement can never disagree.\n */\nexport function resolveInsertContext(\n builder: InsertScope,\n nodeId: string,\n studio: StudioRegistry = getStudioRegistry(),\n): InsertContext | null {\n const root = builder.present\n const node = getNode(root, nodeId)\n if (!node) return null\n // EC-386: resolve against the merged slot vocabulary (`manifestRules` folds\n // blocks-prop regions in), through the SAME `primaryInsertSlot` rule a DnD\n // `into` uses — so a `project.Section.body`-style region receives the insert\n // instead of silently degrading to a sibling insert.\n const manifest = studio.registry.getManifest(node.componentId)\n const childSlot = primaryInsertSlot(manifestRules(manifest))\n if (childSlot) {\n return contextAt(\n builder,\n { parentId: nodeId, slot: childSlot.name },\n `${componentDisplayName(node.componentId, manifest)} · ${childSlot.name}`,\n )\n }\n return resolveSiblingContext(studio, builder, nodeId)\n}\n\n/** A destination's component label — its manifest title (EC-591: never a stripped id prefix). */\nfunction parentLabel(studio: StudioRegistry, componentId: string): string {\n return componentDisplayName(componentId, studio.registry.getManifest(componentId))\n}\n\n/** Sibling-or-root insert context for a node with no insertable child slot. */\nfunction resolveSiblingContext(studio: StudioRegistry, builder: InsertScope, nodeId: string): InsertContext {\n const info = getParentInfo(builder.present, nodeId)\n if (info) {\n // EC-386: the parent slot may be a blocks-prop region — its `allow` leash still\n // filters sibling inserts (the core lookup merges blocks props into the slots).\n return contextAt(\n builder,\n { parentId: info.parent.id, slot: info.slot, index: info.index + 1 },\n `${parentLabel(studio, info.parent.componentId)} · ${info.slot}`,\n )\n }\n return contextAt(builder, { parentId: nodeId, slot: 'children' }, 'root')\n}\n\n/**\n * An in-canvas insertion position relative to a reference node (EC-405) — the\n * DnD position vocabulary (`lib/dnd/drop-rules`), narrowed to the three the\n * insertion affordances offer: `before`/`after` a sibling, or `into` an (empty)\n * container. `left`/`right` (the wrap-into-columns gesture) are DnD-only.\n */\nexport type InsertPosition = 'before' | 'after' | 'into'\n\n/**\n * Resolve an `InsertContext` for an EXPLICIT position relative to `nodeId`\n * (EC-405) — the position-carrying twin of {@link resolveInsertContext}, so an\n * affordance click at a chosen edge lands the insert at exactly that spot rather\n * than the panel re-deriving it from the selection.\n *\n * The slot resolution reuses the shared DnD rules — `into` uses\n * {@link primaryInsertSlot} (the same slot a drop `into` picks; `null` when the\n * node accepts no children), `before`/`after` use the reference's parent slot at\n * `index`/`index + 1` — so this surface and drag-insert can never disagree about\n * where a position lands or what it allows (EC-584: the vocabulary is `allowAt`'s,\n * exactly as in `resolveInsertContext`).\n */\nexport function resolveInsertContextAt(\n builder: InsertScope,\n nodeId: string,\n position: InsertPosition,\n studio: StudioRegistry = getStudioRegistry(),\n): InsertContext | null {\n const root = builder.present\n const node = getNode(root, nodeId)\n if (!node) return null\n\n if (position === 'into') {\n // EC-386: `manifestRules` folds blocks-prop regions into the slot set, so\n // an affordance/drop `into` a Section-style body lands in that region.\n const manifest = studio.registry.getManifest(node.componentId)\n const slot = primaryInsertSlot(manifestRules(manifest))\n if (!slot) return null // the node accepts no children — no `into` position\n return contextAt(\n builder,\n { parentId: nodeId, slot: slot.name, index: (node.slots?.[slot.name] ?? []).length },\n `${componentDisplayName(node.componentId, manifest)} · ${slot.name}`,\n )\n }\n const info = getParentInfo(root, nodeId)\n if (!info) return null // the root has no siblings — no before/after position\n return contextAt(\n builder,\n { parentId: info.parent.id, slot: info.slot, index: info.index + (position === 'after' ? 1 : 0) },\n `${parentLabel(studio, info.parent.componentId)} · ${info.slot}`,\n )\n}\n\n/**\n * Which affordance positions are actually insertable at `nodeId` (EC-405 scope 3):\n * a position is offered only when it structurally exists AND at least one registry\n * component may land there (the SAME judge the tree/DnD, the blocks panel and the\n * reducer apply — {@link hasInsertableComponent} over the resolved `allow`), so a\n * blocked spot shows no dead button.\n *\n * An EMPTY, insertable container surfaces ONLY `into` — the meaningful action there\n * is \"add the first child\", and an empty container often renders zero-height, so\n * its own before/after edges are degenerate and would pile the three \"+\" buttons\n * onto one point. A filled container and every leaf get before/after instead (each\n * child already carries its own edge affordances, so a filled container needs no\n * center \"+\").\n */\nexport function insertablePositions(\n builder: InsertScope,\n nodeId: string,\n studio: StudioRegistry = getStudioRegistry(),\n): InsertPosition[] {\n // Empty insertable container → into only.\n const into = resolveInsertContextAt(builder, nodeId, 'into', studio)\n if (into && (into.target.index ?? 0) === 0 && hasInsertableComponent(studio, into.allow)) {\n return ['into']\n }\n // Otherwise the sibling edges that accept something.\n const out: InsertPosition[] = []\n for (const position of ['before', 'after'] as const) {\n const ctx = resolveInsertContextAt(builder, nodeId, position, studio)\n if (ctx && hasInsertableComponent(studio, ctx.allow)) out.push(position)\n }\n return out\n}\n"]}
1
+ {"version":3,"file":"insert-context.js","sourceRoot":"","sources":["../../../src/components/builder/insert-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { allowAt, getNode, getParentInfo } from '@elytracms/core/project-graph'\nimport type { ComponentNode, InsertTarget, PlacementScope } from '@elytracms/core/project-graph'\nimport { getStudioRegistry } from '../../lib/registry'\nimport type { StudioRegistry } from '../../lib/registry'\nimport { componentDisplayName } from '../../lib/persona/labels'\nimport { manifestRules, primaryInsertSlot } from '../../lib/dnd/drop-rules'\nimport { hasInsertableComponent } from './blocks-model'\nimport type { InsertContext } from './insert-menu'\n\n/**\n * Where an insert lands: the canvas insert-context resolution, shared by every\n * surface that mounts the canvas engine.\n *\n * Extracted from the retired standalone `BuilderShell` (EC-210) so the in-CMS\n * Canvas editor (`composition-field-editor.tsx`, EC-188/AD-12) can own it without\n * importing the dead shell. No UI here — pure graph/registry resolution.\n */\n\n/** What the resolution reads from the builder store: the tree and its placement scope (EC-584). */\nexport interface InsertScope {\n present: ComponentNode\n placement: PlacementScope\n}\n\n/**\n * The offer at a target: its EFFECTIVE vocabulary, read through the core judge's own\n * reader (`allowAt` — the composition field's at the composition root, keyed on node\n * identity, mirroring the top-level leash in `validateCompositionValue`; each slot's\n * own `allow` below), so the offer is exactly what the reducer admits (EC-584).\n * EC-544: a closed set that admits nothing is the \"No allowed components match\" state.\n */\nfunction contextAt(builder: InsertScope, target: InsertTarget, destinationLabel: string): InsertContext {\n return {\n target,\n allow: allowAt(builder.present, target.parentId, target.slot, builder.placement),\n destinationLabel,\n }\n}\n\n/**\n * Resolve where an insert lands: into the node's child slot, else as a sibling. The\n * offered vocabulary is the landing position's (see {@link contextAt}) — the SAME\n * reader the reducer, drag-and-drop and paste judge with, so the offer and the\n * placement can never disagree.\n */\nexport function resolveInsertContext(\n builder: InsertScope,\n nodeId: string,\n studio: StudioRegistry = getStudioRegistry(),\n): InsertContext | null {\n const root = builder.present\n const node = getNode(root, nodeId)\n if (!node) return null\n // EC-386: resolve against the merged slot vocabulary (`manifestRules` folds\n // blocks-prop regions in), through the SAME `primaryInsertSlot` rule a DnD\n // `into` uses — so a `project.Section.body`-style region receives the insert\n // instead of silently degrading to a sibling insert.\n const manifest = studio.registry.getManifest(node.componentId)\n const childSlot = primaryInsertSlot(manifestRules(manifest))\n if (childSlot) {\n return contextAt(\n builder,\n { parentId: nodeId, slot: childSlot.name },\n `${componentDisplayName(node.componentId, manifest)} · ${childSlot.name}`,\n )\n }\n return resolveSiblingContext(studio, builder, nodeId)\n}\n\n/** A destination's component label — its manifest title (EC-591: never a stripped id prefix). */\nfunction parentLabel(studio: StudioRegistry, componentId: string): string {\n return componentDisplayName(componentId, studio.registry.getManifest(componentId))\n}\n\n/** Sibling-or-root insert context for a node with no insertable child slot. */\nfunction resolveSiblingContext(studio: StudioRegistry, builder: InsertScope, nodeId: string): InsertContext {\n const info = getParentInfo(builder.present, nodeId)\n if (info) {\n // EC-386: the parent slot may be a blocks-prop region — its `allow` leash still\n // filters sibling inserts (the core lookup merges blocks props into the slots).\n return contextAt(\n builder,\n { parentId: info.parent.id, slot: info.slot, index: info.index + 1 },\n `${parentLabel(studio, info.parent.componentId)} · ${info.slot}`,\n )\n }\n return contextAt(builder, { parentId: nodeId, slot: 'children' }, 'root')\n}\n\n/**\n * An in-canvas insertion position relative to a reference node (EC-405) — the\n * DnD position vocabulary (`lib/dnd/drop-rules`), narrowed to the three the\n * insertion affordances offer: `before`/`after` a sibling, or `into` an (empty)\n * container. `left`/`right` (the wrap-into-columns gesture) are DnD-only.\n */\nexport type InsertPosition = 'before' | 'after' | 'into'\n\n/**\n * Resolve an `InsertContext` for an EXPLICIT position relative to `nodeId`\n * (EC-405) — the position-carrying twin of {@link resolveInsertContext}, so an\n * affordance click at a chosen edge lands the insert at exactly that spot rather\n * than the panel re-deriving it from the selection.\n *\n * The slot resolution reuses the shared DnD rules — `into` uses\n * {@link primaryInsertSlot} (the same slot a drop `into` picks; `null` when the\n * node accepts no children), `before`/`after` use the reference's parent slot at\n * `index`/`index + 1` — so this surface and drag-insert can never disagree about\n * where a position lands or what it allows (EC-584: the vocabulary is `allowAt`'s,\n * exactly as in `resolveInsertContext`).\n */\nexport function resolveInsertContextAt(\n builder: InsertScope,\n nodeId: string,\n position: InsertPosition,\n studio: StudioRegistry = getStudioRegistry(),\n): InsertContext | null {\n const root = builder.present\n const node = getNode(root, nodeId)\n if (!node) return null\n\n if (position === 'into') {\n // EC-386: `manifestRules` folds blocks-prop regions into the slot set, so\n // an affordance/drop `into` a Section-style body lands in that region.\n const manifest = studio.registry.getManifest(node.componentId)\n const slot = primaryInsertSlot(manifestRules(manifest))\n if (!slot) return null // the node accepts no children — no `into` position\n return contextAt(\n builder,\n { parentId: nodeId, slot: slot.name, index: (node.slots?.[slot.name] ?? []).length },\n `${componentDisplayName(node.componentId, manifest)} · ${slot.name}`,\n )\n }\n const info = getParentInfo(root, nodeId)\n if (!info) return null // the root has no siblings — no before/after position\n return contextAt(\n builder,\n { parentId: info.parent.id, slot: info.slot, index: info.index + (position === 'after' ? 1 : 0) },\n `${parentLabel(studio, info.parent.componentId)} · ${info.slot}`,\n )\n}\n\n/**\n * Which affordance positions are actually insertable at `nodeId` (EC-405 scope 3):\n * a position is offered only when it structurally exists AND at least one registry\n * component may land there (the SAME judge the tree/DnD, the blocks panel and the\n * reducer apply — {@link hasInsertableComponent} over the resolved `allow`), so a\n * blocked spot shows no dead button.\n *\n * An EMPTY, insertable container surfaces ONLY `into` — the meaningful action there\n * is \"add the first child\", and an empty container often renders zero-height, so\n * its own before/after edges are degenerate and would pile the three \"+\" buttons\n * onto one point. A filled container and every leaf get before/after instead (each\n * child already carries its own edge affordances, so a filled container needs no\n * center \"+\").\n */\nexport function insertablePositions(\n builder: InsertScope,\n nodeId: string,\n studio: StudioRegistry = getStudioRegistry(),\n): InsertPosition[] {\n // Empty insertable container → into only.\n const into = resolveInsertContextAt(builder, nodeId, 'into', studio)\n if (into && (into.target.index ?? 0) === 0 && hasInsertableComponent(studio, into.allow)) {\n return ['into']\n }\n // Otherwise the sibling edges that accept something.\n const out: InsertPosition[] = []\n for (const position of ['before', 'after'] as const) {\n const ctx = resolveInsertContextAt(builder, nodeId, position, studio)\n if (ctx && hasInsertableComponent(studio, ctx.allow)) out.push(position)\n }\n return out\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { CONTAINER_ALIGNMENTS, GRID_MAX_COLUMNS, SPACING_TOKEN_NAMES, STACK_ON_MOBILE_PROP, gridStacksOnMobile, isLayoutContainerNode, layoutContainerKind, isSpacingTokenName, isStaticProp, spacingTokenFromLegacyGap, staticProp, } from '@elytracms/core/project-graph';
3
3
  import { Field, InspectorSection, Select, Stack } from '@elytracms/studio/ui';
4
- import { translate } from '@elytracms/studio/lib/i18n';
4
+ import { translate } from '../../lib/i18n/index.js';
5
5
  import { useBuilder } from './builder-store.js';
6
6
  /**
7
7
  * Contextual layout inspector (EC-162): when a `Stack`/`Grid` container is
@@ -1 +1 @@
1
- {"version":3,"file":"layout-inspector.js","sourceRoot":"","sources":["../../../src/components/builder/layout-inspector.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n CONTAINER_ALIGNMENTS,\n GRID_MAX_COLUMNS,\n SPACING_TOKEN_NAMES,\n STACK_ON_MOBILE_PROP,\n gridStacksOnMobile,\n isLayoutContainerNode,\n layoutContainerKind,\n isSpacingTokenName,\n isStaticProp,\n spacingTokenFromLegacyGap,\n staticProp,\n} from '@elytracms/core/project-graph'\nimport type {\n ComponentNode,\n ContainerAlignment,\n LayoutContainerIds,\n JsonValue,\n SpacingTokenName,\n StackDirection,\n} from '@elytracms/core/project-graph'\nimport { Field, InspectorSection, Select, Stack } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { useBuilder } from './builder-store'\n\n/**\n * Contextual layout inspector (EC-162): when a `Stack`/`Grid` container is\n * selected, the Settings tab shows its canonical layout values — direction,\n * gap, alignment, columns — as plain-language controls bound to the EC-161\n * token scale. Gesture creates, this refines: every change is one ordinary\n * `setProp`/`removeProp` builder dispatch (one operations command, one undo\n * step). No CSS vocabulary leaks into the labels.\n */\n\n/** The props this section owns for a container (hidden from the generic Props list). */\nconst LAYOUT_PROPS = ['direction', 'gap', 'align', 'columns', STACK_ON_MOBILE_PROP] as const\n\nexport function layoutManagedPropNames(node: ComponentNode, layout: LayoutContainerIds): readonly string[] {\n return isLayoutContainerNode(node, layout) ? LAYOUT_PROPS : []\n}\n\n/**\n * i18n message key per spacing token step (EC-161 scale) — no CSS lengths.\n * Shared with the canvas gap handles (EC-163): the live drag label and this\n * select resolve the same catalog message for the same token.\n */\nexport const GAP_LABEL_KEYS: Record<SpacingTokenName, string> = {\n px: 'builder.layout.gapStep.hairline',\n '1': 'builder.layout.gapStep.extraSmall',\n '2': 'builder.layout.gapStep.small',\n '3': 'builder.layout.gapStep.medium',\n '4': 'builder.layout.gapStep.large',\n '6': 'builder.layout.gapStep.extraLarge',\n '8': 'builder.layout.gapStep.huge',\n}\n\n/** i18n message key for the \"no gap\" step (gap prop absent). */\nexport const GAP_NONE_LABEL_KEY = 'common.none'\n\nconst GAP_NONE = 'none'\n\nconst ALIGN_LABEL_KEYS: Record<ContainerAlignment, string> = {\n start: 'builder.layout.align.start',\n center: 'builder.layout.align.center',\n end: 'builder.layout.align.end',\n stretch: 'builder.layout.align.stretch',\n}\n\nfunction staticValue(node: ComponentNode, name: string): unknown {\n const prop = node.props?.[name]\n return isStaticProp(prop) ? prop.value : undefined\n}\n\n/** The node's gap as a token step, or `'none'` when absent/off-scale (renders gapless/default). */\nfunction currentGapToken(node: ComponentNode): SpacingTokenName | typeof GAP_NONE {\n const value = staticValue(node, 'gap')\n if (typeof value === 'string' && isSpacingTokenName(value)) return value\n if (typeof value === 'number') {\n const token = spacingTokenFromLegacyGap(value)\n if (token) return token\n }\n return GAP_NONE\n}\n\nfunction currentDirection(node: ComponentNode): StackDirection {\n return staticValue(node, 'direction') === 'horizontal' ? 'horizontal' : 'vertical'\n}\n\nfunction currentAlignment(node: ComponentNode): ContainerAlignment {\n const value = staticValue(node, 'align')\n return typeof value === 'string' && value in ALIGN_LABEL_KEYS ? (value as ContainerAlignment) : 'stretch'\n}\n\n/** The rendered column count: a valid static value, else 1 (the renderer's fallback). */\nfunction currentColumns(node: ComponentNode): number {\n const value = staticValue(node, 'columns')\n return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= GRID_MAX_COLUMNS\n ? value\n : 1\n}\n\nconst COLUMN_OPTIONS = Array.from({ length: GRID_MAX_COLUMNS }, (_, i) => i + 1)\n\n/** The contextual layout section. Renders nothing for non-container nodes. */\nexport function LayoutSection({ node }: { node: ComponentNode }) {\n const builder = useBuilder()\n // EC-591: a layout container is whichever component fills a layout role.\n const kind = layoutContainerKind(node.componentId, builder.layout)\n if (!kind) return null\n const isStack = kind === 'stack'\n\n const set = (name: string, value: JsonValue) =>\n builder.dispatch({ type: 'setProp', id: node.id, name, value: staticProp(value) })\n const clear = (name: string) => builder.dispatch({ type: 'removeProp', id: node.id, name })\n\n return (\n <InspectorSection title={translate('builder.layout.title')}>\n <Stack gap=\"3\">\n {isStack ? (\n <Field label={translate('builder.layout.direction')} htmlFor={`layout-${node.id}-direction`}>\n <Select\n id={`layout-${node.id}-direction`}\n value={currentDirection(node)}\n onChange={(e) => set('direction', e.target.value)}\n >\n <option value=\"vertical\">{translate('builder.layout.vertical')}</option>\n <option value=\"horizontal\">{translate('builder.layout.horizontal')}</option>\n </Select>\n </Field>\n ) : (\n <Field label={translate('builder.layout.columns')} htmlFor={`layout-${node.id}-columns`}>\n <Select\n id={`layout-${node.id}-columns`}\n value={String(currentColumns(node))}\n onChange={(e) => set('columns', Number(e.target.value))}\n >\n {COLUMN_OPTIONS.map((n) => (\n <option key={n} value={String(n)}>\n {translate(\n n === 1 ? 'builder.layout.columnCount.one' : 'builder.layout.columnCount.other',\n { count: n },\n )}\n </option>\n ))}\n </Select>\n </Field>\n )}\n\n <Field label={translate('builder.layout.gap')} htmlFor={`layout-${node.id}-gap`}>\n <Select\n id={`layout-${node.id}-gap`}\n value={currentGapToken(node)}\n onChange={(e) => (e.target.value === GAP_NONE ? clear('gap') : set('gap', e.target.value))}\n >\n <option value={GAP_NONE}>{translate(GAP_NONE_LABEL_KEY)}</option>\n {SPACING_TOKEN_NAMES.map((name) => (\n <option key={name} value={name}>\n {translate(GAP_LABEL_KEYS[name])}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field label={translate('builder.layout.alignment')} htmlFor={`layout-${node.id}-align`}>\n <Select\n id={`layout-${node.id}-align`}\n value={currentAlignment(node)}\n onChange={(e) => set('align', e.target.value)}\n >\n {CONTAINER_ALIGNMENTS.map((name) => (\n <option key={name} value={name}>\n {translate(ALIGN_LABEL_KEYS[name])}\n </option>\n ))}\n </Select>\n </Field>\n\n {/* Responsive default (EC-163): grids stack on phones unless the editor\n explicitly opts out. \"Stack\" is the default (prop absent); \"Keep\n columns\" writes the explicit `stackOnMobile: false` override. */}\n {!isStack ? (\n <Field label={translate('builder.layout.onPhones')} htmlFor={`layout-${node.id}-mobile`}>\n <Select\n id={`layout-${node.id}-mobile`}\n value={gridStacksOnMobile(node, builder.layout) ? 'stack' : 'keep'}\n onChange={(e) =>\n e.target.value === 'keep'\n ? set(STACK_ON_MOBILE_PROP, false)\n : clear(STACK_ON_MOBILE_PROP)\n }\n >\n <option value=\"stack\">{translate('builder.layout.stack')}</option>\n <option value=\"keep\">{translate('builder.layout.keepColumns')}</option>\n </Select>\n </Field>\n ) : null}\n </Stack>\n </InspectorSection>\n )\n}\n"]}
1
+ {"version":3,"file":"layout-inspector.js","sourceRoot":"","sources":["../../../src/components/builder/layout-inspector.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n CONTAINER_ALIGNMENTS,\n GRID_MAX_COLUMNS,\n SPACING_TOKEN_NAMES,\n STACK_ON_MOBILE_PROP,\n gridStacksOnMobile,\n isLayoutContainerNode,\n layoutContainerKind,\n isSpacingTokenName,\n isStaticProp,\n spacingTokenFromLegacyGap,\n staticProp,\n} from '@elytracms/core/project-graph'\nimport type {\n ComponentNode,\n ContainerAlignment,\n LayoutContainerIds,\n JsonValue,\n SpacingTokenName,\n StackDirection,\n} from '@elytracms/core/project-graph'\nimport { Field, InspectorSection, Select, Stack } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\nimport { useBuilder } from './builder-store'\n\n/**\n * Contextual layout inspector (EC-162): when a `Stack`/`Grid` container is\n * selected, the Settings tab shows its canonical layout values — direction,\n * gap, alignment, columns — as plain-language controls bound to the EC-161\n * token scale. Gesture creates, this refines: every change is one ordinary\n * `setProp`/`removeProp` builder dispatch (one operations command, one undo\n * step). No CSS vocabulary leaks into the labels.\n */\n\n/** The props this section owns for a container (hidden from the generic Props list). */\nconst LAYOUT_PROPS = ['direction', 'gap', 'align', 'columns', STACK_ON_MOBILE_PROP] as const\n\nexport function layoutManagedPropNames(node: ComponentNode, layout: LayoutContainerIds): readonly string[] {\n return isLayoutContainerNode(node, layout) ? LAYOUT_PROPS : []\n}\n\n/**\n * i18n message key per spacing token step (EC-161 scale) — no CSS lengths.\n * Shared with the canvas gap handles (EC-163): the live drag label and this\n * select resolve the same catalog message for the same token.\n */\nexport const GAP_LABEL_KEYS: Record<SpacingTokenName, string> = {\n px: 'builder.layout.gapStep.hairline',\n '1': 'builder.layout.gapStep.extraSmall',\n '2': 'builder.layout.gapStep.small',\n '3': 'builder.layout.gapStep.medium',\n '4': 'builder.layout.gapStep.large',\n '6': 'builder.layout.gapStep.extraLarge',\n '8': 'builder.layout.gapStep.huge',\n}\n\n/** i18n message key for the \"no gap\" step (gap prop absent). */\nexport const GAP_NONE_LABEL_KEY = 'common.none'\n\nconst GAP_NONE = 'none'\n\nconst ALIGN_LABEL_KEYS: Record<ContainerAlignment, string> = {\n start: 'builder.layout.align.start',\n center: 'builder.layout.align.center',\n end: 'builder.layout.align.end',\n stretch: 'builder.layout.align.stretch',\n}\n\nfunction staticValue(node: ComponentNode, name: string): unknown {\n const prop = node.props?.[name]\n return isStaticProp(prop) ? prop.value : undefined\n}\n\n/** The node's gap as a token step, or `'none'` when absent/off-scale (renders gapless/default). */\nfunction currentGapToken(node: ComponentNode): SpacingTokenName | typeof GAP_NONE {\n const value = staticValue(node, 'gap')\n if (typeof value === 'string' && isSpacingTokenName(value)) return value\n if (typeof value === 'number') {\n const token = spacingTokenFromLegacyGap(value)\n if (token) return token\n }\n return GAP_NONE\n}\n\nfunction currentDirection(node: ComponentNode): StackDirection {\n return staticValue(node, 'direction') === 'horizontal' ? 'horizontal' : 'vertical'\n}\n\nfunction currentAlignment(node: ComponentNode): ContainerAlignment {\n const value = staticValue(node, 'align')\n return typeof value === 'string' && value in ALIGN_LABEL_KEYS ? (value as ContainerAlignment) : 'stretch'\n}\n\n/** The rendered column count: a valid static value, else 1 (the renderer's fallback). */\nfunction currentColumns(node: ComponentNode): number {\n const value = staticValue(node, 'columns')\n return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= GRID_MAX_COLUMNS\n ? value\n : 1\n}\n\nconst COLUMN_OPTIONS = Array.from({ length: GRID_MAX_COLUMNS }, (_, i) => i + 1)\n\n/** The contextual layout section. Renders nothing for non-container nodes. */\nexport function LayoutSection({ node }: { node: ComponentNode }) {\n const builder = useBuilder()\n // EC-591: a layout container is whichever component fills a layout role.\n const kind = layoutContainerKind(node.componentId, builder.layout)\n if (!kind) return null\n const isStack = kind === 'stack'\n\n const set = (name: string, value: JsonValue) =>\n builder.dispatch({ type: 'setProp', id: node.id, name, value: staticProp(value) })\n const clear = (name: string) => builder.dispatch({ type: 'removeProp', id: node.id, name })\n\n return (\n <InspectorSection title={translate('builder.layout.title')}>\n <Stack gap=\"3\">\n {isStack ? (\n <Field label={translate('builder.layout.direction')} htmlFor={`layout-${node.id}-direction`}>\n <Select\n id={`layout-${node.id}-direction`}\n value={currentDirection(node)}\n onChange={(e) => set('direction', e.target.value)}\n >\n <option value=\"vertical\">{translate('builder.layout.vertical')}</option>\n <option value=\"horizontal\">{translate('builder.layout.horizontal')}</option>\n </Select>\n </Field>\n ) : (\n <Field label={translate('builder.layout.columns')} htmlFor={`layout-${node.id}-columns`}>\n <Select\n id={`layout-${node.id}-columns`}\n value={String(currentColumns(node))}\n onChange={(e) => set('columns', Number(e.target.value))}\n >\n {COLUMN_OPTIONS.map((n) => (\n <option key={n} value={String(n)}>\n {translate(\n n === 1 ? 'builder.layout.columnCount.one' : 'builder.layout.columnCount.other',\n { count: n },\n )}\n </option>\n ))}\n </Select>\n </Field>\n )}\n\n <Field label={translate('builder.layout.gap')} htmlFor={`layout-${node.id}-gap`}>\n <Select\n id={`layout-${node.id}-gap`}\n value={currentGapToken(node)}\n onChange={(e) => (e.target.value === GAP_NONE ? clear('gap') : set('gap', e.target.value))}\n >\n <option value={GAP_NONE}>{translate(GAP_NONE_LABEL_KEY)}</option>\n {SPACING_TOKEN_NAMES.map((name) => (\n <option key={name} value={name}>\n {translate(GAP_LABEL_KEYS[name])}\n </option>\n ))}\n </Select>\n </Field>\n\n <Field label={translate('builder.layout.alignment')} htmlFor={`layout-${node.id}-align`}>\n <Select\n id={`layout-${node.id}-align`}\n value={currentAlignment(node)}\n onChange={(e) => set('align', e.target.value)}\n >\n {CONTAINER_ALIGNMENTS.map((name) => (\n <option key={name} value={name}>\n {translate(ALIGN_LABEL_KEYS[name])}\n </option>\n ))}\n </Select>\n </Field>\n\n {/* Responsive default (EC-163): grids stack on phones unless the editor\n explicitly opts out. \"Stack\" is the default (prop absent); \"Keep\n columns\" writes the explicit `stackOnMobile: false` override. */}\n {!isStack ? (\n <Field label={translate('builder.layout.onPhones')} htmlFor={`layout-${node.id}-mobile`}>\n <Select\n id={`layout-${node.id}-mobile`}\n value={gridStacksOnMobile(node, builder.layout) ? 'stack' : 'keep'}\n onChange={(e) =>\n e.target.value === 'keep'\n ? set(STACK_ON_MOBILE_PROP, false)\n : clear(STACK_ON_MOBILE_PROP)\n }\n >\n <option value=\"stack\">{translate('builder.layout.stack')}</option>\n <option value=\"keep\">{translate('builder.layout.keepColumns')}</option>\n </Select>\n </Field>\n ) : null}\n </Stack>\n </InspectorSection>\n )\n}\n"]}
@@ -21,6 +21,14 @@ import { componentBlockNode } from '@elytracms/core/rich-text';
21
21
  export function nodeFromManifest(manifest) {
22
22
  return componentBlockNode({ id: 'tmp', componentId: manifest.id, props: {} });
23
23
  }
24
+ /**
25
+ * A fresh layout container's layout — the presets' (and so the Default tiles') own
26
+ * explicit values, the ones the wrap gesture writes too (`lib/dnd` `WRAP_GRID_OPTIONS`).
27
+ * EC-624: valid tokens; a container's absent gap means "None", so a fresh one says what
28
+ * it wants.
29
+ */
30
+ const PRESET_GAP = '4';
31
+ const PRESET_GRID_COLUMNS = 2;
24
32
  /** The display title of a role-filling component (its manifest title, else its id). */
25
33
  function titleOf(manifest) {
26
34
  return localizedStringVariants(manifest.title)[0] ?? manifest.id;
@@ -47,10 +55,14 @@ export function layoutPresets(registry) {
47
55
  key: 'stack',
48
56
  label: titleOf(stack),
49
57
  description: 'Vertical flex layout',
50
- build: () => componentBlockNode({
58
+ build: () =>
59
+ // EC-624: the preset's own layout, as a valid token — it stored a legacy numeric
60
+ // `gap: 4` the token select refuses at publish. Not a manifest default (the gap
61
+ // has none: absent is the canvas's "None" step); the wrap gesture writes the same.
62
+ componentBlockNode({
51
63
  id: 'tmp',
52
64
  componentId: stack.id,
53
- props: { gap: staticProp(4) },
65
+ props: { gap: staticProp(PRESET_GAP) },
54
66
  slots: { children: [copy('First item'), copy('Second item')] },
55
67
  }),
56
68
  });
@@ -64,7 +76,7 @@ export function layoutPresets(registry) {
64
76
  build: () => componentBlockNode({
65
77
  id: 'tmp',
66
78
  componentId: grid.id,
67
- props: { columns: staticProp(2), gap: staticProp(4) },
79
+ props: { columns: staticProp(PRESET_GRID_COLUMNS), gap: staticProp(PRESET_GAP) },
68
80
  slots: { children: [copy('Cell one'), copy('Cell two')] },
69
81
  }),
70
82
  });
@@ -1 +1 @@
1
- {"version":3,"file":"node-factory.js","sourceRoot":"","sources":["../../../src/components/builder/node-factory.ts"],"names":[],"mappings":"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","sourcesContent":["import { layoutContainerKind, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, ComponentRegistry } from '@elytracms/core/component-registry'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { componentBlockNode } from '@elytracms/core/rich-text'\n\n/**\n * Build a fresh node for a component (brief §8.2, EC-066). The placeholder id is\n * reassigned by the Builder store on insert. Required slots are left empty so\n * the renderer shows a missing-slot placeholder (EC-063) prompting the author to\n * fill them.\n *\n * EC-591 / ratified 2f: the node carries NO props. An absent prop is `undefined`\n * and the implementation's own default applies — on the canvas and on delivery\n * alike; the manifest `default` is an authoring hint (the inspector's greyed\n * placeholder), never baked into a stored node. (Until EC-591 every manifest\n * `default` was copied in here, so an insert and a \"copy exact configuration\"\n * paste of the same fixture stored — and rendered — different nodes.)\n *\n * EC-547 (EC-500 D1): every factory node goes through `componentBlockNode`, so\n * it carries the `elytra/component` discriminator from birth — a newly created\n * canvas node is a v2 block-sequence citizen without any tagging on write.\n */\nexport function nodeFromManifest(manifest: ComponentManifest): ComponentNode {\n return componentBlockNode({ id: 'tmp', componentId: manifest.id, props: {} })\n}\n\n/** Layout helper presets (brief §8.2, EC-067) — seeded layout containers. */\nexport interface LayoutPreset {\n key: string\n label: string\n description: string\n build: () => ComponentNode\n}\n\n/** The display title of a role-filling component (its manifest title, else its id). */\nfunction titleOf(manifest: ComponentManifest): string {\n return localizedStringVariants(manifest.title)[0] ?? manifest.id\n}\n\n/**\n * The layout presets the component LIST supports (EC-591): a seeded `Stack` and a\n * seeded `Grid` — built from whichever components fill the `layout.stack` /\n * `layout.grid` roles, seeded with the `text`-role component (a preset needs ≥2\n * children to survive EC-161 normalization, EC-383). No hardcoded engine ids: a\n * role nobody fills means its preset is not offered (the canvas notice says why),\n * and with no `text` component there is no seed copy, so no preset at all.\n * Container, Repeater and Switch are plain components — the blocks panel offers\n * them as their own tiles, from their manifests.\n */\nexport function layoutPresets(registry: ComponentRegistry): LayoutPreset[] {\n const text = registry.roleComponent('text')\n if (!text) return []\n const copy = (value: string): ComponentNode =>\n componentBlockNode({ id: 'tmp', componentId: text.id, props: { value: staticProp(value) } })\n const presets: LayoutPreset[] = []\n const stack = registry.roleComponent('layout.stack')\n if (stack) {\n presets.push({\n key: 'stack',\n label: titleOf(stack),\n description: 'Vertical flex layout',\n build: () =>\n componentBlockNode({\n id: 'tmp',\n componentId: stack.id,\n props: { gap: staticProp(4) },\n slots: { children: [copy('First item'), copy('Second item')] },\n }),\n })\n }\n const grid = registry.roleComponent('layout.grid')\n if (grid) {\n presets.push({\n key: 'grid',\n label: titleOf(grid),\n description: 'Multi-column grid',\n build: () =>\n componentBlockNode({\n id: 'tmp',\n componentId: grid.id,\n props: { columns: staticProp(2), gap: staticProp(4) },\n slots: { children: [copy('Cell one'), copy('Cell two')] },\n }),\n })\n }\n return presets\n}\n\n/**\n * Build a fresh node for insertion, seeding layout containers so they survive\n * (EC-383). A bare `Stack`/`Grid` inserts empty, and EC-161 normalization\n * dissolves BOTH empty and single-child containers on commit — so a bare\n * container insert vanishes instantly and reads as a broken insert. Seeding a\n * layout container exactly like its {@link layoutPresets} sibling (≥2 children)\n * gives it visible, survivable content, so the registry \"Default\" tile behaves\n * like the preset instead of disappearing. A component that fills no layout role\n * (a Container, a Repeater) is not normalized, so its bare node survives\n * untouched; every non-container manifest is returned unchanged (EC-066).\n */\nexport function seededNodeFromManifest(manifest: ComponentManifest, registry: ComponentRegistry): ComponentNode {\n const kind = layoutContainerKind(manifest.id, registry.layout)\n if (!kind) return nodeFromManifest(manifest)\n const preset = layoutPresets(registry).find((p) => p.key === kind)\n return preset ? preset.build() : nodeFromManifest(manifest)\n}\n"]}
1
+ {"version":3,"file":"node-factory.js","sourceRoot":"","sources":["../../../src/components/builder/node-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAG/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAE9D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAA2B;IAC1D,OAAO,kBAAkB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAA;AAC/E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,GAAqB,GAAG,CAAA;AACxC,MAAM,mBAAmB,GAAG,CAAC,CAAA;AAU7B,uFAAuF;AACvF,SAAS,OAAO,CAAC,QAA2B;IAC1C,OAAO,uBAAuB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,EAAE,CAAA;AAClE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,QAA2B;IACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;IAC3C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IACpB,MAAM,IAAI,GAAG,CAAC,KAAa,EAAiB,EAAE,CAC5C,kBAAkB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;IAC9F,MAAM,OAAO,GAAmB,EAAE,CAAA;IAClC,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,cAAc,CAAC,CAAA;IACpD,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,IAAI,CAAC;YACX,GAAG,EAAE,OAAO;YACZ,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;YACrB,WAAW,EAAE,sBAAsB;YACnC,KAAK,EAAE,GAAG,EAAE;YACV,iFAAiF;YACjF,gFAAgF;YAChF,mFAAmF;YACnF,kBAAkB,CAAC;gBACjB,EAAE,EAAE,KAAK;gBACT,WAAW,EAAE,KAAK,CAAC,EAAE;gBACrB,KAAK,EAAE,EAAE,GAAG,EAAE,UAAU,CAAC,UAAU,CAAC,EAAE;gBACtC,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE;aAC/D,CAAC;SACL,CAAC,CAAA;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC,CAAA;IAClD,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,CAAC,IAAI,CAAC;YACX,GAAG,EAAE,MAAM;YACX,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC;YACpB,WAAW,EAAE,mBAAmB;YAChC,KAAK,EAAE,GAAG,EAAE,CACV,kBAAkB,CAAC;gBACjB,EAAE,EAAE,KAAK;gBACT,WAAW,EAAE,IAAI,CAAC,EAAE;gBACpB,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,UAAU,CAAC,EAAE;gBAChF,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE;aAC1D,CAAC;SACL,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAA2B,EAAE,QAA2B;IAC7F,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9D,IAAI,CAAC,IAAI;QAAE,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAC5C,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,CAAA;IAClE,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAA;AAC7D,CAAC","sourcesContent":["import { layoutContainerKind, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, SpacingTokenName } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, ComponentRegistry } from '@elytracms/core/component-registry'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { componentBlockNode } from '@elytracms/core/rich-text'\n\n/**\n * Build a fresh node for a component (brief §8.2, EC-066). The placeholder id is\n * reassigned by the Builder store on insert. Required slots are left empty so\n * the renderer shows a missing-slot placeholder (EC-063) prompting the author to\n * fill them.\n *\n * EC-591 / ratified 2f: the node carries NO props. An absent prop is `undefined`\n * and the implementation's own default applies — on the canvas and on delivery\n * alike; the manifest `default` is an authoring hint (the inspector's greyed\n * placeholder), never baked into a stored node. (Until EC-591 every manifest\n * `default` was copied in here, so an insert and a \"copy exact configuration\"\n * paste of the same fixture stored — and rendered — different nodes.)\n *\n * EC-547 (EC-500 D1): every factory node goes through `componentBlockNode`, so\n * it carries the `elytra/component` discriminator from birth — a newly created\n * canvas node is a v2 block-sequence citizen without any tagging on write.\n */\nexport function nodeFromManifest(manifest: ComponentManifest): ComponentNode {\n return componentBlockNode({ id: 'tmp', componentId: manifest.id, props: {} })\n}\n\n/**\n * A fresh layout container's layout — the presets' (and so the Default tiles') own\n * explicit values, the ones the wrap gesture writes too (`lib/dnd` `WRAP_GRID_OPTIONS`).\n * EC-624: valid tokens; a container's absent gap means \"None\", so a fresh one says what\n * it wants.\n */\nconst PRESET_GAP: SpacingTokenName = '4'\nconst PRESET_GRID_COLUMNS = 2\n\n/** Layout helper presets (brief §8.2, EC-067) — seeded layout containers. */\nexport interface LayoutPreset {\n key: string\n label: string\n description: string\n build: () => ComponentNode\n}\n\n/** The display title of a role-filling component (its manifest title, else its id). */\nfunction titleOf(manifest: ComponentManifest): string {\n return localizedStringVariants(manifest.title)[0] ?? manifest.id\n}\n\n/**\n * The layout presets the component LIST supports (EC-591): a seeded `Stack` and a\n * seeded `Grid` — built from whichever components fill the `layout.stack` /\n * `layout.grid` roles, seeded with the `text`-role component (a preset needs ≥2\n * children to survive EC-161 normalization, EC-383). No hardcoded engine ids: a\n * role nobody fills means its preset is not offered (the canvas notice says why),\n * and with no `text` component there is no seed copy, so no preset at all.\n * Container, Repeater and Switch are plain components — the blocks panel offers\n * them as their own tiles, from their manifests.\n */\nexport function layoutPresets(registry: ComponentRegistry): LayoutPreset[] {\n const text = registry.roleComponent('text')\n if (!text) return []\n const copy = (value: string): ComponentNode =>\n componentBlockNode({ id: 'tmp', componentId: text.id, props: { value: staticProp(value) } })\n const presets: LayoutPreset[] = []\n const stack = registry.roleComponent('layout.stack')\n if (stack) {\n presets.push({\n key: 'stack',\n label: titleOf(stack),\n description: 'Vertical flex layout',\n build: () =>\n // EC-624: the preset's own layout, as a valid token — it stored a legacy numeric\n // `gap: 4` the token select refuses at publish. Not a manifest default (the gap\n // has none: absent is the canvas's \"None\" step); the wrap gesture writes the same.\n componentBlockNode({\n id: 'tmp',\n componentId: stack.id,\n props: { gap: staticProp(PRESET_GAP) },\n slots: { children: [copy('First item'), copy('Second item')] },\n }),\n })\n }\n const grid = registry.roleComponent('layout.grid')\n if (grid) {\n presets.push({\n key: 'grid',\n label: titleOf(grid),\n description: 'Multi-column grid',\n build: () =>\n componentBlockNode({\n id: 'tmp',\n componentId: grid.id,\n props: { columns: staticProp(PRESET_GRID_COLUMNS), gap: staticProp(PRESET_GAP) },\n slots: { children: [copy('Cell one'), copy('Cell two')] },\n }),\n })\n }\n return presets\n}\n\n/**\n * Build a fresh node for insertion, seeding layout containers so they survive\n * (EC-383). A bare `Stack`/`Grid` inserts empty, and EC-161 normalization\n * dissolves BOTH empty and single-child containers on commit — so a bare\n * container insert vanishes instantly and reads as a broken insert. Seeding a\n * layout container exactly like its {@link layoutPresets} sibling (≥2 children)\n * gives it visible, survivable content, so the registry \"Default\" tile behaves\n * like the preset instead of disappearing. A component that fills no layout role\n * (a Container, a Repeater) is not normalized, so its bare node survives\n * untouched; every non-container manifest is returned unchanged (EC-066).\n */\nexport function seededNodeFromManifest(manifest: ComponentManifest, registry: ComponentRegistry): ComponentNode {\n const kind = layoutContainerKind(manifest.id, registry.layout)\n if (!kind) return nodeFromManifest(manifest)\n const preset = layoutPresets(registry).find((p) => p.key === kind)\n return preset ? preset.build() : nodeFromManifest(manifest)\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { effectivePropValues } from '@elytracms/core/component-registry';
2
2
  import { richTextPlainText } from '@elytracms/core/rich-text';
3
- import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
3
+ import { componentDisplayName } from '../../lib/persona/labels.js';
4
4
  /**
5
5
  * Node display titles (EC-407) — the ONE helper behind both the page-tree row and
6
6
  * the EC-404 selection-overlay frame label.
@@ -1 +1 @@
1
- {"version":3,"file":"node-title.js","sourceRoot":"","sources":["../../../src/components/builder/node-title.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport { effectivePropValues } from '@elytracms/core/component-registry'\nimport { richTextPlainText } from '@elytracms/core/rich-text'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\n\n/**\n * Node display titles (EC-407) — the ONE helper behind both the page-tree row and\n * the EC-404 selection-overlay frame label.\n *\n * A placed instance shows a derived **instance title** (its own text content, e.g. a\n * heading's words) as the primary label, with the localized **component name** as\n * muted secondary context; a node with no derivable instance title shows the\n * component name alone. This replaces the old tree row's redundant `shortId` (mono\n * id) + `title` pair — the raw id stays one hover away in the inspector header +\n * the tree row `title=` attribute, so agent-inspectability is not lost.\n *\n * Placement: this lives in the studio (not `component-registry`) because the\n * cheap rich-text head reuses `@elytracms/core/rich-text` `richTextPlainText`,\n * which the DAG bars `component-registry` from importing. The pure derivation is\n * mirrored from the CMS `resolveDocumentTitle` pattern (declared field/function +\n * fallback), not shared with it.\n */\n\n/**\n * Prop names, in priority order, whose value best titles an instance (EC-407). The\n * name-affinity phase of {@link deriveInstanceTitle}: identity-ish props first\n * (`title`/`heading`/`label`), then generic text (`text`/`name`/`value`), then media\n * captions (`caption`/`alt`, so an Image titles by its alt text, not its URL `src`).\n */\nexport const INSTANCE_TITLE_AFFINITY = [\n 'title',\n 'heading',\n 'label',\n 'text',\n 'name',\n 'value',\n 'caption',\n 'alt',\n] as const\n\n/** Max characters of a derived instance title before it is truncated for a row/frame. */\nexport const INSTANCE_TITLE_MAX = 64\n\n/** A value that looks like a URL / path / protocol — never a useful title. */\nconst URL_LIKE = /^(https?:\\/\\/|\\/|#|mailto:|tel:|data:)/i\n\nexport interface NodeDisplayTitle {\n /** The main label a surface shows: the instance title when derivable, else the component name. */\n primary: string\n /** The localized component name — shown as muted secondary context ONLY when an instance title distinguishes `primary`. */\n secondary?: string\n /** The localized component TYPE name (always present). */\n componentName: string\n /** The derived per-instance title (already truncated), when one could be derived. */\n instanceTitle?: string\n}\n\n/**\n * Non-empty display text a text-ish prop value carries, else `undefined`.\n * - `text` props: the trimmed string (rejected when `rejectUrl` and it looks URL-ish).\n * - `richText` props: the cheap first-run plain-text head (`richTextPlainText`).\n * - everything else (number/boolean/date/select/relation/asset/blocks/object): not text-ish.\n */\nfunction propDisplayText(field: PropField, value: unknown, rejectUrl: boolean): string | undefined {\n if (field.type === 'text') {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (trimmed.length === 0) return undefined\n if (rejectUrl && URL_LIKE.test(trimmed)) return undefined\n return trimmed\n }\n if (field.type === 'richText') {\n const head = richTextPlainText(value)?.trim()\n return head && head.length > 0 ? head : undefined\n }\n return undefined\n}\n\n/**\n * Derive a placed instance's raw (untruncated) title from its effective prop values\n * (EC-407), or `undefined` when nothing text-ish is set. Pure; mirrors the CMS\n * `resolveDocumentTitle` precedence:\n *\n * 1. A declared `nodeTitle` **function** — composed from the values (throws are\n * swallowed so a bad pattern never breaks the tree).\n * 2. A declared `nodeTitle` **prop name** — that prop's display text.\n * 3. **Name-affinity heuristic** — the first {@link INSTANCE_TITLE_AFFINITY} prop\n * that carries display text.\n * 4. **Fallback** — the first declared text-ish prop (declaration order) with a\n * non-URL value.\n */\nexport function deriveInstanceTitle(\n manifest: ComponentManifest | undefined,\n node: ComponentNode,\n): string | undefined {\n if (!manifest) return undefined\n const values = effectivePropValues(manifest, node)\n\n const declared = manifest.nodeTitle\n if (typeof declared === 'function') {\n try {\n const out = declared(values)\n if (typeof out === 'string' && out.trim()) return out.trim()\n } catch {\n // A throwing title pattern must never break the editor — fall through.\n }\n } else if (typeof declared === 'string') {\n const field = manifest.props[declared]\n const text = field ? propDisplayText(field, values[declared], false) : undefined\n if (text) return text\n }\n\n for (const name of INSTANCE_TITLE_AFFINITY) {\n const field = manifest.props[name]\n if (!field) continue\n const text = propDisplayText(field, values[name], false)\n if (text) return text\n }\n\n for (const [name, field] of Object.entries(manifest.props)) {\n const text = propDisplayText(field, values[name], true)\n if (text) return text\n }\n\n return undefined\n}\n\n/** Truncate a title for a compact row / frame bar, appending an ellipsis. */\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : `${value.slice(0, max - 1).trimEnd()}…`\n}\n\n/**\n * The unified node label (EC-407) feeding the page-tree row AND the EC-404 overlay\n * frame. `primary` is the derived instance title when one exists (truncated), else\n * the localized component name; `secondary` is the localized component name, present\n * only when an instance title distinguishes the row. `locale` is optional — omit it\n * to resolve the component name against the active studio-UI locale.\n */\nexport function nodeDisplayTitle(\n manifest: ComponentManifest | undefined,\n node: ComponentNode,\n locale?: string,\n): NodeDisplayTitle {\n const componentName = componentDisplayName(node.componentId, manifest, locale)\n const raw = deriveInstanceTitle(manifest, node)\n const instanceTitle = raw ? truncate(raw, INSTANCE_TITLE_MAX) : undefined\n return {\n componentName,\n instanceTitle,\n primary: instanceTitle ?? componentName,\n secondary: instanceTitle ? componentName : undefined,\n }\n}\n"]}
1
+ {"version":3,"file":"node-title.js","sourceRoot":"","sources":["../../../src/components/builder/node-title.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAE/D;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,OAAO;IACP,SAAS;IACT,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,SAAS;IACT,KAAK;CACG,CAAA;AAEV,yFAAyF;AACzF,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAA;AAEpC,8EAA8E;AAC9E,MAAM,QAAQ,GAAG,yCAAyC,CAAA;AAa1D;;;;;GAKG;AACH,SAAS,eAAe,CAAC,KAAgB,EAAE,KAAc,EAAE,SAAkB;IAC3E,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAA;QAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;QAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAA;QAC1C,IAAI,SAAS,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,SAAS,CAAA;QACzD,OAAO,OAAO,CAAA;IAChB,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;QAC7C,OAAO,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;IACnD,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAuC,EACvC,IAAmB;IAEnB,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAA;IAC/B,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAElD,MAAM,QAAQ,GAAG,QAAQ,CAAC,SAAS,CAAA;IACnC,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;YAC5B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE;gBAAE,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;QAC9D,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;QACzE,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAChF,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;IACvB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,uBAAuB,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,CAAC,KAAK;YAAE,SAAQ;QACpB,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAA;QACxD,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;IACvB,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;QACvD,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;IACvB,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,6EAA6E;AAC7E,SAAS,QAAQ,CAAC,KAAa,EAAE,GAAW;IAC1C,OAAO,KAAK,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAA;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAuC,EACvC,IAAmB,EACnB,MAAe;IAEf,MAAM,aAAa,GAAG,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;IAC9E,MAAM,GAAG,GAAG,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAC/C,MAAM,aAAa,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACzE,OAAO;QACL,aAAa;QACb,aAAa;QACb,OAAO,EAAE,aAAa,IAAI,aAAa;QACvC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;KACrD,CAAA;AACH,CAAC","sourcesContent":["import type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport { effectivePropValues } from '@elytracms/core/component-registry'\nimport { richTextPlainText } from '@elytracms/core/rich-text'\nimport { componentDisplayName } from '../../lib/persona/labels'\n\n/**\n * Node display titles (EC-407) — the ONE helper behind both the page-tree row and\n * the EC-404 selection-overlay frame label.\n *\n * A placed instance shows a derived **instance title** (its own text content, e.g. a\n * heading's words) as the primary label, with the localized **component name** as\n * muted secondary context; a node with no derivable instance title shows the\n * component name alone. This replaces the old tree row's redundant `shortId` (mono\n * id) + `title` pair — the raw id stays one hover away in the inspector header +\n * the tree row `title=` attribute, so agent-inspectability is not lost.\n *\n * Placement: this lives in the studio (not `component-registry`) because the\n * cheap rich-text head reuses `@elytracms/core/rich-text` `richTextPlainText`,\n * which the DAG bars `component-registry` from importing. The pure derivation is\n * mirrored from the CMS `resolveDocumentTitle` pattern (declared field/function +\n * fallback), not shared with it.\n */\n\n/**\n * Prop names, in priority order, whose value best titles an instance (EC-407). The\n * name-affinity phase of {@link deriveInstanceTitle}: identity-ish props first\n * (`title`/`heading`/`label`), then generic text (`text`/`name`/`value`), then media\n * captions (`caption`/`alt`, so an Image titles by its alt text, not its URL `src`).\n */\nexport const INSTANCE_TITLE_AFFINITY = [\n 'title',\n 'heading',\n 'label',\n 'text',\n 'name',\n 'value',\n 'caption',\n 'alt',\n] as const\n\n/** Max characters of a derived instance title before it is truncated for a row/frame. */\nexport const INSTANCE_TITLE_MAX = 64\n\n/** A value that looks like a URL / path / protocol — never a useful title. */\nconst URL_LIKE = /^(https?:\\/\\/|\\/|#|mailto:|tel:|data:)/i\n\nexport interface NodeDisplayTitle {\n /** The main label a surface shows: the instance title when derivable, else the component name. */\n primary: string\n /** The localized component name — shown as muted secondary context ONLY when an instance title distinguishes `primary`. */\n secondary?: string\n /** The localized component TYPE name (always present). */\n componentName: string\n /** The derived per-instance title (already truncated), when one could be derived. */\n instanceTitle?: string\n}\n\n/**\n * Non-empty display text a text-ish prop value carries, else `undefined`.\n * - `text` props: the trimmed string (rejected when `rejectUrl` and it looks URL-ish).\n * - `richText` props: the cheap first-run plain-text head (`richTextPlainText`).\n * - everything else (number/boolean/date/select/relation/asset/blocks/object): not text-ish.\n */\nfunction propDisplayText(field: PropField, value: unknown, rejectUrl: boolean): string | undefined {\n if (field.type === 'text') {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (trimmed.length === 0) return undefined\n if (rejectUrl && URL_LIKE.test(trimmed)) return undefined\n return trimmed\n }\n if (field.type === 'richText') {\n const head = richTextPlainText(value)?.trim()\n return head && head.length > 0 ? head : undefined\n }\n return undefined\n}\n\n/**\n * Derive a placed instance's raw (untruncated) title from its effective prop values\n * (EC-407), or `undefined` when nothing text-ish is set. Pure; mirrors the CMS\n * `resolveDocumentTitle` precedence:\n *\n * 1. A declared `nodeTitle` **function** — composed from the values (throws are\n * swallowed so a bad pattern never breaks the tree).\n * 2. A declared `nodeTitle` **prop name** — that prop's display text.\n * 3. **Name-affinity heuristic** — the first {@link INSTANCE_TITLE_AFFINITY} prop\n * that carries display text.\n * 4. **Fallback** — the first declared text-ish prop (declaration order) with a\n * non-URL value.\n */\nexport function deriveInstanceTitle(\n manifest: ComponentManifest | undefined,\n node: ComponentNode,\n): string | undefined {\n if (!manifest) return undefined\n const values = effectivePropValues(manifest, node)\n\n const declared = manifest.nodeTitle\n if (typeof declared === 'function') {\n try {\n const out = declared(values)\n if (typeof out === 'string' && out.trim()) return out.trim()\n } catch {\n // A throwing title pattern must never break the editor — fall through.\n }\n } else if (typeof declared === 'string') {\n const field = manifest.props[declared]\n const text = field ? propDisplayText(field, values[declared], false) : undefined\n if (text) return text\n }\n\n for (const name of INSTANCE_TITLE_AFFINITY) {\n const field = manifest.props[name]\n if (!field) continue\n const text = propDisplayText(field, values[name], false)\n if (text) return text\n }\n\n for (const [name, field] of Object.entries(manifest.props)) {\n const text = propDisplayText(field, values[name], true)\n if (text) return text\n }\n\n return undefined\n}\n\n/** Truncate a title for a compact row / frame bar, appending an ellipsis. */\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : `${value.slice(0, max - 1).trimEnd()}…`\n}\n\n/**\n * The unified node label (EC-407) feeding the page-tree row AND the EC-404 overlay\n * frame. `primary` is the derived instance title when one exists (truncated), else\n * the localized component name; `secondary` is the localized component name, present\n * only when an instance title distinguishes the row. `locale` is optional — omit it\n * to resolve the component name against the active studio-UI locale.\n */\nexport function nodeDisplayTitle(\n manifest: ComponentManifest | undefined,\n node: ComponentNode,\n locale?: string,\n): NodeDisplayTitle {\n const componentName = componentDisplayName(node.componentId, manifest, locale)\n const raw = deriveInstanceTitle(manifest, node)\n const instanceTitle = raw ? truncate(raw, INSTANCE_TITLE_MAX) : undefined\n return {\n componentName,\n instanceTitle,\n primary: instanceTitle ?? componentName,\n secondary: instanceTitle ? componentName : undefined,\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { ComponentNode, LayoutContainerIds, PlacementScope } from '@elytracms/core/project-graph';
2
- import type { DragSubject, DropIndicator, DropResolution, ManifestLookup } from '@elytracms/studio/lib/dnd/drop-rules';
2
+ import type { DragSubject, DropIndicator, DropResolution, ManifestLookup } from '../../lib/dnd/drop-rules.js';
3
3
  /**
4
4
  * The tree's dragover resolution (EC-160/EC-162), pure: the row under the pointer
5
5
  * → a drop position (the hit band along the parent's flow axis; the root row is
@@ -2,12 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { Boxes, ChevronUp, ChevronDown, ClipboardCopy, ClipboardPaste, ClipboardX, Copy, MoreHorizontal, Plus, Trash2, Unlink, } from 'lucide-react';
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import { getParentInfo } from '@elytracms/core/project-graph';
5
- import { canNestInto, containerFlowAxis, dropIndicatorFor, dropPositionForPointer, manifestRules, resolveDrop, } from '@elytracms/studio/lib/dnd/drop-rules';
6
- import { getStudioRegistry } from '@elytracms/studio/lib/registry';
7
- import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
8
- import { translate, useUiLocale } from '@elytracms/studio/lib/i18n';
5
+ import { canNestInto, containerFlowAxis, dropIndicatorFor, dropPositionForPointer, manifestRules, resolveDrop, } from '../../lib/dnd/drop-rules.js';
6
+ import { getStudioRegistry } from '../../lib/registry/index.js';
7
+ import { componentDisplayName } from '../../lib/persona/labels.js';
8
+ import { translate, useUiLocale } from '../../lib/i18n/index.js';
9
9
  import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Row, Spacer, Text, } from '@elytracms/studio/ui';
10
- import { cn } from '@elytracms/studio/lib/utils';
10
+ import { cn } from '../../lib/utils.js';
11
11
  import { useBuilder } from './builder-store.js';
12
12
  import { attachClipboardListeners, useStudioClipboard } from './clipboard-actions.js';
13
13
  import { attachDeleteKeyListener } from './delete-key.js';