@elytracms/studio 0.0.92 → 0.0.94

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 (599) hide show
  1. package/convex/analytics-cost.test.ts +817 -0
  2. package/convex/asset-alt-migration.test.ts +127 -0
  3. package/convex/asset-replace.test.ts +168 -0
  4. package/convex/asset-revalidate.test.ts +6 -6
  5. package/convex/assets.ts +41 -5
  6. package/convex/auth.ts +5 -2
  7. package/convex/authority.ts +35 -11
  8. package/convex/cms.ts +25 -11
  9. package/convex/contract-parity.test.ts +25 -3
  10. package/convex/delivery.ts +12 -9
  11. package/convex/document-revalidate.test.ts +10 -12
  12. package/convex/guard.ts +9 -7
  13. package/convex/http-routes.test.ts +18 -8
  14. package/convex/http.ts +23 -17
  15. package/convex/ingest.test.ts +326 -0
  16. package/convex/ingest.ts +281 -0
  17. package/convex/invites.ts +13 -5
  18. package/convex/members.ts +10 -4
  19. package/convex/orphan-hydration.test.ts +116 -0
  20. package/convex/rail-write-parity.test.ts +11 -8
  21. package/convex/record-functions.ts +325 -0
  22. package/convex/records.test.ts +156 -0
  23. package/convex/redirect-migration.test.ts +205 -0
  24. package/convex/redirect-revalidate.test.ts +5 -1
  25. package/convex/redirects.ts +120 -79
  26. package/convex/role-policy.test.ts +315 -0
  27. package/convex/roles.ts +83 -0
  28. package/convex/schema.ts +36 -0
  29. package/convex/triggers.ts +21 -1
  30. package/dist/addon.d.ts +71 -0
  31. package/dist/addon.js +67 -0
  32. package/dist/addon.js.map +1 -0
  33. package/dist/components/account/account-settings.js +2 -2
  34. package/dist/components/account/account-settings.js.map +1 -1
  35. package/dist/components/account/credential-panel.js +2 -2
  36. package/dist/components/account/credential-panel.js.map +1 -1
  37. package/dist/components/account/self-account-schema.d.ts +6 -15
  38. package/dist/components/account/self-account-schema.js +52 -34
  39. package/dist/components/account/self-account-schema.js.map +1 -1
  40. package/dist/components/account/self-account-view-source.d.ts +1 -1
  41. package/dist/components/account/self-account-view-source.js +6 -4
  42. package/dist/components/account/self-account-view-source.js.map +1 -1
  43. package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
  44. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  45. package/dist/components/ai/ai-chat-panel.js +6 -6
  46. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  47. package/dist/components/ai/ai-cms-panel.js +2 -2
  48. package/dist/components/ai/ai-cms-panel.js.map +1 -1
  49. package/dist/components/ai/ai-panel-toggle.js +4 -4
  50. package/dist/components/ai/ai-panel-toggle.js.map +1 -1
  51. package/dist/components/ai/chat-ui.js +2 -2
  52. package/dist/components/ai/chat-ui.js.map +1 -1
  53. package/dist/components/ai/proposal-card.d.ts +2 -2
  54. package/dist/components/ai/proposal-card.js +4 -3
  55. package/dist/components/ai/proposal-card.js.map +1 -1
  56. package/dist/components/ai/proposal-issues.js +2 -2
  57. package/dist/components/ai/proposal-issues.js.map +1 -1
  58. package/dist/components/ai/translate-document-action.js +2 -2
  59. package/dist/components/ai/translate-document-action.js.map +1 -1
  60. package/dist/components/ai/translate-document-dialog.js +10 -8
  61. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  62. package/dist/components/ai/translate-skipped.d.ts +19 -0
  63. package/dist/components/ai/translate-skipped.js +46 -0
  64. package/dist/components/ai/translate-skipped.js.map +1 -0
  65. package/dist/components/app-views/default-modules.d.ts +50 -2
  66. package/dist/components/app-views/default-modules.js +65 -20
  67. package/dist/components/app-views/default-modules.js.map +1 -1
  68. package/dist/components/app-views/module.d.ts +53 -3
  69. package/dist/components/app-views/module.js +14 -7
  70. package/dist/components/app-views/module.js.map +1 -1
  71. package/dist/components/app-views/sidebar.js +1 -1
  72. package/dist/components/app-views/sidebar.js.map +1 -1
  73. package/dist/components/app-views/studio-config.d.ts +11 -2
  74. package/dist/components/app-views/studio-config.js.map +1 -1
  75. package/dist/components/assets/asset-detail.js +20 -10
  76. package/dist/components/assets/asset-detail.js.map +1 -1
  77. package/dist/components/assets/asset-grid.js +2 -2
  78. package/dist/components/assets/asset-grid.js.map +1 -1
  79. package/dist/components/assets/asset-metadata.d.ts +15 -4
  80. package/dist/components/assets/asset-metadata.js +15 -2
  81. package/dist/components/assets/asset-metadata.js.map +1 -1
  82. package/dist/components/assets/asset-search.js +4 -1
  83. package/dist/components/assets/asset-search.js.map +1 -1
  84. package/dist/components/assets/asset-selection.d.ts +0 -8
  85. package/dist/components/assets/asset-selection.js +4 -23
  86. package/dist/components/assets/asset-selection.js.map +1 -1
  87. package/dist/components/assets/asset-thumb.js +6 -5
  88. package/dist/components/assets/asset-thumb.js.map +1 -1
  89. package/dist/components/assets/assets-collection-view-source.d.ts +5 -5
  90. package/dist/components/assets/assets-collection-view-source.js +9 -5
  91. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  92. package/dist/components/assets/assets-module.js +1 -1
  93. package/dist/components/assets/assets-module.js.map +1 -1
  94. package/dist/components/assets/assets-store.d.ts +16 -2
  95. package/dist/components/assets/assets-store.js +28 -6
  96. package/dist/components/assets/assets-store.js.map +1 -1
  97. package/dist/components/assets/assets-view-preference.js +1 -1
  98. package/dist/components/assets/assets-view-preference.js.map +1 -1
  99. package/dist/components/assets/replace-file.d.ts +22 -0
  100. package/dist/components/assets/replace-file.js +59 -0
  101. package/dist/components/assets/replace-file.js.map +1 -0
  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 +15 -27
  106. package/dist/components/builder/blocks-model.js +38 -44
  107. package/dist/components/builder/blocks-model.js.map +1 -1
  108. package/dist/components/builder/blocks-panel.d.ts +8 -4
  109. package/dist/components/builder/blocks-panel.js +10 -9
  110. package/dist/components/builder/blocks-panel.js.map +1 -1
  111. package/dist/components/builder/builder-store.d.ts +104 -15
  112. package/dist/components/builder/builder-store.js +157 -32
  113. package/dist/components/builder/builder-store.js.map +1 -1
  114. package/dist/components/builder/canvas-body.d.ts +30 -0
  115. package/dist/components/builder/canvas-body.js +38 -0
  116. package/dist/components/builder/canvas-body.js.map +1 -0
  117. package/dist/components/builder/canvas-breakpoints.js +1 -1
  118. package/dist/components/builder/canvas-breakpoints.js.map +1 -1
  119. package/dist/components/builder/canvas-chrome-css.js +7 -0
  120. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  121. package/dist/components/builder/canvas-dnd.d.ts +9 -9
  122. package/dist/components/builder/canvas-dnd.js +6 -6
  123. package/dist/components/builder/canvas-dnd.js.map +1 -1
  124. package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
  125. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  126. package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
  127. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
  128. package/dist/components/builder/canvas-render-context.d.ts +28 -20
  129. package/dist/components/builder/canvas-render-context.js +46 -47
  130. package/dist/components/builder/canvas-render-context.js.map +1 -1
  131. package/dist/components/builder/canvas-runs.d.ts +45 -0
  132. package/dist/components/builder/canvas-runs.js +51 -0
  133. package/dist/components/builder/canvas-runs.js.map +1 -0
  134. package/dist/components/builder/canvas-theme-notice.js +2 -2
  135. package/dist/components/builder/canvas-theme-notice.js.map +1 -1
  136. package/dist/components/builder/canvas.js +35 -8
  137. package/dist/components/builder/canvas.js.map +1 -1
  138. package/dist/components/builder/clipboard-actions.d.ts +16 -1
  139. package/dist/components/builder/clipboard-actions.js +48 -11
  140. package/dist/components/builder/clipboard-actions.js.map +1 -1
  141. package/dist/components/builder/composition-field-editor-lazy.js +1 -1
  142. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
  143. package/dist/components/builder/composition-field-editor.d.ts +4 -16
  144. package/dist/components/builder/composition-field-editor.js +32 -43
  145. package/dist/components/builder/composition-field-editor.js.map +1 -1
  146. package/dist/components/builder/delete-key.d.ts +5 -0
  147. package/dist/components/builder/delete-key.js +4 -0
  148. package/dist/components/builder/delete-key.js.map +1 -1
  149. package/dist/components/builder/drag-session.d.ts +1 -1
  150. package/dist/components/builder/drag-session.js.map +1 -1
  151. package/dist/components/builder/fixture-thumbnail.js +1 -1
  152. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  153. package/dist/components/builder/insert-context.d.ts +18 -26
  154. package/dist/components/builder/insert-context.js +47 -94
  155. package/dist/components/builder/insert-context.js.map +1 -1
  156. package/dist/components/builder/layout-inspector.js +1 -1
  157. package/dist/components/builder/layout-inspector.js.map +1 -1
  158. package/dist/components/builder/node-title.js +1 -1
  159. package/dist/components/builder/node-title.js.map +1 -1
  160. package/dist/components/builder/page-tree.d.ts +4 -4
  161. package/dist/components/builder/page-tree.js +8 -8
  162. package/dist/components/builder/page-tree.js.map +1 -1
  163. package/dist/components/builder/placement-refusal.d.ts +18 -0
  164. package/dist/components/builder/placement-refusal.js +46 -0
  165. package/dist/components/builder/placement-refusal.js.map +1 -0
  166. package/dist/components/builder/property-inspector.js +8 -4
  167. package/dist/components/builder/property-inspector.js.map +1 -1
  168. package/dist/components/builder/reuse-context.js +68 -30
  169. package/dist/components/builder/reuse-context.js.map +1 -1
  170. package/dist/components/builder/reuse.d.ts +79 -1
  171. package/dist/components/builder/reuse.js +70 -3
  172. package/dist/components/builder/reuse.js.map +1 -1
  173. package/dist/components/builder/rich-text-toolbar.js +1 -1
  174. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  175. package/dist/components/builder/run-inspector.d.ts +11 -0
  176. package/dist/components/builder/run-inspector.js +33 -0
  177. package/dist/components/builder/run-inspector.js.map +1 -0
  178. package/dist/components/builder/selection-overlay.js +3 -3
  179. package/dist/components/builder/selection-overlay.js.map +1 -1
  180. package/dist/components/builder/spacing-handles.d.ts +2 -2
  181. package/dist/components/builder/spacing-handles.js +3 -3
  182. package/dist/components/builder/spacing-handles.js.map +1 -1
  183. package/dist/components/cms/asset-field.d.ts +38 -1
  184. package/dist/components/cms/asset-field.js +184 -60
  185. package/dist/components/cms/asset-field.js.map +1 -1
  186. package/dist/components/cms/asset-picker-core.d.ts +16 -0
  187. package/dist/components/cms/asset-picker-core.js +20 -0
  188. package/dist/components/cms/asset-picker-core.js.map +1 -1
  189. package/dist/components/cms/asset-picker.js +14 -20
  190. package/dist/components/cms/asset-picker.js.map +1 -1
  191. package/dist/components/cms/bulk-actions.d.ts +4 -20
  192. package/dist/components/cms/bulk-actions.js +5 -26
  193. package/dist/components/cms/bulk-actions.js.map +1 -1
  194. package/dist/components/cms/bulk-bar.d.ts +2 -2
  195. package/dist/components/cms/bulk-bar.js +2 -2
  196. package/dist/components/cms/bulk-bar.js.map +1 -1
  197. package/dist/components/cms/cell-edit.d.ts +1 -1
  198. package/dist/components/cms/cell-edit.js +1 -1
  199. package/dist/components/cms/cell-edit.js.map +1 -1
  200. package/dist/components/cms/cell.d.ts +1 -1
  201. package/dist/components/cms/cell.js +3 -3
  202. package/dist/components/cms/cell.js.map +1 -1
  203. package/dist/components/cms/cms-index.js +2 -2
  204. package/dist/components/cms/cms-index.js.map +1 -1
  205. package/dist/components/cms/cms-labels.js +2 -2
  206. package/dist/components/cms/cms-labels.js.map +1 -1
  207. package/dist/components/cms/cms-store.d.ts +7 -6
  208. package/dist/components/cms/cms-store.js +23 -43
  209. package/dist/components/cms/cms-store.js.map +1 -1
  210. package/dist/components/cms/collection-table.js +7 -7
  211. package/dist/components/cms/collection-table.js.map +1 -1
  212. package/dist/components/cms/detail-editor.d.ts +5 -2
  213. package/dist/components/cms/detail-editor.js +20 -15
  214. package/dist/components/cms/detail-editor.js.map +1 -1
  215. package/dist/components/cms/details-pane.js +1 -1
  216. package/dist/components/cms/details-pane.js.map +1 -1
  217. package/dist/components/cms/doc-state.js +1 -1
  218. package/dist/components/cms/doc-state.js.map +1 -1
  219. package/dist/components/cms/document-diff-view.d.ts +3 -1
  220. package/dist/components/cms/document-diff-view.js +17 -12
  221. package/dist/components/cms/document-diff-view.js.map +1 -1
  222. package/dist/components/cms/document-editor-context.js +1 -1
  223. package/dist/components/cms/document-editor-context.js.map +1 -1
  224. package/dist/components/cms/document-staging.js +1 -1
  225. package/dist/components/cms/document-staging.js.map +1 -1
  226. package/dist/components/cms/document-url-path.js +2 -2
  227. package/dist/components/cms/document-url-path.js.map +1 -1
  228. package/dist/components/cms/duplicate-document.d.ts +1 -1
  229. package/dist/components/cms/duplicate-document.js.map +1 -1
  230. package/dist/components/cms/editable-cell.js +3 -3
  231. package/dist/components/cms/editable-cell.js.map +1 -1
  232. package/dist/components/cms/field-anchors.d.ts +7 -4
  233. package/dist/components/cms/field-anchors.js +9 -7
  234. package/dist/components/cms/field-anchors.js.map +1 -1
  235. package/dist/components/cms/field-collaboration.d.ts +1 -1
  236. package/dist/components/cms/field-collaboration.js +1 -1
  237. package/dist/components/cms/field-collaboration.js.map +1 -1
  238. package/dist/components/cms/field-connection.d.ts +1 -1
  239. package/dist/components/cms/field-connection.js +2 -2
  240. package/dist/components/cms/field-connection.js.map +1 -1
  241. package/dist/components/cms/field-control-lib.d.ts +15 -1
  242. package/dist/components/cms/field-control-lib.js +23 -4
  243. package/dist/components/cms/field-control-lib.js.map +1 -1
  244. package/dist/components/cms/field-control.js +4 -4
  245. package/dist/components/cms/field-control.js.map +1 -1
  246. package/dist/components/cms/field-renderer.d.ts +1 -1
  247. package/dist/components/cms/field-renderer.js +2 -2
  248. package/dist/components/cms/field-renderer.js.map +1 -1
  249. package/dist/components/cms/filter-builder.d.ts +1 -1
  250. package/dist/components/cms/filter-builder.js +3 -3
  251. package/dist/components/cms/filter-builder.js.map +1 -1
  252. package/dist/components/cms/gate-validation.d.ts +21 -30
  253. package/dist/components/cms/gate-validation.js +11 -32
  254. package/dist/components/cms/gate-validation.js.map +1 -1
  255. package/dist/components/cms/import-export.js +2 -2
  256. package/dist/components/cms/import-export.js.map +1 -1
  257. package/dist/components/cms/issue-copy.js +1 -1
  258. package/dist/components/cms/issue-copy.js.map +1 -1
  259. package/dist/components/cms/issues-pane.js +3 -3
  260. package/dist/components/cms/issues-pane.js.map +1 -1
  261. package/dist/components/cms/object-array-field.js +6 -6
  262. package/dist/components/cms/object-array-field.js.map +1 -1
  263. package/dist/components/cms/prop-fields.d.ts +7 -2
  264. package/dist/components/cms/prop-fields.js +14 -7
  265. package/dist/components/cms/prop-fields.js.map +1 -1
  266. package/dist/components/cms/reference-confirm.d.ts +1 -1
  267. package/dist/components/cms/reference-confirm.js +2 -2
  268. package/dist/components/cms/reference-confirm.js.map +1 -1
  269. package/dist/components/cms/relation-picker.js +2 -2
  270. package/dist/components/cms/relation-picker.js.map +1 -1
  271. package/dist/components/cms/rich-text/block-style.js +1 -1
  272. package/dist/components/cms/rich-text/block-style.js.map +1 -1
  273. package/dist/components/cms/rich-text/component-embed.js +4 -4
  274. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  275. package/dist/components/cms/rich-text/editor.js +3 -3
  276. package/dist/components/cms/rich-text/editor.js.map +1 -1
  277. package/dist/components/cms/rich-text/embed-picker.d.ts +1 -1
  278. package/dist/components/cms/rich-text/embed-picker.js +3 -3
  279. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  280. package/dist/components/cms/rich-text/embed-props.js +3 -3
  281. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  282. package/dist/components/cms/rich-text/extensions.js +1 -1
  283. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  284. package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
  285. package/dist/components/cms/rich-text/font-size-control.js +2 -2
  286. package/dist/components/cms/rich-text/font-size-control.js.map +1 -1
  287. package/dist/components/cms/rich-text/insert.d.ts +20 -9
  288. package/dist/components/cms/rich-text/insert.js +25 -17
  289. package/dist/components/cms/rich-text/insert.js.map +1 -1
  290. package/dist/components/cms/rich-text/lazy.js +1 -1
  291. package/dist/components/cms/rich-text/lazy.js.map +1 -1
  292. package/dist/components/cms/rich-text/link-control.js +2 -2
  293. package/dist/components/cms/rich-text/link-control.js.map +1 -1
  294. package/dist/components/cms/rich-text/slash-command.js +3 -3
  295. package/dist/components/cms/rich-text/slash-command.js.map +1 -1
  296. package/dist/components/cms/rich-text/toolbar.js +1 -1
  297. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  298. package/dist/components/cms/rich-text/unknown-node.js +2 -2
  299. package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
  300. package/dist/components/cms/rich-text-preview.js +2 -2
  301. package/dist/components/cms/rich-text-preview.js.map +1 -1
  302. package/dist/components/cms/row-actions.d.ts +1 -1
  303. package/dist/components/cms/row-actions.js.map +1 -1
  304. package/dist/components/cms/scalar-value-editor.d.ts +1 -1
  305. package/dist/components/cms/scalar-value-editor.js +2 -2
  306. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  307. package/dist/components/cms/use-discard-controller.d.ts +1 -1
  308. package/dist/components/cms/use-discard-controller.js +1 -1
  309. package/dist/components/cms/use-discard-controller.js.map +1 -1
  310. package/dist/components/cms/use-document-draft.d.ts +7 -1
  311. package/dist/components/cms/use-document-draft.js +16 -2
  312. package/dist/components/cms/use-document-draft.js.map +1 -1
  313. package/dist/components/cms/use-document-versions.js +1 -1
  314. package/dist/components/cms/use-document-versions.js.map +1 -1
  315. package/dist/components/cms/use-field-focus.d.ts +1 -1
  316. package/dist/components/cms/use-field-focus.js +2 -2
  317. package/dist/components/cms/use-field-focus.js.map +1 -1
  318. package/dist/components/cms/use-live-url.d.ts +1 -1
  319. package/dist/components/cms/use-live-url.js +3 -3
  320. package/dist/components/cms/use-live-url.js.map +1 -1
  321. package/dist/components/cms/use-publish-controller.d.ts +2 -3
  322. package/dist/components/cms/use-publish-controller.js +8 -20
  323. package/dist/components/cms/use-publish-controller.js.map +1 -1
  324. package/dist/components/cms/use-published-perspective.d.ts +1 -1
  325. package/dist/components/cms/use-published-perspective.js.map +1 -1
  326. package/dist/components/cms/use-restore-version.d.ts +1 -1
  327. package/dist/components/cms/use-restore-version.js +1 -1
  328. package/dist/components/cms/use-restore-version.js.map +1 -1
  329. package/dist/components/cms/version-compare-lazy.js +1 -1
  330. package/dist/components/cms/version-compare-lazy.js.map +1 -1
  331. package/dist/components/cms/version-compare.js +1 -1
  332. package/dist/components/cms/version-compare.js.map +1 -1
  333. package/dist/components/cms/versions-pane.js +11 -4
  334. package/dist/components/cms/versions-pane.js.map +1 -1
  335. package/dist/components/cms/view-bar.js +3 -3
  336. package/dist/components/cms/view-bar.js.map +1 -1
  337. package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
  338. package/dist/components/collection-view/cms-collection-view-source.js +5 -3
  339. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  340. package/dist/components/collection-view/collection-view-context.d.ts +1 -1
  341. package/dist/components/collection-view/collection-view-context.js +1 -1
  342. package/dist/components/collection-view/collection-view-context.js.map +1 -1
  343. package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
  344. package/dist/components/collection-view/collection-view-surface.js +18 -0
  345. package/dist/components/collection-view/collection-view-surface.js.map +1 -0
  346. package/dist/components/components/component-detail.js +10 -10
  347. package/dist/components/components/component-detail.js.map +1 -1
  348. package/dist/components/components/component-preview.d.ts +2 -2
  349. package/dist/components/components/component-preview.js +5 -5
  350. package/dist/components/components/component-preview.js.map +1 -1
  351. package/dist/components/components/component-status.d.ts +1 -1
  352. package/dist/components/components/component-status.js +1 -1
  353. package/dist/components/components/component-status.js.map +1 -1
  354. package/dist/components/components/components-index.js +2 -2
  355. package/dist/components/components/components-index.js.map +1 -1
  356. package/dist/components/components/field-inspector.js +1 -1
  357. package/dist/components/components/field-inspector.js.map +1 -1
  358. package/dist/components/components/registry-browser.js +3 -3
  359. package/dist/components/components/registry-browser.js.map +1 -1
  360. package/dist/components/components/taxonomy.d.ts +1 -1
  361. package/dist/components/components/taxonomy.js.map +1 -1
  362. package/dist/components/components/transfer-dialog.js +4 -3
  363. package/dist/components/components/transfer-dialog.js.map +1 -1
  364. package/dist/components/components/transfer.d.ts +10 -10
  365. package/dist/components/components/transfer.js +13 -12
  366. package/dist/components/components/transfer.js.map +1 -1
  367. package/dist/components/current-user-menu.js +1 -1
  368. package/dist/components/current-user-menu.js.map +1 -1
  369. package/dist/components/delivery-health-banner.js +2 -2
  370. package/dist/components/delivery-health-banner.js.map +1 -1
  371. package/dist/components/deployment-setup-gate.js +3 -3
  372. package/dist/components/deployment-setup-gate.js.map +1 -1
  373. package/dist/components/dev-login-badge.js +2 -2
  374. package/dist/components/dev-login-badge.js.map +1 -1
  375. package/dist/components/guards.js +1 -1
  376. package/dist/components/guards.js.map +1 -1
  377. package/dist/components/inspector/document-panes.d.ts +54 -0
  378. package/dist/components/inspector/document-panes.js +41 -0
  379. package/dist/components/inspector/document-panes.js.map +1 -0
  380. package/dist/components/inspector/inspector-sidebar.js +1 -1
  381. package/dist/components/inspector/inspector-sidebar.js.map +1 -1
  382. package/dist/components/nav-issues-registry.d.ts +53 -0
  383. package/dist/components/nav-issues-registry.js +69 -0
  384. package/dist/components/nav-issues-registry.js.map +1 -0
  385. package/dist/components/nav-issues.js +4 -4
  386. package/dist/components/nav-issues.js.map +1 -1
  387. package/dist/components/operation-error.d.ts +1 -1
  388. package/dist/components/operation-error.js +4 -4
  389. package/dist/components/operation-error.js.map +1 -1
  390. package/dist/components/playground-badge.js +2 -2
  391. package/dist/components/playground-badge.js.map +1 -1
  392. package/dist/components/project-list.js +3 -3
  393. package/dist/components/project-list.js.map +1 -1
  394. package/dist/components/project-session.d.ts +2 -2
  395. package/dist/components/project-session.js +5 -5
  396. package/dist/components/project-session.js.map +1 -1
  397. package/dist/components/project-switcher.js +2 -2
  398. package/dist/components/project-switcher.js.map +1 -1
  399. package/dist/components/rail-store-status.js +1 -1
  400. package/dist/components/rail-store-status.js.map +1 -1
  401. package/dist/components/rail-view-source.d.ts +6 -5
  402. package/dist/components/rail-view-source.js +4 -3
  403. package/dist/components/rail-view-source.js.map +1 -1
  404. package/dist/components/records/record-lens.d.ts +51 -0
  405. package/dist/components/records/record-lens.js +156 -0
  406. package/dist/components/records/record-lens.js.map +1 -0
  407. package/dist/components/records/record-rail.d.ts +86 -0
  408. package/dist/components/records/record-rail.js +241 -0
  409. package/dist/components/records/record-rail.js.map +1 -0
  410. package/dist/components/redirects/redirects-schema.js +1 -1
  411. package/dist/components/redirects/redirects-schema.js.map +1 -1
  412. package/dist/components/references/references-panel.js +2 -2
  413. package/dist/components/references/references-panel.js.map +1 -1
  414. package/dist/components/section-nav.js +27 -11
  415. package/dist/components/section-nav.js.map +1 -1
  416. package/dist/components/section-state.js +2 -2
  417. package/dist/components/section-state.js.map +1 -1
  418. package/dist/components/section-surface.js +2 -2
  419. package/dist/components/section-surface.js.map +1 -1
  420. package/dist/components/session-expired-overlay.js +3 -3
  421. package/dist/components/session-expired-overlay.js.map +1 -1
  422. package/dist/components/settings-form.js +1 -1
  423. package/dist/components/settings-form.js.map +1 -1
  424. package/dist/components/sign-in-needed-badge.js +1 -1
  425. package/dist/components/sign-in-needed-badge.js.map +1 -1
  426. package/dist/components/sign-in-view.d.ts +2 -2
  427. package/dist/components/sign-in-view.js +5 -5
  428. package/dist/components/sign-in-view.js.map +1 -1
  429. package/dist/components/studio-context.js +3 -3
  430. package/dist/components/studio-context.js.map +1 -1
  431. package/dist/components/studio-project-screen.js +30 -3
  432. package/dist/components/studio-project-screen.js.map +1 -1
  433. package/dist/components/studio-shell.js +6 -5
  434. package/dist/components/studio-shell.js.map +1 -1
  435. package/dist/components/theme-context.d.ts +1 -1
  436. package/dist/components/theme-context.js +2 -2
  437. package/dist/components/theme-context.js.map +1 -1
  438. package/dist/components/use-hydration-phase.d.ts +1 -1
  439. package/dist/components/use-hydration-phase.js +1 -1
  440. package/dist/components/use-hydration-phase.js.map +1 -1
  441. package/dist/components/users/invite-user-bar.d.ts +27 -0
  442. package/dist/components/users/invite-user-bar.js +25 -9
  443. package/dist/components/users/invite-user-bar.js.map +1 -1
  444. package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
  445. package/dist/components/users/role-policy-drift-issues.js +71 -0
  446. package/dist/components/users/role-policy-drift-issues.js.map +1 -0
  447. package/dist/components/users/users-collection-view-source.d.ts +1 -1
  448. package/dist/components/users/users-collection-view-source.js +4 -2
  449. package/dist/components/users/users-collection-view-source.js.map +1 -1
  450. package/dist/components/users/users-module.js +1 -1
  451. package/dist/components/users/users-module.js.map +1 -1
  452. package/dist/components/users/users-schema.d.ts +10 -1
  453. package/dist/components/users/users-schema.js +45 -25
  454. package/dist/components/users/users-schema.js.map +1 -1
  455. package/dist/components/users/users-store.js +3 -3
  456. package/dist/components/users/users-store.js.map +1 -1
  457. package/dist/components/workspace-projects.d.ts +1 -1
  458. package/dist/components/workspace-projects.js +1 -1
  459. package/dist/components/workspace-projects.js.map +1 -1
  460. package/dist/lib/ai/apply-content.js +56 -116
  461. package/dist/lib/ai/apply-content.js.map +1 -1
  462. package/dist/lib/ai/chat-context.d.ts +8 -0
  463. package/dist/lib/ai/chat-context.js +37 -6
  464. package/dist/lib/ai/chat-context.js.map +1 -1
  465. package/dist/lib/ai/chat-state.d.ts +6 -0
  466. package/dist/lib/ai/chat-state.js.map +1 -1
  467. package/dist/lib/ai/constraints.d.ts +20 -5
  468. package/dist/lib/ai/constraints.js +125 -33
  469. package/dist/lib/ai/constraints.js.map +1 -1
  470. package/dist/lib/ai/fake-provider.d.ts +16 -3
  471. package/dist/lib/ai/fake-provider.js +22 -6
  472. package/dist/lib/ai/fake-provider.js.map +1 -1
  473. package/dist/lib/ai/index.d.ts +3 -1
  474. package/dist/lib/ai/index.js +3 -1
  475. package/dist/lib/ai/index.js.map +1 -1
  476. package/dist/lib/ai/leash.d.ts +34 -0
  477. package/dist/lib/ai/leash.js +28 -0
  478. package/dist/lib/ai/leash.js.map +1 -0
  479. package/dist/lib/ai/node-handler.d.ts +1 -1
  480. package/dist/lib/ai/node-handler.js.map +1 -1
  481. package/dist/lib/ai/rich-text-input.d.ts +8 -8
  482. package/dist/lib/ai/rich-text-input.js +2 -1
  483. package/dist/lib/ai/rich-text-input.js.map +1 -1
  484. package/dist/lib/ai/rich-text-translate.d.ts +27 -4
  485. package/dist/lib/ai/rich-text-translate.js +28 -10
  486. package/dist/lib/ai/rich-text-translate.js.map +1 -1
  487. package/dist/lib/ai/shape.d.ts +13 -13
  488. package/dist/lib/ai/shape.js +69 -93
  489. package/dist/lib/ai/shape.js.map +1 -1
  490. package/dist/lib/ai/tools.d.ts +36 -41
  491. package/dist/lib/ai/tools.js +83 -87
  492. package/dist/lib/ai/tools.js.map +1 -1
  493. package/dist/lib/ai/translate-document.d.ts +32 -4
  494. package/dist/lib/ai/translate-document.js +145 -47
  495. package/dist/lib/ai/translate-document.js.map +1 -1
  496. package/dist/lib/ai/types.d.ts +34 -23
  497. package/dist/lib/ai/types.js.map +1 -1
  498. package/dist/lib/ai/update-fields.d.ts +15 -12
  499. package/dist/lib/ai/update-fields.js +112 -188
  500. package/dist/lib/ai/update-fields.js.map +1 -1
  501. package/dist/lib/ai/writer-form.d.ts +116 -0
  502. package/dist/lib/ai/writer-form.js +429 -0
  503. package/dist/lib/ai/writer-form.js.map +1 -0
  504. package/dist/lib/assets/upload.d.ts +31 -2
  505. package/dist/lib/assets/upload.js +61 -6
  506. package/dist/lib/assets/upload.js.map +1 -1
  507. package/dist/lib/clipboard/os-clipboard.d.ts +10 -2
  508. package/dist/lib/clipboard/os-clipboard.js +8 -4
  509. package/dist/lib/clipboard/os-clipboard.js.map +1 -1
  510. package/dist/lib/clipboard/paste-issues.js +1 -1
  511. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  512. package/dist/lib/clipboard/paste-target.d.ts +18 -9
  513. package/dist/lib/clipboard/paste-target.js +10 -9
  514. package/dist/lib/clipboard/paste-target.js.map +1 -1
  515. package/dist/lib/cms/field-patch.d.ts +0 -10
  516. package/dist/lib/cms/field-patch.js +4 -30
  517. package/dist/lib/cms/field-patch.js.map +1 -1
  518. package/dist/lib/cms/published-documents.js +1 -1
  519. package/dist/lib/cms/published-documents.js.map +1 -1
  520. package/dist/lib/cms-views/view-model.d.ts +9 -9
  521. package/dist/lib/cms-views/view-model.js +2 -2
  522. package/dist/lib/cms-views/view-model.js.map +1 -1
  523. package/dist/lib/collection-view/collection-view-source.d.ts +14 -5
  524. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  525. package/dist/lib/data/collection-content.js +3 -2
  526. package/dist/lib/data/collection-content.js.map +1 -1
  527. package/dist/lib/data/content-session-refresh.d.ts +1 -1
  528. package/dist/lib/data/content-session-refresh.js.map +1 -1
  529. package/dist/lib/data/playground-unload-guard.d.ts +1 -1
  530. package/dist/lib/data/playground-unload-guard.js +1 -1
  531. package/dist/lib/data/playground-unload-guard.js.map +1 -1
  532. package/dist/lib/dnd/drop-rules.d.ts +25 -31
  533. package/dist/lib/dnd/drop-rules.js +53 -59
  534. package/dist/lib/dnd/drop-rules.js.map +1 -1
  535. package/dist/lib/engine-version.d.ts +1 -1
  536. package/dist/lib/engine-version.js +1 -1
  537. package/dist/lib/engine-version.js.map +1 -1
  538. package/dist/lib/i18n/i18n.js +1 -1
  539. package/dist/lib/i18n/i18n.js.map +1 -1
  540. package/dist/lib/i18n/messages.de.js +44 -3
  541. package/dist/lib/i18n/messages.de.js.map +1 -1
  542. package/dist/lib/i18n/messages.en.d.ts +38 -2
  543. package/dist/lib/i18n/messages.en.js +51 -3
  544. package/dist/lib/i18n/messages.en.js.map +1 -1
  545. package/dist/lib/operations/project-runtime-config.d.ts +34 -6
  546. package/dist/lib/operations/project-runtime-config.js +50 -10
  547. package/dist/lib/operations/project-runtime-config.js.map +1 -1
  548. package/dist/lib/operations/publish-with-revision.d.ts +33 -1
  549. package/dist/lib/operations/publish-with-revision.js +32 -0
  550. package/dist/lib/operations/publish-with-revision.js.map +1 -1
  551. package/dist/lib/operations/runtime-validator.d.ts +19 -68
  552. package/dist/lib/operations/runtime-validator.js +12 -138
  553. package/dist/lib/operations/runtime-validator.js.map +1 -1
  554. package/dist/lib/operations/studio-operations.d.ts +17 -2
  555. package/dist/lib/operations/studio-operations.js +18 -11
  556. package/dist/lib/operations/studio-operations.js.map +1 -1
  557. package/dist/lib/persistence/convex-adapter.d.ts +16 -3
  558. package/dist/lib/persistence/convex-adapter.js +83 -47
  559. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  560. package/dist/lib/persistence/index.js +14 -1
  561. package/dist/lib/persistence/index.js.map +1 -1
  562. package/dist/lib/persona/labels.js +4 -2
  563. package/dist/lib/persona/labels.js.map +1 -1
  564. package/dist/lib/query/operations-query.d.ts +1 -1
  565. package/dist/lib/query/operations-query.js +1 -1
  566. package/dist/lib/query/operations-query.js.map +1 -1
  567. package/dist/lib/references/reference-model.js +1 -1
  568. package/dist/lib/references/reference-model.js.map +1 -1
  569. package/dist/lib/references/references-store.js +1 -1
  570. package/dist/lib/references/references-store.js.map +1 -1
  571. package/dist/lib/workspace/index.d.ts +14 -11
  572. package/dist/lib/workspace/index.js +20 -24
  573. package/dist/lib/workspace/index.js.map +1 -1
  574. package/dist/mount.d.ts +28 -0
  575. package/dist/mount.js +27 -0
  576. package/dist/mount.js.map +1 -0
  577. package/dist/router/root.js +10 -10
  578. package/dist/router/root.js.map +1 -1
  579. package/dist/ui/badge.d.ts +1 -1
  580. package/dist/ui/button.d.ts +2 -2
  581. package/package.json +15 -11
  582. package/dist/components/cms/cms-composition-validators.d.ts +0 -16
  583. package/dist/components/cms/cms-composition-validators.js +0 -133
  584. package/dist/components/cms/cms-composition-validators.js.map +0 -1
  585. package/dist/components/cms/publish-clean.d.ts +0 -28
  586. package/dist/components/cms/publish-clean.js +0 -38
  587. package/dist/components/cms/publish-clean.js.map +0 -1
  588. package/dist/components/redirects/redirects-collection-view-source.d.ts +0 -53
  589. package/dist/components/redirects/redirects-collection-view-source.js +0 -146
  590. package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
  591. package/dist/components/redirects/redirects-module.d.ts +0 -21
  592. package/dist/components/redirects/redirects-module.js +0 -40
  593. package/dist/components/redirects/redirects-module.js.map +0 -1
  594. package/dist/components/redirects/redirects-store.d.ts +0 -68
  595. package/dist/components/redirects/redirects-store.js +0 -132
  596. package/dist/components/redirects/redirects-store.js.map +0 -1
  597. package/dist/lib/assets/asset-placements.d.ts +0 -28
  598. package/dist/lib/assets/asset-placements.js +0 -83
  599. package/dist/lib/assets/asset-placements.js.map +0 -1
@@ -3,8 +3,8 @@ import { useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { EditorContent, useEditor } from '@tiptap/react';
4
4
  import { Minimize2 } from 'lucide-react';
5
5
  import { ExpandableSurface, Text } from '@elytracms/studio/ui';
6
- import { cn } from '@elytracms/studio/lib/utils';
7
- import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
6
+ import { cn } from '../../../lib/utils.js';
7
+ import { localizedLabel, translate } from '../../../lib/i18n/index.js';
8
8
  import { useOptionalCollectionView } from '../../collection-view/collection-view-context.js';
9
9
  import { useOptionalAssets } from '../../assets/assets-store.js';
10
10
  import { AssetPicker } from '../asset-picker.js';
@@ -12,7 +12,7 @@ import { buildRichTextExtensions } from './extensions.js';
12
12
  import { blockSequenceValueFromDoc, isEffectivelyEmpty, isEquivalentToStored, normalizeEditorDoc, toEditorDoc, } from './value.js';
13
13
  import { INLINE_IMAGE_ACCEPT, insertAssetImage, insertComponentEmbed, listEmbeddable } from './insert.js';
14
14
  import { EmbedPicker } from './embed-picker.js';
15
- import { getStudioRegistry } from '@elytracms/studio/lib/registry';
15
+ import { getStudioRegistry } from '../../../lib/registry/index.js';
16
16
  import { RichTextToolbar } from './toolbar.js';
17
17
  /**
18
18
  * The CMS rich-text field editor (EC-314) — a real TipTap editor over the shared
@@ -1 +1 @@
1
- {"version":3,"file":"editor.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/editor.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { Minimize2 } from 'lucide-react'\nimport type { RichTextDoc } from '@elytracms/core/rich-text'\nimport { ExpandableSurface, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { useOptionalCollectionView } from '../../collection-view/collection-view-context'\nimport { useOptionalAssets } from '../../assets/assets-store'\nimport { AssetPicker } from '../asset-picker'\nimport { buildRichTextExtensions } from './extensions'\nimport {\n blockSequenceValueFromDoc,\n isEffectivelyEmpty,\n isEquivalentToStored,\n normalizeEditorDoc,\n toEditorDoc,\n} from './value'\nimport { INLINE_IMAGE_ACCEPT, insertAssetImage, insertComponentEmbed, listEmbeddable } from './insert'\nimport { EmbedPicker } from './embed-picker'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { RichTextToolbar } from './toolbar'\nimport type { LinkTarget } from './link-control'\n\ninterface RichTextEditorProps {\n /** The stored value (v2 `{ version: 2, content }`; staging: v1/legacy forms). */\n value: unknown\n onChange: (value: unknown) => void\n readOnly?: boolean\n id?: string\n /** The field's embed `allow` list (component ids editors may embed). */\n allow?: string[]\n /**\n * EC-436 (AD-1) — the field's resolved localized display label, the SAME\n * string the field/prop row renders next to this editor. Used only to title\n * the expand stage (\"<label> — Rich text\" via the parameterized\n * `richText.expand.titleWithField` message, {@link richTextStageTitle});\n * absent → the generic `richText.expand.title` fallback.\n */\n label?: string\n}\n\n/**\n * The CMS rich-text field editor (EC-314) — a real TipTap editor over the shared\n * `@elytracms/core/rich-text` storage, replacing the plaintext textarea stub.\n *\n * It stays *controlled without churn*: the editor is created once from the initial\n * value; the stored value is only re-pushed into ProseMirror when it changes\n * externally (locale switch, reset, compare) and is not already equivalent to what\n * is on screen. On edit, output is pruned to the lean storage convention\n * ({@link normalizeEditorDoc}) and — crucially — when an edit lands the document back\n * on its stored meaning, the *exact stored value* is re-emitted so the field never\n * shows a spurious dirty state (the same \"commit-nothing-when-equivalent\" contract\n * the canvas inline editor uses).\n *\n * The image and component-embed pickers are owned here (not the toolbar) so both the\n * toolbar buttons and the `/` slash menu open the same modal — one insertion path.\n */\nexport function RichTextEditor({ value, onChange, readOnly = false, id, allow, label }: RichTextEditorProps) {\n // Ambient context for reference-aware insertion (null outside a collection view /\n // asset backend — the editor then degrades to external links and no image insert).\n const source = useOptionalCollectionView()\n const assets = useOptionalAssets()\n\n const [imageOpen, setImageOpen] = useState(false)\n const [embedOpen, setEmbedOpen] = useState(false)\n // EC-388/EC-431: whether the editor is promoted onto its centered expand stage.\n const [expanded, setExpanded] = useState(false)\n\n const canImage = assets != null\n // The `allow`-gated components this field may embed — drives both the toolbar\n // picker and the slash menu's direct per-component items (0.0.41).\n const embeddable = useMemo(\n () => listEmbeddable(allow, getStudioRegistry().registry.list()),\n [allow],\n )\n const canEmbed = embeddable.length > 0\n\n // Every linkable document across the workspace (excluding the asset rail — you link\n // to content, not to raw files). Recomputed only when the source changes.\n const linkTargets = useMemo<LinkTarget[]>(() => {\n if (!source) return []\n const out: LinkTarget[] = []\n for (const collection of source.collections) {\n if (collection.id === source.assetCollectionId) continue\n const collectionLabel = localizedLabel(collection.form?.labelSingular, collection.id)\n for (const doc of source.referenceCandidates(collection.id)) {\n const ref = { collection: collection.id, id: doc.id }\n out.push({ ref, label: source.titleOf(ref), collectionLabel })\n }\n }\n return out\n }, [source])\n\n // Latest value/onChange, read inside the stable onUpdate closure.\n const valueRef = useRef(value)\n valueRef.current = value\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n // What we last emitted upward — lets the sync effect ignore our own echoes.\n const lastEmitted = useRef<unknown>(value)\n\n // Media openers for the toolbar + slash menu. setState fns are stable, so these\n // stay valid for the life of the editor even though it's built once.\n const openImage = canImage ? () => setImageOpen(true) : undefined\n const openEmbed = canEmbed ? () => setEmbedOpen(true) : undefined\n\n const editor = useEditor({\n extensions: buildRichTextExtensions({\n slash: {\n onImage: openImage,\n // Direct per-component slash items (0.0.41) — insert with manifest defaults\n // and select, so the embed inspector opens onto the new node's props.\n embed: canEmbed\n ? {\n components: embeddable,\n insert: (ed, range, manifest) => {\n ed.chain().focus().deleteRange(range).run()\n insertComponentEmbed(ed, manifest, { select: true })\n },\n }\n : undefined,\n },\n }),\n content: toEditorDoc(value),\n editable: !readOnly,\n editorProps: {\n attributes: { class: 'ec-rte-content', ...(id ? { id } : {}) },\n },\n onUpdate: ({ editor }) => {\n const current = normalizeEditorDoc(editor.getJSON() as unknown as RichTextDoc)\n const stored = valueRef.current\n // Three outcomes: unchanged → re-emit the stored value verbatim (no dirty);\n // emptied → clear the field (undefined); otherwise → the new stamped value.\n const next = isEquivalentToStored(current, stored)\n ? stored\n : isEffectivelyEmpty(current)\n ? undefined\n : blockSequenceValueFromDoc(current)\n lastEmitted.current = next\n onChangeRef.current(next)\n },\n })\n\n // Reflect external value changes into the editor, never echoing our own emits.\n useEffect(() => {\n if (!editor) return\n if (value === lastEmitted.current) return\n const current = normalizeEditorDoc(editor.getJSON() as unknown as RichTextDoc)\n if (isEquivalentToStored(current, value)) return\n editor.commands.setContent(toEditorDoc(value), { emitUpdate: false })\n }, [editor, value])\n\n // Toggle editability without rebuilding the editor (preserves history/selection).\n useEffect(() => {\n editor?.setEditable(!readOnly)\n }, [editor, readOnly])\n\n if (!editor) return null\n\n // The editing surface — the toolbar (which carries the expand toggle) + the\n // editable content. Mounted inline OR inside the expand stage, never both. TipTap\n // preserves the editor instance across `EditorContent` remounts (its view DOM is\n // carried onto the new element), so relocating this surface keeps ONE editor\n // document and ONE value flow — the same instance, grown, not a second form.\n // While expanded, the toolbar stays put above the scrolling prose column\n // (the scroll area is the EditorContent wrapper).\n const surface = (\n <>\n {!readOnly ? (\n <RichTextToolbar\n editor={editor}\n targets={linkTargets}\n onOpenImage={openImage}\n onOpenEmbed={openEmbed}\n expanded={expanded}\n onToggleExpand={() => setExpanded((v) => !v)}\n />\n ) : null}\n <EditorContent editor={editor} className={cn(expanded && RICH_TEXT_STAGE_CONTENT_CLASS)} />\n </>\n )\n\n return (\n <div\n className={cn(\n 'ec-rte overflow-hidden rounded-md border border-input bg-background shadow-sm',\n 'focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/40',\n readOnly && 'opacity-70',\n )}\n >\n {expanded ? (\n <>\n <RichTextExpandPlaceholder onCollapse={() => setExpanded(false)} />\n <RichTextExpandStage label={label} onCollapse={() => setExpanded(false)}>\n {surface}\n </RichTextExpandStage>\n </>\n ) : (\n surface\n )}\n {canImage ? (\n <AssetPicker\n open={imageOpen}\n onOpenChange={setImageOpen}\n value={null}\n accept={INLINE_IMAGE_ACCEPT}\n onChange={(assetId) => {\n setImageOpen(false)\n if (assetId) insertAssetImage(editor, assetId, assets)\n }}\n />\n ) : null}\n {canEmbed ? (\n <EmbedPicker\n open={embedOpen}\n onOpenChange={setEmbedOpen}\n allow={allow}\n onSelect={(manifest) => insertComponentEmbed(editor, manifest, { select: true })}\n />\n ) : null}\n </div>\n )\n}\n\n/**\n * EC-431 (AD-B) — the expanded editing surface's scroll body: the stage may be\n * wider than the text (that is the point — distraction-free writing, not more\n * pixels), so the PROSE COLUMN inside is constrained to a readable measure and\n * centered with `mx-auto`, which keeps the gutters symmetric at every stage\n * width. EC-436 widened the measure from the classic ~70ch to ~78ch in step\n * with the wider `prose` stage panel (owner feedback: \"the dialog + text looks\n * a bit narrow on a big screen\"). Applied to the `EditorContent` wrapper only\n * while expanded; it is also the scroll area, so the toolbar above it stays\n * put. Pinned by `expand.test.tsx` (the EC-391 class-contract pattern).\n */\nexport const RICH_TEXT_STAGE_CONTENT_CLASS =\n 'min-h-0 flex-1 overflow-y-auto px-6 py-6 sm:px-10 sm:py-8 [&>.ec-rte-content]:mx-auto [&>.ec-rte-content]:max-w-[78ch]'\n\n/**\n * The inline strip shown in the field row while the editor is expanded onto its\n * stage (the real editing surface lives there then). Clicking it returns — so\n * the collapsed field never appears as a broken empty box.\n */\nexport function RichTextExpandPlaceholder({ onCollapse }: { onCollapse: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onCollapse}\n className=\"flex min-h-[8rem] w-full items-center justify-center gap-2 px-3 py-6 text-sm text-muted-foreground transition-colors hover:bg-muted/40\"\n >\n <Minimize2 className=\"size-4\" />\n <Text size=\"sm\" tone=\"muted\">\n {translate('richText.expand.editingInSheet')}\n </Text>\n </button>\n )\n}\n\n/**\n * EC-436 (AD-1) — the expand stage's title. With a field label in reach it\n * composes \"<label> — <localized type>\" through the PARAMETERIZED\n * `richText.expand.titleWithField` message — never string concatenation in\n * code, because word order is a translation concern; without one (a custom\n * mount that passes no label) it falls back to the generic\n * `richText.expand.title`, unchanged. Exported for the `expand.test.tsx` pins.\n */\nexport function richTextStageTitle(label?: string): string {\n return label !== undefined && label.trim() !== ''\n ? translate('richText.expand.titleWithField', { field: label })\n : translate('richText.expand.title')\n}\n\n/**\n * EC-388/EC-431 — the expanded editing surface: the SAME editor, grown onto the\n * centered STAGE (ratified AD-B — writing is a primary activity, so it takes\n * the `stage` prose preset, never the peripheral side sheet) where long prose\n * (a privacy statement, a Freeform block) is comfortable to edit. It renders\n * through the shared {@link ExpandableSurface} primitive (portal-to-body,\n * backdrop, header close, layer-stacked Escape); the prose column inside is\n * constrained to the distraction-free measure\n * ({@link RICH_TEXT_STAGE_CONTENT_CLASS}). It is the same edit surface, not a\n * second form: no save/cancel of its own — edits flow through the editor's\n * existing value contract, and it is lightly dismissible (Escape / backdrop /\n * the toolbar's collapse toggle / the header close), the forms-never-modal\n * spirit applied. The `children` are the live toolbar + `EditorContent`;\n * unmounting them here returns the editor inline. The asset picker opened from\n * the stage's toolbar is a Radix dialog ABOVE the stage — it consumes Escape\n * first (the stage stays), per the EC-392 stacking rules.\n */\nfunction RichTextExpandStage({\n children,\n onCollapse,\n label,\n}: {\n children: ReactNode\n onCollapse: () => void\n /** The field's resolved display label — see {@link richTextStageTitle}. */\n label?: string\n}) {\n return (\n <ExpandableSurface\n title={richTextStageTitle(label)}\n closeLabel={translate('richText.expand.collapse')}\n variant=\"stage\"\n preset=\"prose\"\n onDismiss={onCollapse}\n >\n <div className=\"ec-rte flex min-h-0 flex-1 flex-col overflow-hidden bg-background\">\n {children}\n </div>\n </ExpandableSurface>\n )\n}\n"]}
1
+ {"version":3,"file":"editor.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/editor.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { Minimize2 } from 'lucide-react'\nimport type { RichTextDoc } from '@elytracms/core/rich-text'\nimport { ExpandableSurface, Text } from '@elytracms/studio/ui'\nimport { cn } from '../../../lib/utils'\nimport { localizedLabel, translate } from '../../../lib/i18n'\nimport { useOptionalCollectionView } from '../../collection-view/collection-view-context'\nimport { useOptionalAssets } from '../../assets/assets-store'\nimport { AssetPicker } from '../asset-picker'\nimport { buildRichTextExtensions } from './extensions'\nimport {\n blockSequenceValueFromDoc,\n isEffectivelyEmpty,\n isEquivalentToStored,\n normalizeEditorDoc,\n toEditorDoc,\n} from './value'\nimport { INLINE_IMAGE_ACCEPT, insertAssetImage, insertComponentEmbed, listEmbeddable } from './insert'\nimport { EmbedPicker } from './embed-picker'\nimport { getStudioRegistry } from '../../../lib/registry'\nimport { RichTextToolbar } from './toolbar'\nimport type { LinkTarget } from './link-control'\n\ninterface RichTextEditorProps {\n /** The stored value (v2 `{ version: 2, content }`; staging: v1/legacy forms). */\n value: unknown\n onChange: (value: unknown) => void\n readOnly?: boolean\n id?: string\n /** The field's embed `allow` list (component ids editors may embed). */\n allow?: string[]\n /**\n * EC-436 (AD-1) — the field's resolved localized display label, the SAME\n * string the field/prop row renders next to this editor. Used only to title\n * the expand stage (\"<label> — Rich text\" via the parameterized\n * `richText.expand.titleWithField` message, {@link richTextStageTitle});\n * absent → the generic `richText.expand.title` fallback.\n */\n label?: string\n}\n\n/**\n * The CMS rich-text field editor (EC-314) — a real TipTap editor over the shared\n * `@elytracms/core/rich-text` storage, replacing the plaintext textarea stub.\n *\n * It stays *controlled without churn*: the editor is created once from the initial\n * value; the stored value is only re-pushed into ProseMirror when it changes\n * externally (locale switch, reset, compare) and is not already equivalent to what\n * is on screen. On edit, output is pruned to the lean storage convention\n * ({@link normalizeEditorDoc}) and — crucially — when an edit lands the document back\n * on its stored meaning, the *exact stored value* is re-emitted so the field never\n * shows a spurious dirty state (the same \"commit-nothing-when-equivalent\" contract\n * the canvas inline editor uses).\n *\n * The image and component-embed pickers are owned here (not the toolbar) so both the\n * toolbar buttons and the `/` slash menu open the same modal — one insertion path.\n */\nexport function RichTextEditor({ value, onChange, readOnly = false, id, allow, label }: RichTextEditorProps) {\n // Ambient context for reference-aware insertion (null outside a collection view /\n // asset backend — the editor then degrades to external links and no image insert).\n const source = useOptionalCollectionView()\n const assets = useOptionalAssets()\n\n const [imageOpen, setImageOpen] = useState(false)\n const [embedOpen, setEmbedOpen] = useState(false)\n // EC-388/EC-431: whether the editor is promoted onto its centered expand stage.\n const [expanded, setExpanded] = useState(false)\n\n const canImage = assets != null\n // The `allow`-gated components this field may embed — drives both the toolbar\n // picker and the slash menu's direct per-component items (0.0.41).\n const embeddable = useMemo(\n () => listEmbeddable(allow, getStudioRegistry().registry.list()),\n [allow],\n )\n const canEmbed = embeddable.length > 0\n\n // Every linkable document across the workspace (excluding the asset rail — you link\n // to content, not to raw files). Recomputed only when the source changes.\n const linkTargets = useMemo<LinkTarget[]>(() => {\n if (!source) return []\n const out: LinkTarget[] = []\n for (const collection of source.collections) {\n if (collection.id === source.assetCollectionId) continue\n const collectionLabel = localizedLabel(collection.form?.labelSingular, collection.id)\n for (const doc of source.referenceCandidates(collection.id)) {\n const ref = { collection: collection.id, id: doc.id }\n out.push({ ref, label: source.titleOf(ref), collectionLabel })\n }\n }\n return out\n }, [source])\n\n // Latest value/onChange, read inside the stable onUpdate closure.\n const valueRef = useRef(value)\n valueRef.current = value\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n // What we last emitted upward — lets the sync effect ignore our own echoes.\n const lastEmitted = useRef<unknown>(value)\n\n // Media openers for the toolbar + slash menu. setState fns are stable, so these\n // stay valid for the life of the editor even though it's built once.\n const openImage = canImage ? () => setImageOpen(true) : undefined\n const openEmbed = canEmbed ? () => setEmbedOpen(true) : undefined\n\n const editor = useEditor({\n extensions: buildRichTextExtensions({\n slash: {\n onImage: openImage,\n // Direct per-component slash items (0.0.41) — insert with manifest defaults\n // and select, so the embed inspector opens onto the new node's props.\n embed: canEmbed\n ? {\n components: embeddable,\n insert: (ed, range, manifest) => {\n ed.chain().focus().deleteRange(range).run()\n insertComponentEmbed(ed, manifest, { select: true })\n },\n }\n : undefined,\n },\n }),\n content: toEditorDoc(value),\n editable: !readOnly,\n editorProps: {\n attributes: { class: 'ec-rte-content', ...(id ? { id } : {}) },\n },\n onUpdate: ({ editor }) => {\n const current = normalizeEditorDoc(editor.getJSON() as unknown as RichTextDoc)\n const stored = valueRef.current\n // Three outcomes: unchanged → re-emit the stored value verbatim (no dirty);\n // emptied → clear the field (undefined); otherwise → the new stamped value.\n const next = isEquivalentToStored(current, stored)\n ? stored\n : isEffectivelyEmpty(current)\n ? undefined\n : blockSequenceValueFromDoc(current)\n lastEmitted.current = next\n onChangeRef.current(next)\n },\n })\n\n // Reflect external value changes into the editor, never echoing our own emits.\n useEffect(() => {\n if (!editor) return\n if (value === lastEmitted.current) return\n const current = normalizeEditorDoc(editor.getJSON() as unknown as RichTextDoc)\n if (isEquivalentToStored(current, value)) return\n editor.commands.setContent(toEditorDoc(value), { emitUpdate: false })\n }, [editor, value])\n\n // Toggle editability without rebuilding the editor (preserves history/selection).\n useEffect(() => {\n editor?.setEditable(!readOnly)\n }, [editor, readOnly])\n\n if (!editor) return null\n\n // The editing surface — the toolbar (which carries the expand toggle) + the\n // editable content. Mounted inline OR inside the expand stage, never both. TipTap\n // preserves the editor instance across `EditorContent` remounts (its view DOM is\n // carried onto the new element), so relocating this surface keeps ONE editor\n // document and ONE value flow — the same instance, grown, not a second form.\n // While expanded, the toolbar stays put above the scrolling prose column\n // (the scroll area is the EditorContent wrapper).\n const surface = (\n <>\n {!readOnly ? (\n <RichTextToolbar\n editor={editor}\n targets={linkTargets}\n onOpenImage={openImage}\n onOpenEmbed={openEmbed}\n expanded={expanded}\n onToggleExpand={() => setExpanded((v) => !v)}\n />\n ) : null}\n <EditorContent editor={editor} className={cn(expanded && RICH_TEXT_STAGE_CONTENT_CLASS)} />\n </>\n )\n\n return (\n <div\n className={cn(\n 'ec-rte overflow-hidden rounded-md border border-input bg-background shadow-sm',\n 'focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/40',\n readOnly && 'opacity-70',\n )}\n >\n {expanded ? (\n <>\n <RichTextExpandPlaceholder onCollapse={() => setExpanded(false)} />\n <RichTextExpandStage label={label} onCollapse={() => setExpanded(false)}>\n {surface}\n </RichTextExpandStage>\n </>\n ) : (\n surface\n )}\n {canImage ? (\n <AssetPicker\n open={imageOpen}\n onOpenChange={setImageOpen}\n value={null}\n accept={INLINE_IMAGE_ACCEPT}\n onChange={(assetId) => {\n setImageOpen(false)\n if (assetId) insertAssetImage(editor, assetId, assets)\n }}\n />\n ) : null}\n {canEmbed ? (\n <EmbedPicker\n open={embedOpen}\n onOpenChange={setEmbedOpen}\n allow={allow}\n onSelect={(manifest) => insertComponentEmbed(editor, manifest, { select: true })}\n />\n ) : null}\n </div>\n )\n}\n\n/**\n * EC-431 (AD-B) — the expanded editing surface's scroll body: the stage may be\n * wider than the text (that is the point — distraction-free writing, not more\n * pixels), so the PROSE COLUMN inside is constrained to a readable measure and\n * centered with `mx-auto`, which keeps the gutters symmetric at every stage\n * width. EC-436 widened the measure from the classic ~70ch to ~78ch in step\n * with the wider `prose` stage panel (owner feedback: \"the dialog + text looks\n * a bit narrow on a big screen\"). Applied to the `EditorContent` wrapper only\n * while expanded; it is also the scroll area, so the toolbar above it stays\n * put. Pinned by `expand.test.tsx` (the EC-391 class-contract pattern).\n */\nexport const RICH_TEXT_STAGE_CONTENT_CLASS =\n 'min-h-0 flex-1 overflow-y-auto px-6 py-6 sm:px-10 sm:py-8 [&>.ec-rte-content]:mx-auto [&>.ec-rte-content]:max-w-[78ch]'\n\n/**\n * The inline strip shown in the field row while the editor is expanded onto its\n * stage (the real editing surface lives there then). Clicking it returns — so\n * the collapsed field never appears as a broken empty box.\n */\nexport function RichTextExpandPlaceholder({ onCollapse }: { onCollapse: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onCollapse}\n className=\"flex min-h-[8rem] w-full items-center justify-center gap-2 px-3 py-6 text-sm text-muted-foreground transition-colors hover:bg-muted/40\"\n >\n <Minimize2 className=\"size-4\" />\n <Text size=\"sm\" tone=\"muted\">\n {translate('richText.expand.editingInSheet')}\n </Text>\n </button>\n )\n}\n\n/**\n * EC-436 (AD-1) — the expand stage's title. With a field label in reach it\n * composes \"<label> — <localized type>\" through the PARAMETERIZED\n * `richText.expand.titleWithField` message — never string concatenation in\n * code, because word order is a translation concern; without one (a custom\n * mount that passes no label) it falls back to the generic\n * `richText.expand.title`, unchanged. Exported for the `expand.test.tsx` pins.\n */\nexport function richTextStageTitle(label?: string): string {\n return label !== undefined && label.trim() !== ''\n ? translate('richText.expand.titleWithField', { field: label })\n : translate('richText.expand.title')\n}\n\n/**\n * EC-388/EC-431 — the expanded editing surface: the SAME editor, grown onto the\n * centered STAGE (ratified AD-B — writing is a primary activity, so it takes\n * the `stage` prose preset, never the peripheral side sheet) where long prose\n * (a privacy statement, a Freeform block) is comfortable to edit. It renders\n * through the shared {@link ExpandableSurface} primitive (portal-to-body,\n * backdrop, header close, layer-stacked Escape); the prose column inside is\n * constrained to the distraction-free measure\n * ({@link RICH_TEXT_STAGE_CONTENT_CLASS}). It is the same edit surface, not a\n * second form: no save/cancel of its own — edits flow through the editor's\n * existing value contract, and it is lightly dismissible (Escape / backdrop /\n * the toolbar's collapse toggle / the header close), the forms-never-modal\n * spirit applied. The `children` are the live toolbar + `EditorContent`;\n * unmounting them here returns the editor inline. The asset picker opened from\n * the stage's toolbar is a Radix dialog ABOVE the stage — it consumes Escape\n * first (the stage stays), per the EC-392 stacking rules.\n */\nfunction RichTextExpandStage({\n children,\n onCollapse,\n label,\n}: {\n children: ReactNode\n onCollapse: () => void\n /** The field's resolved display label — see {@link richTextStageTitle}. */\n label?: string\n}) {\n return (\n <ExpandableSurface\n title={richTextStageTitle(label)}\n closeLabel={translate('richText.expand.collapse')}\n variant=\"stage\"\n preset=\"prose\"\n onDismiss={onCollapse}\n >\n <div className=\"ec-rte flex min-h-0 flex-1 flex-col overflow-hidden bg-background\">\n {children}\n </div>\n </ExpandableSurface>\n )\n}\n"]}
@@ -2,7 +2,7 @@ import type { ComponentManifest } from '@elytracms/core/component-registry';
2
2
  interface EmbedPickerProps {
3
3
  open: boolean;
4
4
  onOpenChange: (open: boolean) => void;
5
- /** The field's embed `allow` list (component ids). Empty/absent ⇒ any non-primitive. */
5
+ /** The field's declared embed `allow` (component ids); absent ⇒ the richText default (EC-584: any component that fills no layout role). */
6
6
  allow?: string[];
7
7
  onSelect: (manifest: ComponentManifest) => void;
8
8
  }
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from 'react';
3
3
  import { Blocks } from 'lucide-react';
4
- import { getStudioRegistry } from '@elytracms/studio/lib/registry';
4
+ import { getStudioRegistry } from '../../../lib/registry/index.js';
5
5
  import { Dialog, DialogContent, DialogHeader, DialogTitle, Input, Stack, Text, } from '@elytracms/studio/ui';
6
6
  import { localizedStringVariants } from '@elytracms/core/cms-core';
7
- import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
8
- import { cn } from '@elytracms/studio/lib/utils';
7
+ import { localizedLabel, translate } from '../../../lib/i18n/index.js';
8
+ import { cn } from '../../../lib/utils.js';
9
9
  import { listEmbeddable } from './insert.js';
10
10
  /**
11
11
  * The component-embed picker dialog (EC-314/EC-189): a searchable list of the
@@ -1 +1 @@
1
- {"version":3,"file":"embed-picker.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { Blocks } from 'lucide-react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n Input,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { listEmbeddable } from './insert'\n\ninterface EmbedPickerProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** The field's embed `allow` list (component ids). Empty/absent ⇒ any non-primitive. */\n allow?: string[]\n onSelect: (manifest: ComponentManifest) => void\n}\n\n/**\n * The component-embed picker dialog (EC-314/EC-189): a searchable list of the\n * components permitted for inline embedding. Rendered once at the editor level and\n * opened by the toolbar button or the `/embed` slash command.\n */\nexport function EmbedPicker({ open, onOpenChange, allow, onSelect }: EmbedPickerProps) {\n const [query, setQuery] = useState('')\n\n const embeddable = useMemo(\n () => listEmbeddable(allow, getStudioRegistry().registry.list()),\n [allow],\n )\n\n const results = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return embeddable\n return embeddable.filter((m) =>\n [m.id, ...localizedStringVariants(m.title)].some((s) => s.toLowerCase().includes(q)),\n )\n }, [embeddable, query])\n\n function choose(manifest: ComponentManifest) {\n onSelect(manifest)\n onOpenChange(false)\n setQuery('')\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>{translate('richText.embed.add')}</DialogTitle>\n </DialogHeader>\n <Stack gap=\"2\">\n <Input\n autoFocus\n value={query}\n placeholder={translate('richText.link.documentSearch')}\n onChange={(e) => setQuery(e.target.value)}\n />\n <Stack gap=\"none\" className=\"max-h-72 overflow-y-auto\">\n {results.length === 0 ? (\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"px-1 py-2\">\n {translate('richText.embed.empty')}\n </Text>\n ) : (\n results.map((manifest) => (\n <button\n key={manifest.id}\n type=\"button\"\n onClick={() => choose(manifest)}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-md px-2 py-2 text-left',\n 'hover:bg-muted focus-visible:bg-muted outline-none',\n )}\n >\n <Blocks className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"truncate text-sm font-medium\">{localizedLabel(manifest.title, manifest.id)}</span>\n <span className=\"truncate text-xs text-muted-foreground\">{manifest.id}</span>\n </span>\n </button>\n ))\n )}\n </Stack>\n </Stack>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
1
+ {"version":3,"file":"embed-picker.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { Blocks } from 'lucide-react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { getStudioRegistry } from '../../../lib/registry'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n Input,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { localizedLabel, translate } from '../../../lib/i18n'\nimport { cn } from '../../../lib/utils'\nimport { listEmbeddable } from './insert'\n\ninterface EmbedPickerProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** The field's declared embed `allow` (component ids); absent ⇒ the richText default (EC-584: any component that fills no layout role). */\n allow?: string[]\n onSelect: (manifest: ComponentManifest) => void\n}\n\n/**\n * The component-embed picker dialog (EC-314/EC-189): a searchable list of the\n * components permitted for inline embedding. Rendered once at the editor level and\n * opened by the toolbar button or the `/embed` slash command.\n */\nexport function EmbedPicker({ open, onOpenChange, allow, onSelect }: EmbedPickerProps) {\n const [query, setQuery] = useState('')\n\n const embeddable = useMemo(\n () => listEmbeddable(allow, getStudioRegistry().registry.list()),\n [allow],\n )\n\n const results = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return embeddable\n return embeddable.filter((m) =>\n [m.id, ...localizedStringVariants(m.title)].some((s) => s.toLowerCase().includes(q)),\n )\n }, [embeddable, query])\n\n function choose(manifest: ComponentManifest) {\n onSelect(manifest)\n onOpenChange(false)\n setQuery('')\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>{translate('richText.embed.add')}</DialogTitle>\n </DialogHeader>\n <Stack gap=\"2\">\n <Input\n autoFocus\n value={query}\n placeholder={translate('richText.link.documentSearch')}\n onChange={(e) => setQuery(e.target.value)}\n />\n <Stack gap=\"none\" className=\"max-h-72 overflow-y-auto\">\n {results.length === 0 ? (\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"px-1 py-2\">\n {translate('richText.embed.empty')}\n </Text>\n ) : (\n results.map((manifest) => (\n <button\n key={manifest.id}\n type=\"button\"\n onClick={() => choose(manifest)}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-md px-2 py-2 text-left',\n 'hover:bg-muted focus-visible:bg-muted outline-none',\n )}\n >\n <Blocks className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"truncate text-sm font-medium\">{localizedLabel(manifest.title, manifest.id)}</span>\n <span className=\"truncate text-xs text-muted-foreground\">{manifest.id}</span>\n </span>\n </button>\n ))\n )}\n </Stack>\n </Stack>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
@@ -3,9 +3,9 @@ import { Lock } from 'lucide-react';
3
3
  import { isStaticProp, staticProp } from '@elytracms/core/project-graph';
4
4
  import { stableStringify } from '@elytracms/core/cms-core';
5
5
  import { Badge, ExpandableSurface, Field, Row, Separator, Stack, Text, } from '@elytracms/studio/ui';
6
- import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
7
- import { humanizeName } from '@elytracms/studio/lib/utils';
8
- import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
6
+ import { localizedLabel, translate } from '../../../lib/i18n/index.js';
7
+ import { humanizeName } from '../../../lib/utils.js';
8
+ import { componentDisplayName } from '../../../lib/persona/labels.js';
9
9
  import { PropValueControl } from '../prop-fields.js';
10
10
  import { visiblePropFields } from '../prop-visibility.js';
11
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"embed-props.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-props.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Lock } from 'lucide-react'\nimport { isStaticProp, staticProp } from '@elytracms/core/project-graph'\nimport { stableStringify } from '@elytracms/core/cms-core'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport {\n Badge,\n ExpandableSurface,\n Field,\n Row,\n Separator,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { humanizeName } from '@elytracms/studio/lib/utils'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { PropValueControl } from '../prop-fields'\nimport { visiblePropFields } from '../prop-visibility'\n\n/**\n * Prop editing for rich-text component embeds (EC-314 phase 4; stage surface\n * EC-430/AD-D).\n *\n * An embedded component is a full `ComponentNode` stored verbatim at\n * `componentEmbed.attrs.node`. The {@link EmbedPropsStage} edits that node's *props*\n * through the SAME EC-190 \"props-as-fields\" control stack the canvas inspector and\n * `blocks` field use ({@link PropValueControl}) — asset props open the library\n * picker, relation props the document picker, etc. — never a parallel form system.\n *\n * One surface, reachable everywhere (ratified AD-D, 2026-07-16): \"Edit settings\" on\n * an embed card opens the centered stage (`ExpandableSurface variant=\"stage\"\n * preset=\"form\"`) identically from the CANVAS tab and the form view. It replaced\n * BOTH halves of the 0.0.41 fork — the selection-driven sidebar swap (which only\n * rendered in the form view, leaving the canvas tab's \"Edit settings\" dead) and the\n * modal fallback.\n *\n * Value contract (unchanged from the 0.0.41 sidebar inspector): write-back is\n * **immediate per prop change** — each edit commits through `onChange` (one\n * ProseMirror `updateAttributes` transaction upstream, undoable), guarded by\n * {@link resolveEmbedWrite} so an unchanged write never dirties the field. There is\n * no draft+Apply and no save/cancel of its own: the stage is the same value flow\n * relocated, lightly dismissible (Escape / backdrop / header close) per the\n * forms-never-modal spirit. The dialog fork's draft+Apply semantics died with it.\n *\n * `blocks`-typed props (nested composition) are NOT edited here — that needs the\n * canvas, not a form. They are excluded from the form and surfaced as a note.\n */\n\n/** Set a prop to a fresh static value, preserving identity, slots, and sibling props. */\nexport function setComponentNodeProp(\n node: ComponentNode,\n name: string,\n value: JsonValue,\n): ComponentNode {\n return { ...node, props: { ...node.props, [name]: staticProp(value) } }\n}\n\n/** Remove a prop (revert to the manifest default), preserving everything else. */\nexport function clearComponentNodeProp(node: ComponentNode, name: string): ComponentNode {\n const props = { ...node.props }\n delete props[name]\n return { ...node, props }\n}\n\n/**\n * Key-order-insensitive structural equality — a redundant \"save\" must not write. The\n * core `stableStringify` (EC-586: one serialization; this used to be a studio copy).\n * Deliberately STRUCTURAL, not `semanticValueEqual`: a no-op guard must never swallow a\n * real edit that a tolerant reader would read as \"the same\".\n */\nexport function componentNodesEqual(a: ComponentNode, b: ComponentNode): boolean {\n return stableStringify(a) === stableStringify(b)\n}\n\n/**\n * The no-dirty guard for immediate per-prop write-back (0.0.41 sidebar inspector,\n * inherited verbatim by the EC-430 stage form): the node to commit for an edit, or\n * `undefined` when it is a structural no-op so an unchanged write never dirties the\n * field ({@link componentNodesEqual}), applied per keystroke.\n */\nexport function resolveEmbedWrite(current: ComponentNode, next: ComponentNode): ComponentNode | undefined {\n return componentNodesEqual(next, current) ? undefined : next\n}\n\n/** The manifest's `blocks` (composition) props — excluded from the form, shown as a note. */\nexport function blocksEmbedProps(manifest: ComponentManifest): Array<[string, PropField]> {\n return Object.entries(manifest.props).filter(([, spec]) => spec.type === 'blocks')\n}\n\n/**\n * A lean one-line summary of an embed's static scalar props (`Label: value`,\n * up to three), for the card. Skips unset props and structured values (refs /\n * objects / arrays) — those are edited in the stage form, not summarised.\n */\nexport function embedPropsSummary(\n node: ComponentNode,\n manifest?: ComponentManifest,\n): string | undefined {\n const parts: string[] = []\n for (const [name, value] of Object.entries(node.props ?? {})) {\n if (!isStaticProp(value)) continue\n const raw = value.value\n if (raw === null || raw === undefined || typeof raw === 'object') continue\n const label = manifest?.props[name]?.form?.label\n const labelText = label ? localizedLabel(label, name) : humanizeName(name)\n parts.push(`${labelText}: ${String(raw)}`)\n if (parts.length >= 3) break\n }\n return parts.length > 0 ? parts.join(' · ') : undefined\n}\n\nexport interface EmbedStageFormProps {\n /** The embedded component node whose props are being edited (the STORED node). */\n node: ComponentNode\n /** Its manifest — drives which prop fields render. */\n manifest: ComponentManifest\n /**\n * Commit an edited node. Upstream writes it through one ProseMirror\n * `updateAttributes` transaction (undoable); the no-dirty guard has already been\n * applied here, so this only fires on a real structural change.\n */\n onChange: (node: ComponentNode) => void\n}\n\n/**\n * The embed prop form body — pure of surface chrome so it smoke-renders in the node\n * test env (the stage itself portals and renders `null` without a DOM).\n *\n * EC-393 parity: rows filter through the SAME {@link visiblePropFields} core the\n * canvas property inspector uses — a predicate-hidden row (`form.hidden` over the\n * sibling prop values) unmounts/remounts live as its driver prop flips, because the\n * committed node re-renders this form. EC-395 parity: `form.readOnly` renders the\n * document form's Lock badge next to the label and disables the delegated control\n * (`PropValueControl` reads the same flag); `form.control` and `form.description`\n * route through the same delegation.\n */\nexport function EmbedStageForm({ node, manifest, onChange }: EmbedStageFormProps) {\n const visible = visiblePropFields(manifest, node)\n const blocks = blocksEmbedProps(manifest)\n\n function set(name: string, value: JsonValue) {\n const next = resolveEmbedWrite(node, setComponentNodeProp(node, name, value))\n if (next) onChange(next)\n }\n function clear(name: string) {\n const next = resolveEmbedWrite(node, clearComponentNodeProp(node, name))\n if (next) onChange(next)\n }\n\n return (\n <Stack gap=\"4\">\n {visible.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('richText.embed.noProps')}\n </Text>\n ) : (\n visible.map(([name, spec]) => {\n const id = `embed-${node.id}-${name}`\n return (\n <Field\n key={name}\n label={\n spec.form?.readOnly === true ? (\n // EC-395: the SAME read-only badge the document form and the canvas\n // prop inspector render next to a locked label — identical icon,\n // i18n key, and `Badge` variant, never a bespoke treatment.\n <Row gap=\"2\">\n <span>{localizedLabel(spec.form?.label, name)}</span>\n <Badge variant=\"outline\">\n <Lock className=\"size-3\" /> {translate('cms.editor.readOnlyBadge')}\n </Badge>\n </Row>\n ) : (\n localizedLabel(spec.form?.label, name)\n )\n }\n htmlFor={id}\n hint={localizedLabel(spec.form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec}\n value={node.props?.[name]}\n onSet={(v) => set(name, v)}\n onClear={() => clear(name)}\n />\n </Field>\n )\n })\n )}\n {blocks.length > 0 ? (\n <>\n <Separator />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('richText.embed.blocksNote')}\n </Text>\n </>\n ) : null}\n </Stack>\n )\n}\n\nexport interface EmbedPropsStageProps extends EmbedStageFormProps {\n /** Dismiss request (Escape / backdrop / header close). The caller owns the open state. */\n onDismiss: () => void\n}\n\n/**\n * The stage-body focus handoff (a module-level constant so React never re-invokes it\n * on re-renders — a per-keystroke re-invocation would churn the guard). The stage\n * opens from a card INSIDE a focused TipTap editor, and prosemirror-view\n * preventDefaults every Escape while its contenteditable holds focus\n * (`capturekeys.ts` — its \"dangerous default\" backdrop map), which the surface's\n * EC-392 bridge reads as \"handled by a layer above\" — so Escape would never dismiss,\n * and stray typing would land in the covered document. Moving focus into the stage\n * on mount hands Escape to the layer stack; the `focusin` guard keeps it there,\n * because ProseMirror re-asserts focus on its editable off deferred click handling\n * and window-refocus events. The guard is NOT a focus trap: it only reclaims from a\n * ProseMirror editable OUTSIDE the stage (unreachable to the user behind the\n * backdrop — only programmatic steals hit this), never from the stage's own controls\n * (incl. nested rich-text prop editors, `el.contains`) or a picker dialog above.\n * Per-caller policy, deliberately NOT in ExpandableSurface: the rich-text prose\n * stage RELOCATES the editor, so there the editor keeping focus is the point.\n * React 19 ref-cleanup unregisters the guard when the stage unmounts.\n */\nconst focusStageBody = (el: HTMLDivElement | null) => {\n if (!el) return undefined\n el.focus()\n const guard = () => {\n const active = document.activeElement\n if (el.contains(active)) return\n if (active instanceof HTMLElement && active.closest('.ProseMirror')) el.focus()\n }\n document.addEventListener('focusin', guard)\n return () => document.removeEventListener('focusin', guard)\n}\n\n/**\n * The embed prop inspector on the centered stage (EC-430/AD-D) — mounted by the\n * embed card's \"Edit settings\" from ANY rich-text surface (form view, canvas-tab\n * prop inspector, or inside the expanded rich-text stage — a later mount rides the\n * EC-392 layer stack above it, and pickers opened from these controls stack above\n * this stage in turn). Hook-free by construction so the geometry contract is\n * pinnable by direct invocation in the node test env.\n */\nexport function EmbedPropsStage({ node, manifest, onChange, onDismiss }: EmbedPropsStageProps) {\n return (\n <ExpandableSurface\n variant=\"stage\"\n preset=\"form\"\n title={translate('richText.embed.editTitle', {\n name: componentDisplayName(node.componentId, manifest),\n })}\n closeLabel={translate('common.close')}\n onDismiss={onDismiss}\n >\n <div\n ref={focusStageBody}\n tabIndex={-1}\n className=\"min-h-0 flex-1 overflow-y-auto p-6 outline-none\"\n >\n <EmbedStageForm node={node} manifest={manifest} onChange={onChange} />\n </div>\n </ExpandableSurface>\n )\n}\n"]}
1
+ {"version":3,"file":"embed-props.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-props.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Lock } from 'lucide-react'\nimport { isStaticProp, staticProp } from '@elytracms/core/project-graph'\nimport { stableStringify } from '@elytracms/core/cms-core'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport {\n Badge,\n ExpandableSurface,\n Field,\n Row,\n Separator,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '../../../lib/i18n'\nimport { humanizeName } from '../../../lib/utils'\nimport { componentDisplayName } from '../../../lib/persona/labels'\nimport { PropValueControl } from '../prop-fields'\nimport { visiblePropFields } from '../prop-visibility'\n\n/**\n * Prop editing for rich-text component embeds (EC-314 phase 4; stage surface\n * EC-430/AD-D).\n *\n * An embedded component is a full `ComponentNode` stored verbatim at\n * `componentEmbed.attrs.node`. The {@link EmbedPropsStage} edits that node's *props*\n * through the SAME EC-190 \"props-as-fields\" control stack the canvas inspector and\n * `blocks` field use ({@link PropValueControl}) — asset props open the library\n * picker, relation props the document picker, etc. — never a parallel form system.\n *\n * One surface, reachable everywhere (ratified AD-D, 2026-07-16): \"Edit settings\" on\n * an embed card opens the centered stage (`ExpandableSurface variant=\"stage\"\n * preset=\"form\"`) identically from the CANVAS tab and the form view. It replaced\n * BOTH halves of the 0.0.41 fork — the selection-driven sidebar swap (which only\n * rendered in the form view, leaving the canvas tab's \"Edit settings\" dead) and the\n * modal fallback.\n *\n * Value contract (unchanged from the 0.0.41 sidebar inspector): write-back is\n * **immediate per prop change** — each edit commits through `onChange` (one\n * ProseMirror `updateAttributes` transaction upstream, undoable), guarded by\n * {@link resolveEmbedWrite} so an unchanged write never dirties the field. There is\n * no draft+Apply and no save/cancel of its own: the stage is the same value flow\n * relocated, lightly dismissible (Escape / backdrop / header close) per the\n * forms-never-modal spirit. The dialog fork's draft+Apply semantics died with it.\n *\n * `blocks`-typed props (nested composition) are NOT edited here — that needs the\n * canvas, not a form. They are excluded from the form and surfaced as a note.\n */\n\n/** Set a prop to a fresh static value, preserving identity, slots, and sibling props. */\nexport function setComponentNodeProp(\n node: ComponentNode,\n name: string,\n value: JsonValue,\n): ComponentNode {\n return { ...node, props: { ...node.props, [name]: staticProp(value) } }\n}\n\n/** Remove a prop (revert to the manifest default), preserving everything else. */\nexport function clearComponentNodeProp(node: ComponentNode, name: string): ComponentNode {\n const props = { ...node.props }\n delete props[name]\n return { ...node, props }\n}\n\n/**\n * Key-order-insensitive structural equality — a redundant \"save\" must not write. The\n * core `stableStringify` (EC-586: one serialization; this used to be a studio copy).\n * Deliberately STRUCTURAL, not `semanticValueEqual`: a no-op guard must never swallow a\n * real edit that a tolerant reader would read as \"the same\".\n */\nexport function componentNodesEqual(a: ComponentNode, b: ComponentNode): boolean {\n return stableStringify(a) === stableStringify(b)\n}\n\n/**\n * The no-dirty guard for immediate per-prop write-back (0.0.41 sidebar inspector,\n * inherited verbatim by the EC-430 stage form): the node to commit for an edit, or\n * `undefined` when it is a structural no-op so an unchanged write never dirties the\n * field ({@link componentNodesEqual}), applied per keystroke.\n */\nexport function resolveEmbedWrite(current: ComponentNode, next: ComponentNode): ComponentNode | undefined {\n return componentNodesEqual(next, current) ? undefined : next\n}\n\n/** The manifest's `blocks` (composition) props — excluded from the form, shown as a note. */\nexport function blocksEmbedProps(manifest: ComponentManifest): Array<[string, PropField]> {\n return Object.entries(manifest.props).filter(([, spec]) => spec.type === 'blocks')\n}\n\n/**\n * A lean one-line summary of an embed's static scalar props (`Label: value`,\n * up to three), for the card. Skips unset props and structured values (refs /\n * objects / arrays) — those are edited in the stage form, not summarised.\n */\nexport function embedPropsSummary(\n node: ComponentNode,\n manifest?: ComponentManifest,\n): string | undefined {\n const parts: string[] = []\n for (const [name, value] of Object.entries(node.props ?? {})) {\n if (!isStaticProp(value)) continue\n const raw = value.value\n if (raw === null || raw === undefined || typeof raw === 'object') continue\n const label = manifest?.props[name]?.form?.label\n const labelText = label ? localizedLabel(label, name) : humanizeName(name)\n parts.push(`${labelText}: ${String(raw)}`)\n if (parts.length >= 3) break\n }\n return parts.length > 0 ? parts.join(' · ') : undefined\n}\n\nexport interface EmbedStageFormProps {\n /** The embedded component node whose props are being edited (the STORED node). */\n node: ComponentNode\n /** Its manifest — drives which prop fields render. */\n manifest: ComponentManifest\n /**\n * Commit an edited node. Upstream writes it through one ProseMirror\n * `updateAttributes` transaction (undoable); the no-dirty guard has already been\n * applied here, so this only fires on a real structural change.\n */\n onChange: (node: ComponentNode) => void\n}\n\n/**\n * The embed prop form body — pure of surface chrome so it smoke-renders in the node\n * test env (the stage itself portals and renders `null` without a DOM).\n *\n * EC-393 parity: rows filter through the SAME {@link visiblePropFields} core the\n * canvas property inspector uses — a predicate-hidden row (`form.hidden` over the\n * sibling prop values) unmounts/remounts live as its driver prop flips, because the\n * committed node re-renders this form. EC-395 parity: `form.readOnly` renders the\n * document form's Lock badge next to the label and disables the delegated control\n * (`PropValueControl` reads the same flag); `form.control` and `form.description`\n * route through the same delegation.\n */\nexport function EmbedStageForm({ node, manifest, onChange }: EmbedStageFormProps) {\n const visible = visiblePropFields(manifest, node)\n const blocks = blocksEmbedProps(manifest)\n\n function set(name: string, value: JsonValue) {\n const next = resolveEmbedWrite(node, setComponentNodeProp(node, name, value))\n if (next) onChange(next)\n }\n function clear(name: string) {\n const next = resolveEmbedWrite(node, clearComponentNodeProp(node, name))\n if (next) onChange(next)\n }\n\n return (\n <Stack gap=\"4\">\n {visible.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('richText.embed.noProps')}\n </Text>\n ) : (\n visible.map(([name, spec]) => {\n const id = `embed-${node.id}-${name}`\n return (\n <Field\n key={name}\n label={\n spec.form?.readOnly === true ? (\n // EC-395: the SAME read-only badge the document form and the canvas\n // prop inspector render next to a locked label — identical icon,\n // i18n key, and `Badge` variant, never a bespoke treatment.\n <Row gap=\"2\">\n <span>{localizedLabel(spec.form?.label, name)}</span>\n <Badge variant=\"outline\">\n <Lock className=\"size-3\" /> {translate('cms.editor.readOnlyBadge')}\n </Badge>\n </Row>\n ) : (\n localizedLabel(spec.form?.label, name)\n )\n }\n htmlFor={id}\n hint={localizedLabel(spec.form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec}\n value={node.props?.[name]}\n onSet={(v) => set(name, v)}\n onClear={() => clear(name)}\n />\n </Field>\n )\n })\n )}\n {blocks.length > 0 ? (\n <>\n <Separator />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('richText.embed.blocksNote')}\n </Text>\n </>\n ) : null}\n </Stack>\n )\n}\n\nexport interface EmbedPropsStageProps extends EmbedStageFormProps {\n /** Dismiss request (Escape / backdrop / header close). The caller owns the open state. */\n onDismiss: () => void\n}\n\n/**\n * The stage-body focus handoff (a module-level constant so React never re-invokes it\n * on re-renders — a per-keystroke re-invocation would churn the guard). The stage\n * opens from a card INSIDE a focused TipTap editor, and prosemirror-view\n * preventDefaults every Escape while its contenteditable holds focus\n * (`capturekeys.ts` — its \"dangerous default\" backdrop map), which the surface's\n * EC-392 bridge reads as \"handled by a layer above\" — so Escape would never dismiss,\n * and stray typing would land in the covered document. Moving focus into the stage\n * on mount hands Escape to the layer stack; the `focusin` guard keeps it there,\n * because ProseMirror re-asserts focus on its editable off deferred click handling\n * and window-refocus events. The guard is NOT a focus trap: it only reclaims from a\n * ProseMirror editable OUTSIDE the stage (unreachable to the user behind the\n * backdrop — only programmatic steals hit this), never from the stage's own controls\n * (incl. nested rich-text prop editors, `el.contains`) or a picker dialog above.\n * Per-caller policy, deliberately NOT in ExpandableSurface: the rich-text prose\n * stage RELOCATES the editor, so there the editor keeping focus is the point.\n * React 19 ref-cleanup unregisters the guard when the stage unmounts.\n */\nconst focusStageBody = (el: HTMLDivElement | null) => {\n if (!el) return undefined\n el.focus()\n const guard = () => {\n const active = document.activeElement\n if (el.contains(active)) return\n if (active instanceof HTMLElement && active.closest('.ProseMirror')) el.focus()\n }\n document.addEventListener('focusin', guard)\n return () => document.removeEventListener('focusin', guard)\n}\n\n/**\n * The embed prop inspector on the centered stage (EC-430/AD-D) — mounted by the\n * embed card's \"Edit settings\" from ANY rich-text surface (form view, canvas-tab\n * prop inspector, or inside the expanded rich-text stage — a later mount rides the\n * EC-392 layer stack above it, and pickers opened from these controls stack above\n * this stage in turn). Hook-free by construction so the geometry contract is\n * pinnable by direct invocation in the node test env.\n */\nexport function EmbedPropsStage({ node, manifest, onChange, onDismiss }: EmbedPropsStageProps) {\n return (\n <ExpandableSurface\n variant=\"stage\"\n preset=\"form\"\n title={translate('richText.embed.editTitle', {\n name: componentDisplayName(node.componentId, manifest),\n })}\n closeLabel={translate('common.close')}\n onDismiss={onDismiss}\n >\n <div\n ref={focusStageBody}\n tabIndex={-1}\n className=\"min-h-0 flex-1 overflow-y-auto p-6 outline-none\"\n >\n <EmbedStageForm node={node} manifest={manifest} onChange={onChange} />\n </div>\n </ExpandableSurface>\n )\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import StarterKit from '@tiptap/starter-kit';
2
2
  import { Placeholder } from '@tiptap/extensions';
3
- import { translate } from '@elytracms/studio/lib/i18n';
3
+ import { translate } from '../../../lib/i18n/index.js';
4
4
  import { RefLink } from './ref-link.js';
5
5
  import { FontSize } from './font-size.js';
6
6
  import { AssetImage } from './asset-image.js';
@@ -1 +1 @@
1
- {"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAA2C,EAAE;IACnF,OAAO;QACL,UAAU,CAAC,SAAS,CAAC;YACnB,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;SACxC,CAAC;QACF,OAAO,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAC5E,QAAQ;QACR,UAAU;QACV,cAAc;QACd,kBAAkB;QAClB,WAAW,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACzE,kBAAkB,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;KACxC,CAAA;AACH,CAAC","sourcesContent":["import StarterKit from '@tiptap/starter-kit'\nimport { Placeholder } from '@tiptap/extensions'\nimport type { Extensions } from '@tiptap/react'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { RefLink } from './ref-link'\nimport { FontSize } from './font-size'\nimport { AssetImage } from './asset-image'\nimport { ComponentEmbed } from './component-embed'\nimport { UnknownNodeCarrier } from './unknown-node'\nimport { createSlashCommand } from './slash-command'\nimport type { SlashCommandOptions } from './slash-command'\n\n/**\n * The TipTap extension set for the CMS rich-text editor (EC-314).\n *\n * Deliberately matches the storage vocabulary (`@elytracms/core/rich-text` schema)\n * one-for-one so every editor document round-trips through `parseRichTextValue` /\n * the delivery renderer with no format change:\n *\n * - **StarterKit** supplies `doc/paragraph/text/heading/bulletList/orderedList/\n * listItem/blockquote/codeBlock/hardBreak/horizontalRule` and the marks\n * `bold/italic/code/strike/underline` — all ProseMirror-standard names identical\n * to storage. Its bundled `link` is disabled and replaced by {@link RefLink} so\n * internal-document links can carry a structured `ref` (EC-290). Headings allow\n * all six levels so existing `h4`–`h6` content is preserved even though the\n * toolbar surfaces only 1–3.\n * - {@link AssetImage} / {@link ComponentEmbed} declare the two reference-carrying\n * atoms (asset image, embedded registry component) so existing content of those\n * kinds survives a round-trip verbatim rather than being dropped by the schema.\n * - {@link UnknownNodeCarrier} (EC-547) is the editor-only opaque atom the\n * storage⇄editor projection wraps any OTHER stored node type into, so the\n * closed ProseMirror schema can never drop a node the flow editor does not\n * natively edit — the EC-500 D3 round-trip law.\n * - {@link FontSize} declares the semantic font-size mark (EC-411) so a sized run\n * round-trips (an undeclared mark would be silently dropped on load).\n *\n * No `allow` gating lives here — that governs *insertion* (the embed picker) and\n * *validation* (`validateRichTextEmbeds`), not which nodes the schema tolerates.\n *\n * `slash` wires the Notion-style `/` menu to the editor's shared image/embed pickers\n * (omit an action to hide that slash item).\n */\nexport function buildRichTextExtensions(options: { slash?: SlashCommandOptions } = {}): Extensions {\n return [\n StarterKit.configure({\n link: false,\n heading: { levels: [1, 2, 3, 4, 5, 6] },\n }),\n RefLink.configure({ openOnClick: false, autolink: true, linkOnPaste: true }),\n FontSize,\n AssetImage,\n ComponentEmbed,\n UnknownNodeCarrier,\n Placeholder.configure({ placeholder: translate('richText.placeholder') }),\n createSlashCommand(options.slash ?? {}),\n ]\n}\n"]}
1
+ {"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAA2C,EAAE;IACnF,OAAO;QACL,UAAU,CAAC,SAAS,CAAC;YACnB,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;SACxC,CAAC;QACF,OAAO,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAC5E,QAAQ;QACR,UAAU;QACV,cAAc;QACd,kBAAkB;QAClB,WAAW,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACzE,kBAAkB,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;KACxC,CAAA;AACH,CAAC","sourcesContent":["import StarterKit from '@tiptap/starter-kit'\nimport { Placeholder } from '@tiptap/extensions'\nimport type { Extensions } from '@tiptap/react'\nimport { translate } from '../../../lib/i18n'\nimport { RefLink } from './ref-link'\nimport { FontSize } from './font-size'\nimport { AssetImage } from './asset-image'\nimport { ComponentEmbed } from './component-embed'\nimport { UnknownNodeCarrier } from './unknown-node'\nimport { createSlashCommand } from './slash-command'\nimport type { SlashCommandOptions } from './slash-command'\n\n/**\n * The TipTap extension set for the CMS rich-text editor (EC-314).\n *\n * Deliberately matches the storage vocabulary (`@elytracms/core/rich-text` schema)\n * one-for-one so every editor document round-trips through `parseRichTextValue` /\n * the delivery renderer with no format change:\n *\n * - **StarterKit** supplies `doc/paragraph/text/heading/bulletList/orderedList/\n * listItem/blockquote/codeBlock/hardBreak/horizontalRule` and the marks\n * `bold/italic/code/strike/underline` — all ProseMirror-standard names identical\n * to storage. Its bundled `link` is disabled and replaced by {@link RefLink} so\n * internal-document links can carry a structured `ref` (EC-290). Headings allow\n * all six levels so existing `h4`–`h6` content is preserved even though the\n * toolbar surfaces only 1–3.\n * - {@link AssetImage} / {@link ComponentEmbed} declare the two reference-carrying\n * atoms (asset image, embedded registry component) so existing content of those\n * kinds survives a round-trip verbatim rather than being dropped by the schema.\n * - {@link UnknownNodeCarrier} (EC-547) is the editor-only opaque atom the\n * storage⇄editor projection wraps any OTHER stored node type into, so the\n * closed ProseMirror schema can never drop a node the flow editor does not\n * natively edit — the EC-500 D3 round-trip law.\n * - {@link FontSize} declares the semantic font-size mark (EC-411) so a sized run\n * round-trips (an undeclared mark would be silently dropped on load).\n *\n * No `allow` gating lives here — that governs *insertion* (the embed picker) and\n * *validation* (`validateRichTextEmbeds`), not which nodes the schema tolerates.\n *\n * `slash` wires the Notion-style `/` menu to the editor's shared image/embed pickers\n * (omit an action to hide that slash item).\n */\nexport function buildRichTextExtensions(options: { slash?: SlashCommandOptions } = {}): Extensions {\n return [\n StarterKit.configure({\n link: false,\n heading: { levels: [1, 2, 3, 4, 5, 6] },\n }),\n RefLink.configure({ openOnClick: false, autolink: true, linkOnPaste: true }),\n FontSize,\n AssetImage,\n ComponentEmbed,\n UnknownNodeCarrier,\n Placeholder.configure({ placeholder: translate('richText.placeholder') }),\n createSlashCommand(options.slash ?? {}),\n ]\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { Editor } from '@tiptap/react';
2
2
  import type { RichTextFontSize } from '@elytracms/core/rich-text';
3
- import type { MessageKey } from '@elytracms/studio/lib/i18n';
3
+ import type { MessageKey } from '../../../lib/i18n/index.js';
4
4
  /**
5
5
  * A font-size preset in the toolbar's dropdown (EC-411). `size: null` is the
6
6
  * markless *normal* step — selecting it REMOVES the mark rather than storing a value.
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ALargeSmall, Check, ChevronDown } from 'lucide-react';
3
3
  import { FONT_SIZE_MARK_TYPE } from '@elytracms/core/rich-text';
4
4
  import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@elytracms/studio/ui';
5
- import { cn } from '@elytracms/studio/lib/utils';
6
- import { translate } from '@elytracms/studio/lib/i18n';
5
+ import { cn } from '../../../lib/utils.js';
6
+ import { translate } from '../../../lib/i18n/index.js';
7
7
  import { RICH_TEXT_MENU_TRIGGER_CLASS } from './toolbar.js';
8
8
  /** The semantic scale the toolbar offers, ascending: small · normal · large · x-large. */
9
9
  export const FONT_SIZE_PRESETS = [
@@ -1 +1 @@
1
- {"version":3,"file":"font-size-control.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/font-size-control.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAE/D,OAAO,EACL,MAAM,EACN,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAEtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAA;AAWxD,0FAA0F;AAC1F,MAAM,CAAC,MAAM,iBAAiB,GAA8B;IAC1D,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,yBAAyB,EAAE;IACnD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,0BAA0B,EAAE;IACpD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,yBAAyB,EAAE;IACnD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,0BAA0B,EAAE;CACrD,CAAA;AAED,0FAA0F;AAC1F,MAAM,UAAU,cAAc,CAAC,MAAsB,EAAE,UAAmC;IACxF,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU,CAAA;AACnC,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,IAA6B;IAC/E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAA;IACpC,IAAI,IAAI;QAAE,KAAK,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAA;;QACvD,KAAK,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC,GAAG,EAAE,CAAA;AACjD,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,UAAU,EAAwB;IAC1E,OAAO,CACL,MAAC,YAAY,eACX,KAAC,mBAAmB,IAAC,OAAO,kBAC1B,MAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,4BAA4B,gBAC3B,SAAS,CAAC,yBAAyB,CAAC,EAChD,KAAK,EAAE,SAAS,CAAC,yBAAyB,CAAC,EAC3C,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,aAEtC,KAAC,WAAW,IAAC,SAAS,EAAC,UAAU,GAAG,EACpC,KAAC,WAAW,IAAC,SAAS,EAAC,mBAAmB,GAAG,IACtC,GACW,EACtB,KAAC,mBAAmB,IAAC,KAAK,EAAC,OAAO,EAAC,SAAS,EAAC,UAAU,YACpD,iBAAiB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;oBAChC,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;oBACjD,OAAO,CACL,MAAC,gBAAgB,IAEf,QAAQ,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,aAExD,KAAC,KAAK,IAAC,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,MAAM,IAAI,WAAW,CAAC,GAAI,EAAC,GAAG,EAC/D,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,KAJtB,MAAM,CAAC,IAAI,IAAI,QAAQ,CAKX,CACpB,CAAA;gBACH,CAAC,CAAC,GACkB,IACT,CAChB,CAAA;AACH,CAAC","sourcesContent":["import { ALargeSmall, Check, ChevronDown } from 'lucide-react'\nimport type { Editor } from '@tiptap/react'\nimport { FONT_SIZE_MARK_TYPE } from '@elytracms/core/rich-text'\nimport type { RichTextFontSize } from '@elytracms/core/rich-text'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} 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 { RICH_TEXT_MENU_TRIGGER_CLASS } from './toolbar'\n\n/**\n * A font-size preset in the toolbar's dropdown (EC-411). `size: null` is the\n * markless *normal* step — selecting it REMOVES the mark rather than storing a value.\n */\nexport interface FontSizePreset {\n size: RichTextFontSize | null\n labelKey: MessageKey\n}\n\n/** The semantic scale the toolbar offers, ascending: small · normal · large · x-large. */\nexport const FONT_SIZE_PRESETS: readonly FontSizePreset[] = [\n { size: 'sm', labelKey: 'richText.fontSize.small' },\n { size: null, labelKey: 'richText.fontSize.normal' },\n { size: 'lg', labelKey: 'richText.fontSize.large' },\n { size: 'xl', labelKey: 'richText.fontSize.xlarge' },\n]\n\n/** Whether a preset is the currently-active step (normal ⇔ no mark ⇔ activeSize null). */\nexport function isPresetActive(preset: FontSizePreset, activeSize: RichTextFontSize | null): boolean {\n return preset.size === activeSize\n}\n\n/** Apply a preset on a focused chain: set the mark for a step, or unset it for normal. */\nexport function applyFontSizePreset(editor: Editor, size: RichTextFontSize | null): void {\n const chain = editor.chain().focus()\n if (size) chain.setMark(FONT_SIZE_MARK_TYPE, { size }).run()\n else chain.unsetMark(FONT_SIZE_MARK_TYPE).run()\n}\n\ninterface FontSizeControlProps {\n editor: Editor\n /** The active semantic step (from the toolbar state), or null for normal. */\n activeSize: RichTextFontSize | null\n}\n\n/**\n * The rich-text font-size control (EC-411). Width-stable under the EC-418 contract\n * (AD-4): the trigger shows a fixed-width icon (+ ChevronDown) in the SAME fixed box\n * as the block-style trigger ({@link RICH_TEXT_MENU_TRIGGER_CLASS}) — the size labels\n * live only in the opened menu, never on the strip, so the control never resizes with\n * selection. The trigger keeps an accessible name via an `aria-label`.\n */\nexport function FontSizeControl({ editor, activeSize }: FontSizeControlProps) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={RICH_TEXT_MENU_TRIGGER_CLASS}\n aria-label={translate('richText.fontSize.label')}\n title={translate('richText.fontSize.label')}\n onMouseDown={(e) => e.preventDefault()}\n >\n <ALargeSmall className=\"size-3.5\" />\n <ChevronDown className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-40\">\n {FONT_SIZE_PRESETS.map((preset) => {\n const active = isPresetActive(preset, activeSize)\n return (\n <DropdownMenuItem\n key={preset.size ?? 'normal'}\n onSelect={() => applyFontSizePreset(editor, preset.size)}\n >\n <Check className={cn('size-3.5', !active && 'invisible')} />{' '}\n {translate(preset.labelKey)}\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"]}
1
+ {"version":3,"file":"font-size-control.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/font-size-control.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAE/D,OAAO,EACL,MAAM,EACN,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE7C,OAAO,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAA;AAWxD,0FAA0F;AAC1F,MAAM,CAAC,MAAM,iBAAiB,GAA8B;IAC1D,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,yBAAyB,EAAE;IACnD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,0BAA0B,EAAE;IACpD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,yBAAyB,EAAE;IACnD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,0BAA0B,EAAE;CACrD,CAAA;AAED,0FAA0F;AAC1F,MAAM,UAAU,cAAc,CAAC,MAAsB,EAAE,UAAmC;IACxF,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU,CAAA;AACnC,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,IAA6B;IAC/E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAA;IACpC,IAAI,IAAI;QAAE,KAAK,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAA;;QACvD,KAAK,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC,GAAG,EAAE,CAAA;AACjD,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,UAAU,EAAwB;IAC1E,OAAO,CACL,MAAC,YAAY,eACX,KAAC,mBAAmB,IAAC,OAAO,kBAC1B,MAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,4BAA4B,gBAC3B,SAAS,CAAC,yBAAyB,CAAC,EAChD,KAAK,EAAE,SAAS,CAAC,yBAAyB,CAAC,EAC3C,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,aAEtC,KAAC,WAAW,IAAC,SAAS,EAAC,UAAU,GAAG,EACpC,KAAC,WAAW,IAAC,SAAS,EAAC,mBAAmB,GAAG,IACtC,GACW,EACtB,KAAC,mBAAmB,IAAC,KAAK,EAAC,OAAO,EAAC,SAAS,EAAC,UAAU,YACpD,iBAAiB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;oBAChC,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;oBACjD,OAAO,CACL,MAAC,gBAAgB,IAEf,QAAQ,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,aAExD,KAAC,KAAK,IAAC,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,MAAM,IAAI,WAAW,CAAC,GAAI,EAAC,GAAG,EAC/D,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,KAJtB,MAAM,CAAC,IAAI,IAAI,QAAQ,CAKX,CACpB,CAAA;gBACH,CAAC,CAAC,GACkB,IACT,CAChB,CAAA;AACH,CAAC","sourcesContent":["import { ALargeSmall, Check, ChevronDown } from 'lucide-react'\nimport type { Editor } from '@tiptap/react'\nimport { FONT_SIZE_MARK_TYPE } from '@elytracms/core/rich-text'\nimport type { RichTextFontSize } from '@elytracms/core/rich-text'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@elytracms/studio/ui'\nimport { cn } from '../../../lib/utils'\nimport { translate } from '../../../lib/i18n'\nimport type { MessageKey } from '../../../lib/i18n'\nimport { RICH_TEXT_MENU_TRIGGER_CLASS } from './toolbar'\n\n/**\n * A font-size preset in the toolbar's dropdown (EC-411). `size: null` is the\n * markless *normal* step — selecting it REMOVES the mark rather than storing a value.\n */\nexport interface FontSizePreset {\n size: RichTextFontSize | null\n labelKey: MessageKey\n}\n\n/** The semantic scale the toolbar offers, ascending: small · normal · large · x-large. */\nexport const FONT_SIZE_PRESETS: readonly FontSizePreset[] = [\n { size: 'sm', labelKey: 'richText.fontSize.small' },\n { size: null, labelKey: 'richText.fontSize.normal' },\n { size: 'lg', labelKey: 'richText.fontSize.large' },\n { size: 'xl', labelKey: 'richText.fontSize.xlarge' },\n]\n\n/** Whether a preset is the currently-active step (normal ⇔ no mark ⇔ activeSize null). */\nexport function isPresetActive(preset: FontSizePreset, activeSize: RichTextFontSize | null): boolean {\n return preset.size === activeSize\n}\n\n/** Apply a preset on a focused chain: set the mark for a step, or unset it for normal. */\nexport function applyFontSizePreset(editor: Editor, size: RichTextFontSize | null): void {\n const chain = editor.chain().focus()\n if (size) chain.setMark(FONT_SIZE_MARK_TYPE, { size }).run()\n else chain.unsetMark(FONT_SIZE_MARK_TYPE).run()\n}\n\ninterface FontSizeControlProps {\n editor: Editor\n /** The active semantic step (from the toolbar state), or null for normal. */\n activeSize: RichTextFontSize | null\n}\n\n/**\n * The rich-text font-size control (EC-411). Width-stable under the EC-418 contract\n * (AD-4): the trigger shows a fixed-width icon (+ ChevronDown) in the SAME fixed box\n * as the block-style trigger ({@link RICH_TEXT_MENU_TRIGGER_CLASS}) — the size labels\n * live only in the opened menu, never on the strip, so the control never resizes with\n * selection. The trigger keeps an accessible name via an `aria-label`.\n */\nexport function FontSizeControl({ editor, activeSize }: FontSizeControlProps) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={RICH_TEXT_MENU_TRIGGER_CLASS}\n aria-label={translate('richText.fontSize.label')}\n title={translate('richText.fontSize.label')}\n onMouseDown={(e) => e.preventDefault()}\n >\n <ALargeSmall className=\"size-3.5\" />\n <ChevronDown className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-40\">\n {FONT_SIZE_PRESETS.map((preset) => {\n const active = isPresetActive(preset, activeSize)\n return (\n <DropdownMenuItem\n key={preset.size ?? 'normal'}\n onSelect={() => applyFontSizePreset(editor, preset.size)}\n >\n <Check className={cn('size-3.5', !active && 'invisible')} />{' '}\n {translate(preset.labelKey)}\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { Editor } from '@tiptap/react';
2
+ import { assetNode } from '@elytracms/core/cms-core';
2
3
  import type { ComponentNode } from '@elytracms/core/project-graph';
3
4
  import type { ComponentManifest } from '@elytracms/core/component-registry';
4
5
  import type { AssetsStore } from '../../assets/assets-store.js';
@@ -14,6 +15,16 @@ import type { AssetsStore } from '../../assets/assets-store.js';
14
15
  * rule from it, like a field's own `accept`.
15
16
  */
16
17
  export declare const INLINE_IMAGE_ACCEPT: readonly string[];
18
+ /**
19
+ * The `image` node attrs for a project asset: the canonical `attrs.ref` plus an
20
+ * editor-display `src` snapshot. NO `alt` (EC-587, ASSETS D19): an asset-pinned image's
21
+ * alt is the asset's, read at render through the one reader — a snapshot here went
22
+ * stale on the first alt edit (and read the legacy column only). Pure, for the test.
23
+ */
24
+ export declare function assetImageAttrs(assetId: string, assets: AssetsStore | null): {
25
+ src: string;
26
+ ref: ReturnType<typeof assetNode>;
27
+ };
17
28
  /** Insert an `image` node for a project asset (canonical `attrs.ref`, snapshot `src`). */
18
29
  export declare function insertAssetImage(editor: Editor, assetId: string, assets: AssetsStore | null): void;
19
30
  /**
@@ -31,14 +42,14 @@ export declare function insertComponentEmbed(editor: Editor, manifest: Component
31
42
  */
32
43
  export declare function embedNodeFromManifest(manifest: ComponentManifest): ComponentNode;
33
44
  /**
34
- * The components embeddable in a rich-text field: the field's `allow` list (a
35
- * closed set — EC-544: an empty one offers nothing), or EVERY registered
36
- * component when absent — the ONE semantic the `blocks` field, the blocks panel
37
- * and the `validateRichTextEmbeds` leash already apply (fields.ts: "absent = any").
38
- * EC-591: the old "every non-primitive" reading keyed on the `base.*` id prefix,
39
- * which no longer means anything (and diverged from the validator — COMPOSITION
40
- * #6); a field that wants a narrower offer declares `allow`. Eligibility is
41
- * `isCanvasEligible`, the one predicate the blocks panel and `elytra previews`
42
- * read too (EC-514) — never a local reading of the manifest flag.
45
+ * The components embeddable in a rich-text field or prop (EC-584, ratified 4b): its
46
+ * declared `allow` (a closed set — EC-544: an empty one offers nothing), else the
47
+ * `richText` TYPE's default, declared once in the per-FieldType definition
48
+ * (`FIELD_TYPES.richText.defaultAllow`): any canvas-eligible component that fills no
49
+ * LAYOUT role — a Stack or Grid mid-paragraph is a layout surface's job. Judged by the
50
+ * ONE core placement judge (role exclusion + `isCanvasEligible`, carried on the
51
+ * manifest view), the same verdict the composition validator reaches — so the embed
52
+ * picker, the toolbar, the slash menu and the inspector's rich-text editor offer
53
+ * exactly what publishes.
43
54
  */
44
55
  export declare function listEmbeddable(allow: string[] | undefined, registered: ComponentManifest[]): ComponentManifest[];
@@ -1,5 +1,7 @@
1
1
  import { assetNode } from '@elytracms/core/cms-core';
2
- import { isCanvasEligible } from '@elytracms/core/component-registry';
2
+ import { FIELD_TYPES } from '@elytracms/core/cms-core';
3
+ import { placementRefusal, resolveAllow } from '@elytracms/core/project-graph';
4
+ import { toManifestView } from '@elytracms/core/component-registry';
3
5
  import { COMPONENT_EMBED_NODE_TYPE } from './component-embed.js';
4
6
  /**
5
7
  * Reference-carrying node insertions shared by the toolbar controls and the slash
@@ -13,16 +15,22 @@ import { COMPONENT_EMBED_NODE_TYPE } from './component-embed.js';
13
15
  * rule from it, like a field's own `accept`.
14
16
  */
15
17
  export const INLINE_IMAGE_ACCEPT = ['image'];
18
+ /**
19
+ * The `image` node attrs for a project asset: the canonical `attrs.ref` plus an
20
+ * editor-display `src` snapshot. NO `alt` (EC-587, ASSETS D19): an asset-pinned image's
21
+ * alt is the asset's, read at render through the one reader — a snapshot here went
22
+ * stale on the first alt edit (and read the legacy column only). Pure, for the test.
23
+ */
24
+ export function assetImageAttrs(assetId, assets) {
25
+ const record = assets?.assets.find((a) => a.id === assetId);
26
+ return { src: record?.url ?? '', ref: assetNode(assetId) };
27
+ }
16
28
  /** Insert an `image` node for a project asset (canonical `attrs.ref`, snapshot `src`). */
17
29
  export function insertAssetImage(editor, assetId, assets) {
18
- const record = assets?.assets.find((a) => a.id === assetId);
19
30
  editor
20
31
  .chain()
21
32
  .focus()
22
- .insertContent({
23
- type: 'image',
24
- attrs: { src: record?.url ?? '', alt: record?.alt ?? '', ref: assetNode(assetId) },
25
- })
33
+ .insertContent({ type: 'image', attrs: assetImageAttrs(assetId, assets) })
26
34
  .run();
27
35
  }
28
36
  /**
@@ -71,19 +79,19 @@ export function embedNodeFromManifest(manifest) {
71
79
  return { id: newEmbedId(), componentId: manifest.id, props: {} };
72
80
  }
73
81
  /**
74
- * The components embeddable in a rich-text field: the field's `allow` list (a
75
- * closed set — EC-544: an empty one offers nothing), or EVERY registered
76
- * component when absent — the ONE semantic the `blocks` field, the blocks panel
77
- * and the `validateRichTextEmbeds` leash already apply (fields.ts: "absent = any").
78
- * EC-591: the old "every non-primitive" reading keyed on the `base.*` id prefix,
79
- * which no longer means anything (and diverged from the validator — COMPOSITION
80
- * #6); a field that wants a narrower offer declares `allow`. Eligibility is
81
- * `isCanvasEligible`, the one predicate the blocks panel and `elytra previews`
82
- * read too (EC-514) — never a local reading of the manifest flag.
82
+ * The components embeddable in a rich-text field or prop (EC-584, ratified 4b): its
83
+ * declared `allow` (a closed set — EC-544: an empty one offers nothing), else the
84
+ * `richText` TYPE's default, declared once in the per-FieldType definition
85
+ * (`FIELD_TYPES.richText.defaultAllow`): any canvas-eligible component that fills no
86
+ * LAYOUT role — a Stack or Grid mid-paragraph is a layout surface's job. Judged by the
87
+ * ONE core placement judge (role exclusion + `isCanvasEligible`, carried on the
88
+ * manifest view), the same verdict the composition validator reaches — so the embed
89
+ * picker, the toolbar, the slash menu and the inspector's rich-text editor offer
90
+ * exactly what publishes.
83
91
  */
84
92
  export function listEmbeddable(allow, registered) {
85
- const permitted = allow ? registered.filter((m) => allow.includes(m.id)) : registered;
86
- return permitted.filter(isCanvasEligible);
93
+ const vocabulary = resolveAllow(allow, FIELD_TYPES.richText.defaultAllow);
94
+ return registered.filter((manifest) => placementRefusal({ componentId: manifest.id, view: toManifestView(manifest), allow: vocabulary }) === null);
87
95
  }
88
96
  /** A unique id for an embedded node (studio runs in the browser; crypto is present). */
89
97
  function newEmbedId() {
@@ -1 +1 @@
1
- {"version":3,"file":"insert.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/insert.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Editor } from '@tiptap/react'\nimport { assetNode } from '@elytracms/core/cms-core'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { isCanvasEligible } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { COMPONENT_EMBED_NODE_TYPE } from './component-embed'\nimport type { AssetsStore } from '../../assets/assets-store'\n\n/**\n * Reference-carrying node insertions shared by the toolbar controls and the slash\n * menu (EC-314). Centralised so both entry points produce byte-identical envelopes\n * (the shapes `reference-extraction` indexes).\n */\n\n/**\n * What the rich-text image insert accepts (EC-588): images, in the structured `accept`\n * vocabulary every picker takes — stated once here rather than as a file-input literal\n * at the call site. The picker derives its grid filter, its Upload dialog and its drop\n * rule from it, like a field's own `accept`.\n */\nexport const INLINE_IMAGE_ACCEPT: readonly string[] = ['image']\n\n/** Insert an `image` node for a project asset (canonical `attrs.ref`, snapshot `src`). */\nexport function insertAssetImage(editor: Editor, assetId: string, assets: AssetsStore | null): void {\n const record = assets?.assets.find((a) => a.id === assetId)\n editor\n .chain()\n .focus()\n .insertContent({\n type: 'image',\n attrs: { src: record?.url ?? '', alt: record?.alt ?? '', ref: assetNode(assetId) },\n })\n .run()\n}\n\n/**\n * Insert a `componentEmbed` wrapping a fresh ComponentNode built from manifest\n * defaults. With `{ select: true }` the inserted node is then selected — so a host\n * embed inspector (0.0.41) opens straight onto its props for editing.\n */\nexport function insertComponentEmbed(\n editor: Editor,\n manifest: ComponentManifest,\n options?: { select?: boolean },\n): void {\n const embed = embedNodeFromManifest(manifest)\n editor\n .chain()\n .focus()\n .insertContent({ type: COMPONENT_EMBED_NODE_TYPE, attrs: { node: embed } })\n .run()\n if (options?.select) selectComponentEmbed(editor, embed.id)\n}\n\n/**\n * Select the just-inserted embed by its unique embedded-node id. Located by scan\n * (rather than fragile position arithmetic after `insertContent`, which may split a\n * paragraph or append a trailing block); the id is unique so the first match is it.\n */\nfunction selectComponentEmbed(editor: Editor, id: string): void {\n let pos = -1\n editor.state.doc.descendants((node, at) => {\n if (pos >= 0) return false\n if (node.type.name === COMPONENT_EMBED_NODE_TYPE) {\n const embedded = node.attrs.node as { id?: string } | null\n if (embedded?.id === id) {\n pos = at\n return false\n }\n }\n return true\n })\n if (pos >= 0) editor.commands.setNodeSelection(pos)\n}\n\n/**\n * A fresh embedded ComponentNode (mirrors builder node-factory). EC-591 / ratified\n * 2f: NO props — an absent prop is `undefined`, so the implementation's own default\n * applies; the manifest `default` is a hint, never baked into the stored node.\n */\nexport function embedNodeFromManifest(manifest: ComponentManifest): ComponentNode {\n return { id: newEmbedId(), componentId: manifest.id, props: {} }\n}\n\n/**\n * The components embeddable in a rich-text field: the field's `allow` list (a\n * closed set — EC-544: an empty one offers nothing), or EVERY registered\n * component when absent — the ONE semantic the `blocks` field, the blocks panel\n * and the `validateRichTextEmbeds` leash already apply (fields.ts: \"absent = any\").\n * EC-591: the old \"every non-primitive\" reading keyed on the `base.*` id prefix,\n * which no longer means anything (and diverged from the validator — COMPOSITION\n * #6); a field that wants a narrower offer declares `allow`. Eligibility is\n * `isCanvasEligible`, the one predicate the blocks panel and `elytra previews`\n * read too (EC-514) — never a local reading of the manifest flag.\n */\nexport function listEmbeddable(\n allow: string[] | undefined,\n registered: ComponentManifest[],\n): ComponentManifest[] {\n const permitted = allow ? registered.filter((m) => allow.includes(m.id)) : registered\n return permitted.filter(isCanvasEligible)\n}\n\n/** A unique id for an embedded node (studio runs in the browser; crypto is present). */\nfunction newEmbedId(): string {\n const uuid = globalThis.crypto?.randomUUID?.()\n return uuid ? `embed-${uuid}` : `embed-${Math.random().toString(36).slice(2)}`\n}\n"]}
1
+ {"version":3,"file":"insert.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/insert.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Editor } from '@tiptap/react'\nimport { assetNode } from '@elytracms/core/cms-core'\nimport { FIELD_TYPES } from '@elytracms/core/cms-core'\nimport { placementRefusal, resolveAllow } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { toManifestView } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { COMPONENT_EMBED_NODE_TYPE } from './component-embed'\nimport type { AssetsStore } from '../../assets/assets-store'\n\n/**\n * Reference-carrying node insertions shared by the toolbar controls and the slash\n * menu (EC-314). Centralised so both entry points produce byte-identical envelopes\n * (the shapes `reference-extraction` indexes).\n */\n\n/**\n * What the rich-text image insert accepts (EC-588): images, in the structured `accept`\n * vocabulary every picker takes — stated once here rather than as a file-input literal\n * at the call site. The picker derives its grid filter, its Upload dialog and its drop\n * rule from it, like a field's own `accept`.\n */\nexport const INLINE_IMAGE_ACCEPT: readonly string[] = ['image']\n\n/**\n * The `image` node attrs for a project asset: the canonical `attrs.ref` plus an\n * editor-display `src` snapshot. NO `alt` (EC-587, ASSETS D19): an asset-pinned image's\n * alt is the asset's, read at render through the one reader — a snapshot here went\n * stale on the first alt edit (and read the legacy column only). Pure, for the test.\n */\nexport function assetImageAttrs(assetId: string, assets: AssetsStore | null): { src: string; ref: ReturnType<typeof assetNode> } {\n const record = assets?.assets.find((a) => a.id === assetId)\n return { src: record?.url ?? '', ref: assetNode(assetId) }\n}\n\n/** Insert an `image` node for a project asset (canonical `attrs.ref`, snapshot `src`). */\nexport function insertAssetImage(editor: Editor, assetId: string, assets: AssetsStore | null): void {\n editor\n .chain()\n .focus()\n .insertContent({ type: 'image', attrs: assetImageAttrs(assetId, assets) })\n .run()\n}\n\n/**\n * Insert a `componentEmbed` wrapping a fresh ComponentNode built from manifest\n * defaults. With `{ select: true }` the inserted node is then selected — so a host\n * embed inspector (0.0.41) opens straight onto its props for editing.\n */\nexport function insertComponentEmbed(\n editor: Editor,\n manifest: ComponentManifest,\n options?: { select?: boolean },\n): void {\n const embed = embedNodeFromManifest(manifest)\n editor\n .chain()\n .focus()\n .insertContent({ type: COMPONENT_EMBED_NODE_TYPE, attrs: { node: embed } })\n .run()\n if (options?.select) selectComponentEmbed(editor, embed.id)\n}\n\n/**\n * Select the just-inserted embed by its unique embedded-node id. Located by scan\n * (rather than fragile position arithmetic after `insertContent`, which may split a\n * paragraph or append a trailing block); the id is unique so the first match is it.\n */\nfunction selectComponentEmbed(editor: Editor, id: string): void {\n let pos = -1\n editor.state.doc.descendants((node, at) => {\n if (pos >= 0) return false\n if (node.type.name === COMPONENT_EMBED_NODE_TYPE) {\n const embedded = node.attrs.node as { id?: string } | null\n if (embedded?.id === id) {\n pos = at\n return false\n }\n }\n return true\n })\n if (pos >= 0) editor.commands.setNodeSelection(pos)\n}\n\n/**\n * A fresh embedded ComponentNode (mirrors builder node-factory). EC-591 / ratified\n * 2f: NO props — an absent prop is `undefined`, so the implementation's own default\n * applies; the manifest `default` is a hint, never baked into the stored node.\n */\nexport function embedNodeFromManifest(manifest: ComponentManifest): ComponentNode {\n return { id: newEmbedId(), componentId: manifest.id, props: {} }\n}\n\n/**\n * The components embeddable in a rich-text field or prop (EC-584, ratified 4b): its\n * declared `allow` (a closed set — EC-544: an empty one offers nothing), else the\n * `richText` TYPE's default, declared once in the per-FieldType definition\n * (`FIELD_TYPES.richText.defaultAllow`): any canvas-eligible component that fills no\n * LAYOUT role — a Stack or Grid mid-paragraph is a layout surface's job. Judged by the\n * ONE core placement judge (role exclusion + `isCanvasEligible`, carried on the\n * manifest view), the same verdict the composition validator reaches — so the embed\n * picker, the toolbar, the slash menu and the inspector's rich-text editor offer\n * exactly what publishes.\n */\nexport function listEmbeddable(\n allow: string[] | undefined,\n registered: ComponentManifest[],\n): ComponentManifest[] {\n const vocabulary = resolveAllow(allow, FIELD_TYPES.richText.defaultAllow!)\n return registered.filter(\n (manifest) => placementRefusal({ componentId: manifest.id, view: toManifestView(manifest), allow: vocabulary }) === null,\n )\n}\n\n/** A unique id for an embedded node (studio runs in the browser; crypto is present). */\nfunction newEmbedId(): string {\n const uuid = globalThis.crypto?.randomUUID?.()\n return uuid ? `embed-${uuid}` : `embed-${Math.random().toString(36).slice(2)}`\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Suspense, lazy } from 'react';
3
- import { translate } from '@elytracms/studio/lib/i18n';
3
+ import { translate } from '../../../lib/i18n/index.js';
4
4
  /**
5
5
  * Lazy boundary for the CMS rich-text editor (EC-322 load-time pass).
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/lazy.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAEtD;;;;;;;;;;;;;;GAcG;AACH,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CACnC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAC1E,CAAA;AAED,SAAS,sBAAsB;IAC7B,OAAO,CACL,cACE,SAAS,EAAC,0FAA0F,EACpG,IAAI,EAAC,QAAQ,eACH,MAAM,YAEhB,eAAM,SAAS,EAAC,SAAS,YAAE,SAAS,CAAC,kBAAkB,CAAC,GAAQ,GAC5D,CACP,CAAA;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAgD;IAC7E,OAAO,CACL,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAC,sBAAsB,KAAG,YAC5C,KAAC,kBAAkB,OAAK,KAAK,GAAI,GACxB,CACZ,CAAA;AACH,CAAC","sourcesContent":["import { Suspense, lazy } from 'react'\nimport type { ComponentProps } from 'react'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Lazy boundary for the CMS rich-text editor (EC-322 load-time pass).\n *\n * The TipTap editor + its ProseMirror runtime and extension set is a large\n * dependency, and `field-control` sits on the critical path of *every* CMS view.\n * Importing the editor statically pulled all of TipTap into the studio's main\n * route chunk even for documents with no `richText` field on screen. Splitting it\n * behind {@link lazy} moves the whole editor subtree into an on-demand chunk that\n * only downloads when a `richText` field actually renders — the field control's\n * seam is unchanged (it still imports `RichTextEditor` from `./rich-text`).\n *\n * The fallback mirrors the loaded editor's outer frame (same border/rounding + a\n * toolbar-and-body min-height) so the swap-in is layout-jank-free, and carries an\n * accessible loading label from the message catalog.\n */\nconst RichTextEditorImpl = lazy(() =>\n import('./editor').then((module) => ({ default: module.RichTextEditor })),\n)\n\nfunction RichTextEditorFallback() {\n return (\n <div\n className=\"ec-rte min-h-[7.5rem] animate-pulse rounded-md border border-input bg-muted/20 shadow-sm\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <span className=\"sr-only\">{translate('richText.loading')}</span>\n </div>\n )\n}\n\nexport function RichTextEditor(props: ComponentProps<typeof RichTextEditorImpl>) {\n return (\n <Suspense fallback={<RichTextEditorFallback />}>\n <RichTextEditorImpl {...props} />\n </Suspense>\n )\n}\n"]}
1
+ {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/lazy.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE7C;;;;;;;;;;;;;;GAcG;AACH,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CACnC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAC1E,CAAA;AAED,SAAS,sBAAsB;IAC7B,OAAO,CACL,cACE,SAAS,EAAC,0FAA0F,EACpG,IAAI,EAAC,QAAQ,eACH,MAAM,YAEhB,eAAM,SAAS,EAAC,SAAS,YAAE,SAAS,CAAC,kBAAkB,CAAC,GAAQ,GAC5D,CACP,CAAA;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAgD;IAC7E,OAAO,CACL,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAC,sBAAsB,KAAG,YAC5C,KAAC,kBAAkB,OAAK,KAAK,GAAI,GACxB,CACZ,CAAA;AACH,CAAC","sourcesContent":["import { Suspense, lazy } from 'react'\nimport type { ComponentProps } from 'react'\nimport { translate } from '../../../lib/i18n'\n\n/**\n * Lazy boundary for the CMS rich-text editor (EC-322 load-time pass).\n *\n * The TipTap editor + its ProseMirror runtime and extension set is a large\n * dependency, and `field-control` sits on the critical path of *every* CMS view.\n * Importing the editor statically pulled all of TipTap into the studio's main\n * route chunk even for documents with no `richText` field on screen. Splitting it\n * behind {@link lazy} moves the whole editor subtree into an on-demand chunk that\n * only downloads when a `richText` field actually renders — the field control's\n * seam is unchanged (it still imports `RichTextEditor` from `./rich-text`).\n *\n * The fallback mirrors the loaded editor's outer frame (same border/rounding + a\n * toolbar-and-body min-height) so the swap-in is layout-jank-free, and carries an\n * accessible loading label from the message catalog.\n */\nconst RichTextEditorImpl = lazy(() =>\n import('./editor').then((module) => ({ default: module.RichTextEditor })),\n)\n\nfunction RichTextEditorFallback() {\n return (\n <div\n className=\"ec-rte min-h-[7.5rem] animate-pulse rounded-md border border-input bg-muted/20 shadow-sm\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <span className=\"sr-only\">{translate('richText.loading')}</span>\n </div>\n )\n}\n\nexport function RichTextEditor(props: ComponentProps<typeof RichTextEditorImpl>) {\n return (\n <Suspense fallback={<RichTextEditorFallback />}>\n <RichTextEditorImpl {...props} />\n </Suspense>\n )\n}\n"]}
@@ -3,8 +3,8 @@ import { useMemo, useState } from 'react';
3
3
  import { FileText, Link2, Trash2 } from 'lucide-react';
4
4
  import { referenceNode } from '@elytracms/core/cms-core';
5
5
  import { Button, Input, Popover, PopoverContent, PopoverTrigger, Row, Separator, Stack, Text, } from '@elytracms/studio/ui';
6
- import { cn } from '@elytracms/studio/lib/utils';
7
- import { translate } from '@elytracms/studio/lib/i18n';
6
+ import { cn } from '../../../lib/utils.js';
7
+ import { translate } from '../../../lib/i18n/index.js';
8
8
  const MAX_RESULTS = 40;
9
9
  /**
10
10
  * The rich-text link control (EC-314): set/clear a link on the selection, either as