@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
@@ -1 +1 @@
1
- {"version":3,"file":"drop-rules.js","sourceRoot":"","sources":["../../../src/lib/dnd/drop-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_SLOT,\n OUTLET_SLOT,\n getNode,\n getParentInfo,\n gridContainerNode,\n isDescendantOf,\n isStaticProp,\n layoutContainerKind,\n removeNode,\n updateNode,\n} from '@elytracms/core/project-graph'\nimport type {\n ComponentNode,\n GridContainerOptions,\n InsertTarget,\n LayoutContainerIds,\n} from '@elytracms/core/project-graph'\nimport { manifestSlots } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\n\n/**\n * Drag & drop eligibility rules (EC-160 + EC-162, vision §3.1) — pure\n * graph-level functions with **no DOM or UI dependencies**. The canvas and the\n * page tree both resolve drops through `resolveDrop`, so the two surfaces can\n * never disagree about what a manifest slot allow-list permits, what the\n * composition field's top-level vocabulary permits at the root (EC-554), which\n * children a component accepts, or whether a move would create a cycle.\n *\n * The drop-zone model is position-based: `into` a slot, `before`/`after` a\n * sibling along the parent's flow axis, and the EC-162 gesture positions\n * `left`/`right` — dropping *beside* an element wraps both in a two-column\n * `Grid` container (see `applyWrap`) — the component that fills the\n * `layout.grid` role (EC-591); with no such component the gesture is off.\n */\n\n// ---------------------------------------------------------------------------\n// Inputs\n// ---------------------------------------------------------------------------\n\n/**\n * The manifest slice the rules evaluate. Structural (not the full\n * `ComponentManifest`) so tests and future callers can feed minimal lookups.\n */\nexport interface SlotRule {\n name: string\n /** Restrict which component ids may be inserted (omit to allow any). */\n allow?: string[]\n}\n\nexport interface ManifestRules {\n slots: SlotRule[]\n}\n\n/** Resolve a component id to its manifest slot rules (undefined = unknown component). */\nexport type ManifestLookup = (componentId: string) => ManifestRules | undefined\n\n/**\n * The canonical `ManifestRules` view of a full manifest (EC-386): the declared\n * `slots` merged with the blocks-prop-derived slots (`manifestSlots`, EC-190\n * \"slots are blocks props\" — a `blocks`-type prop IS a named child region whose\n * subtrees live in `node.slots[propName]`, carrying the prop's `allow` leash).\n * Every surface that feeds a real `ComponentManifest` into a {@link ManifestLookup}\n * or {@link primaryInsertSlot} must adapt through this — resolving against raw\n * `manifest.slots` silently degrades a `project.Section.body`-style region to a\n * sibling insert. One derivation, so tree insert, DnD, paste, and the canvas\n * affordances can never disagree about which regions exist or what they allow.\n */\nexport function manifestRules(manifest: ComponentManifest | undefined): ManifestRules | undefined {\n return manifest ? { slots: manifestSlots(manifest) } : undefined\n}\n\n/** What is being dragged. */\nexport type DragSubject =\n /** An existing node of the current page — a completed drop is a `move`. */\n | { kind: 'node'; nodeId: string; componentId: string }\n /** A new subtree (blocks-panel tile) — a completed drop is an `insert`. */\n | { kind: 'component'; componentId: string }\n\n/**\n * Where, relative to a reference node, the drop would land. `into`, `before`\n * and `after` are the EC-160 positions; `left`/`right` are the EC-162 gesture\n * positions — \"beside\" the reference, wrapping both elements in a two-column\n * `Grid` container.\n */\nexport type DropPosition = 'into' | 'before' | 'after' | 'left' | 'right'\n\n/** The side of the reference an EC-162 wrap gesture drops on. */\nexport type WrapSide = 'left' | 'right'\n\nexport interface DropLocation {\n /** The reference node id (the hovered node/row). */\n nodeId: string\n position: DropPosition\n}\n\n// ---------------------------------------------------------------------------\n// Outcomes\n// ---------------------------------------------------------------------------\n\nexport type DropRejectionCode =\n /** The reference node does not exist in the tree. */\n | 'missing-target'\n /** `into` on a component that declares no insertable slot. */\n | 'no-slot'\n /** The target slot's manifest allow-list rejects the dragged component. */\n | 'not-allowed'\n /**\n * EC-554: the composition FIELD's top-level `allow` rejects the component\n * landing as a direct child of the composition root (see {@link fieldAllowAt}).\n */\n | 'field-not-allowed'\n /** Moving a node into itself or its own subtree (cycle). */\n | 'own-subtree'\n /** The page root cannot be moved, and nothing can drop beside it. */\n | 'root'\n /** Structurally valid but changes nothing (dropping a node onto its own position). */\n | 'no-op'\n /**\n * EC-591: the wrap-beside gesture needs the component that fills the\n * `layout.grid` role, and the project's component list has none — the gesture\n * is off (the canvas's role notice says so), never a hardcoded fallback id.\n */\n | 'no-grid'\n\nexport interface DropRejection {\n eligible: false\n code: DropRejectionCode\n reason: string\n}\n\n/** An eligible drop: exactly one builder action (move, insert, or wrap) commits it. */\nexport type DropPlan =\n | { eligible: true; kind: 'move'; nodeId: string; target: InsertTarget }\n | { eligible: true; kind: 'insert'; target: InsertTarget }\n /**\n * EC-162 wrap gesture: the reference node and the dragged subject become the\n * two columns of a new `Grid` container, created atomically (`applyWrap`).\n * `nodeId` is present when the subject is an existing node (a move-wrap);\n * absent for blocks-panel components (an insert-wrap via `buildNode`).\n */\n | { eligible: true; kind: 'wrap'; referenceId: string; side: WrapSide; nodeId?: string }\n\nexport type DropResolution = DropPlan | DropRejection\n\nfunction rejected(code: DropRejectionCode, reason: string): DropRejection {\n return { eligible: false, code, reason }\n}\n\n// ---------------------------------------------------------------------------\n// Slot rules\n// ---------------------------------------------------------------------------\n\n/**\n * The slot an `into` drop inserts into: the default `children` slot when\n * declared, else the first authored slot that is not the layout `outlet`.\n * Mirrors the shell's insert-context resolution so click-insert and\n * drag-insert land in the same place.\n */\nexport function primaryInsertSlot(manifest: ManifestRules | undefined): SlotRule | undefined {\n if (!manifest) return undefined\n return (\n manifest.slots.find((s) => s.name === DEFAULT_SLOT) ??\n manifest.slots.find((s) => s.name !== OUTLET_SLOT)\n )\n}\n\n/** True when a component can receive `into` drops at all (declares an insertable slot). */\nexport function canNestInto(getManifest: ManifestLookup, componentId: string): boolean {\n return primaryInsertSlot(getManifest(componentId)) !== undefined\n}\n\n/**\n * True when an allow-list permits the component id — absent = any, a present\n * list (even an empty one) is a closed set (EC-544). The one membership rule the\n * slot leash and the field leash share.\n */\nfunction allowListPermits(allow: readonly string[] | undefined, componentId: string): boolean {\n return !allow || allow.includes(componentId)\n}\n\n/** True when a slot's allow-list (if any) permits the component id. */\nexport function slotAllows(slot: SlotRule | undefined, componentId: string): boolean {\n return allowListPermits(slot?.allow, componentId)\n}\n\n/**\n * The composition FIELD's top-level vocabulary as it applies to a placement under\n * `parentId` (EC-188, EC-554) — the ONE reader of a field's `allow` on the studio\n * side: every insertion surface (the insert offer in `insert-context`, drag-and-drop\n * on the canvas and the page tree incl. the wrap gesture, paste) asks here, so no\n * surface can apply the field leash differently or not at all.\n *\n * A field `allow` governs only the value's TOP level — a direct child of the\n * composition root, mirroring `validateCompositionValue` — so this returns the\n * field vocabulary when `parentId` IS the root node and `undefined` (no field\n * constraint) anywhere deeper. Keyed on node IDENTITY, never on the component id:\n * the composition editor's synthetic root is the `layout.stack` component, which\n * is also a nestable block, and a rule keyed on the component id would leash\n * every nested Stack. EC-544: an omitted `allow` is `undefined` here (any registered\n * component); a declared list is a closed set.\n */\nexport function fieldAllowAt(\n root: ComponentNode,\n parentId: string,\n fieldAllow: readonly string[] | undefined,\n): readonly string[] | undefined {\n return parentId === root.id ? fieldAllow : undefined\n}\n\n/**\n * Which declared leash refuses `componentId` landing under `parentId` in `slot`\n * (EC-545 slot leash, EC-554 field leash) — `null` when both admit it. The field\n * is judged first: it can only refuse at the composition root, where the slot is\n * the invisible synthetic root's and the field is the declaration the author sees.\n */\nexport function placementRefusal(args: {\n root: ComponentNode\n /** The node the placement lands under (the insertion target's `parentId`). */\n parentId: string\n slot: SlotRule | undefined\n fieldAllow: readonly string[] | undefined\n componentId: string\n}): 'field' | 'slot' | null {\n const { root, parentId, slot, fieldAllow, componentId } = args\n if (!allowListPermits(fieldAllowAt(root, parentId, fieldAllow), componentId)) return 'field'\n if (!slotAllows(slot, componentId)) return 'slot'\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a prospective drop into either an executable plan (one `move` or\n * `insert` builder action → one operations command → one undo step) or a\n * structured rejection the UI shows *during* the drag. Pure: same inputs,\n * same answer, on both surfaces.\n */\nexport function resolveDrop(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n /**\n * The components filling the layout roles (EC-591, `ComponentRegistry.layout`) —\n * the wrap gesture builds the `layout.grid` one, and is OFF when nobody fills it.\n * Required so no surface can forget it.\n */\n layout: LayoutContainerIds\n /**\n * The composition field's top-level `allow` (EC-554) — `undefined` when the\n * field declares none. Required so no surface can forget to pass it; judged\n * through {@link fieldAllowAt} for drops whose parent is the root.\n */\n fieldAllow: readonly string[] | undefined\n subject: DragSubject\n location: DropLocation\n}): DropResolution {\n const { root, getManifest, layout, fieldAllow, subject, location } = args\n\n if (subject.kind === 'node' && subject.nodeId === root.id) {\n return rejected('root', 'The page root cannot be moved.')\n }\n\n const reference = getNode(root, location.nodeId)\n if (!reference) {\n return rejected('missing-target', `Node \"${location.nodeId}\" is not on this page.`)\n }\n\n // EC-162: dropping beside the reference wraps both in a two-column Grid.\n if (location.position === 'left' || location.position === 'right') {\n return resolveWrap({ root, getManifest, layout, fieldAllow, subject, reference, side: location.position })\n }\n\n // Resolve the concrete insertion point: parent node, slot, index.\n let parentId: string\n let slotRule: SlotRule | undefined\n let slotName: string\n let index: number\n\n if (location.position === 'into') {\n const manifest = getManifest(reference.componentId)\n const slot = primaryInsertSlot(manifest)\n if (!slot) {\n return rejected('no-slot', `\"${reference.componentId}\" does not accept children.`)\n }\n parentId = reference.id\n slotRule = slot\n slotName = slot.name\n index = (reference.slots?.[slot.name] ?? []).length\n } else {\n const info = getParentInfo(root, reference.id)\n if (!info) {\n return rejected('root', 'Nothing can be dropped beside the page root.')\n }\n parentId = info.parent.id\n slotName = info.slot\n // Unknown parent manifests impose no allow-list; validation owns that issue.\n slotRule = getManifest(info.parent.componentId)?.slots.find((s) => s.name === info.slot)\n index = info.index + (location.position === 'after' ? 1 : 0)\n }\n\n const refusal = placementRefusal({ root, parentId, slot: slotRule, fieldAllow, componentId: subject.componentId })\n if (refusal === 'field') {\n return rejected(\n 'field-not-allowed',\n `The field's top-level vocabulary does not allow \"${subject.componentId}\".`,\n )\n }\n if (refusal === 'slot') {\n return rejected(\n 'not-allowed',\n `Slot \"${slotName}\" does not allow \"${subject.componentId}\".`,\n )\n }\n\n const target: InsertTarget = { parentId, slot: slotName, index }\n\n if (subject.kind === 'component') {\n return { eligible: true, kind: 'insert', target }\n }\n\n // Cycle prevention: a node can never land inside its own subtree.\n if (subject.nodeId === parentId || isDescendantOf(root, subject.nodeId, parentId)) {\n return rejected('own-subtree', 'A node cannot be dropped into its own subtree.')\n }\n\n // No-op detection: the final position (after same-slot removal compensation,\n // matching `moveNode`) equals the node's current position.\n const current = getParentInfo(root, subject.nodeId)\n if (current && current.parent.id === parentId && current.slot === slotName) {\n const finalIndex = index > current.index ? index - 1 : index\n if (finalIndex === current.index) {\n return rejected('no-op', 'Dropping here would not move the node.')\n }\n }\n\n return { eligible: true, kind: 'move', nodeId: subject.nodeId, target }\n}\n\n// ---------------------------------------------------------------------------\n// Wrap gesture (EC-162): drop beside → two-column Grid container\n// ---------------------------------------------------------------------------\n\n/**\n * The container a \"drop beside\" gesture creates. Canonical choice (EC-161\n * semantics): a `Grid` with two columns, **not** a horizontal `Stack` —\n * EC-161 gives `Grid` the canonical column vocabulary (`columns` is a\n * validated graph value the contextual inspector refines from 2 → n), while\n * a horizontal Stack expresses flow order, not column tracks. The values\n * mirror the Grid manifest defaults (`columns: 2`, `gap: '4'`), so the\n * gesture's graph is exactly what composing the same layout manually would\n * produce.\n */\nexport const WRAP_GRID_OPTIONS: GridContainerOptions = { columns: 2, gap: '4' }\n\n/** Eligibility for a `left`/`right` (wrap-beside) drop. */\nfunction resolveWrap(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n layout: LayoutContainerIds\n fieldAllow: readonly string[] | undefined\n subject: DragSubject\n reference: ComponentNode\n side: WrapSide\n}): DropResolution {\n const { root, getManifest, layout, fieldAllow, subject, reference, side } = args\n\n // EC-591: the gesture builds the `layout.grid` component — a role nobody fills\n // switches it off (the canvas shows the role notice), never a hidden default id.\n const gridId = layout.grid\n if (gridId === undefined) {\n return rejected('no-grid', 'No component fills the layout.grid role — side-by-side columns are unavailable.')\n }\n\n // The Grid takes the reference's place in its parent slot — so the\n // reference needs a parent (the root is the page anchor, never wrapped)\n // and that slot's allow-list must accept a Grid. EC-554: beside a direct\n // child of the composition root the Grid lands AT the root, so the field's\n // top-level vocabulary must accept it too.\n const info = getParentInfo(root, reference.id)\n if (!info) {\n return rejected('root', 'The page root cannot be wrapped in a container.')\n }\n const parentSlotRule = getManifest(info.parent.componentId)?.slots.find(\n (s) => s.name === info.slot,\n )\n const refusal = placementRefusal({\n root,\n parentId: info.parent.id,\n slot: parentSlotRule,\n fieldAllow,\n componentId: gridId,\n })\n if (refusal === 'field') {\n return rejected('field-not-allowed', \"The field's top-level vocabulary does not allow a Grid container.\")\n }\n if (refusal === 'slot') {\n return rejected('not-allowed', `Slot \"${info.slot}\" does not allow a Grid container.`)\n }\n\n // Both columns land in the Grid's children slot.\n const gridSlot = primaryInsertSlot(getManifest(gridId))\n for (const componentId of [reference.componentId, subject.componentId]) {\n if (!slotAllows(gridSlot, componentId)) {\n return rejected('not-allowed', `A Grid container does not allow \"${componentId}\".`)\n }\n }\n\n if (subject.kind === 'component') {\n return { eligible: true, kind: 'wrap', referenceId: reference.id, side }\n }\n if (subject.nodeId === reference.id) {\n return rejected('no-op', 'A node cannot be placed beside itself.')\n }\n // The reference must not live inside the dragged subtree (the subject is\n // lifted out first — taking the reference with it would orphan the wrap).\n if (isDescendantOf(root, subject.nodeId, reference.id)) {\n return rejected('own-subtree', 'A node cannot wrap an element of its own subtree.')\n }\n return { eligible: true, kind: 'wrap', referenceId: reference.id, side, nodeId: subject.nodeId }\n}\n\n/** What lands beside the reference: an existing node (moved) or a built subtree (inserted). */\nexport type WrapPartner =\n | { kind: 'node'; nodeId: string }\n | { kind: 'subtree'; node: ComponentNode }\n\nexport interface WrapResult {\n root: ComponentNode\n /** The id of the created Grid container. */\n containerId: string\n}\n\n/**\n * Execute a wrap plan as **one** structural edit: lift the partner out of the\n * tree (when it is an existing node) and replace the reference with\n * `gridContainerNode(containerId, WRAP_GRID_OPTIONS, [left, right])` in a\n * single pass. The caller commits the result as one builder action → one\n * operations command → one undo step; EC-161 normalization then runs on the\n * *committed* tree, so the two-child container can never be observed (or\n * persisted) in a half-built state. Returns `null` for structurally invalid\n * input (missing nodes, root reference, reference inside the partner).\n *\n * Determinism (EC-162): the produced container subtree is exactly the\n * `gridContainerNode` constructor output — identical to manual composition.\n */\nexport function applyWrap(args: {\n root: ComponentNode\n /** The `layout.grid` component's id (EC-591) — the container the gesture builds. */\n gridComponentId: string\n referenceId: string\n side: WrapSide\n partner: WrapPartner\n containerId: string\n}): WrapResult | null {\n const { root, gridComponentId, referenceId, side, partner, containerId } = args\n if (referenceId === root.id || !getNode(root, referenceId)) return null\n\n let working = root\n let partnerNode: ComponentNode\n if (partner.kind === 'node') {\n if (partner.nodeId === referenceId || partner.nodeId === root.id) return null\n const node = getNode(root, partner.nodeId)\n if (!node || isDescendantOf(root, partner.nodeId, referenceId)) return null\n partnerNode = node\n working = removeNode(root, partner.nodeId)\n } else {\n partnerNode = partner.node\n }\n\n const next = updateNode(working, referenceId, (reference) =>\n gridContainerNode(\n gridComponentId,\n containerId,\n WRAP_GRID_OPTIONS,\n side === 'left' ? [partnerNode, reference] : [reference, partnerNode],\n ),\n )\n return { root: next, containerId }\n}\n\n// ---------------------------------------------------------------------------\n// Pointer → position bands (shared hit-testing model)\n// ---------------------------------------------------------------------------\n\n/** The axis along which a container lays out its children. */\nexport type FlowAxis = 'vertical' | 'horizontal'\n\nfunction staticPropValue(node: ComponentNode, name: string): unknown {\n const prop = node.props?.[name]\n return isStaticProp(prop) ? prop.value : undefined\n}\n\n/**\n * The flow axis of a container — the axis its children stack along, and\n * therefore the hit-band axis for drops over those children (EC-162 ledger\n * fix: bands follow the parent's flow direction, on both surfaces). Mirrors\n * the renderer: a horizontal Stack flows horizontally; a Grid flows\n * horizontally when it actually has ≥ 2 column tracks (an absent/invalid\n * `columns` renders one column). Everything else is block flow (vertical).\n */\nexport function containerFlowAxis(node: ComponentNode | undefined, layout: LayoutContainerIds): FlowAxis {\n if (!node) return 'vertical'\n const kind = layoutContainerKind(node.componentId, layout)\n if (kind === 'stack') {\n return staticPropValue(node, 'direction') === 'horizontal' ? 'horizontal' : 'vertical'\n }\n if (kind === 'grid') {\n const columns = staticPropValue(node, 'columns')\n return typeof columns === 'number' && columns >= 2 ? 'horizontal' : 'vertical'\n }\n return 'vertical'\n}\n\n/** Cross-axis side-band width (fraction of the box) that triggers the wrap gesture. */\nconst WRAP_BAND = 0.18\n\n/**\n * Map a 2D pointer offset within a drop target's box onto a `DropPosition`\n * (EC-162). `flow` is the flow axis of the target's *parent* — before/after\n * bands run along it. On vertically flowing targets, narrow side bands on the\n * cross axis produce `left`/`right` (the wrap-into-columns gesture, when\n * `wrappable`); children of horizontal containers get left/right bands for\n * plain reorder instead, so \"beside\" already means before/after there.\n */\nexport function dropPositionForPoint(args: {\n /** Pointer offset across the target's width, 0..1. */\n x: number\n /** Pointer offset across the target's height, 0..1. */\n y: number\n /** Flow axis of the target's parent (the axis siblings stack along). */\n flow: FlowAxis\n nestable: boolean\n edges: boolean\n /** Enable the wrap side-bands (the gesture surface opts in). */\n wrappable?: boolean\n}): DropPosition {\n const { x, y, flow, nestable, edges, wrappable = false } = args\n if (!edges) return 'into'\n if (wrappable && flow === 'vertical') {\n if (x < WRAP_BAND) return 'left'\n if (x > 1 - WRAP_BAND) return 'right'\n }\n return dropPositionForPointer({ ratio: flow === 'horizontal' ? x : y, nestable, edges })\n}\n\n/**\n * Map a pointer offset within a drop target's box onto a `DropPosition`.\n * `ratio` is the pointer's offset along the target's main axis, 0..1 — the\n * caller picks the axis (the parent's flow axis; see `containerFlowAxis`).\n *\n * - nestable targets with siblings: edge quarters insert before/after, the\n * middle half nests into the slot\n * - leaf targets: top/bottom halves insert before/after\n * - targets without siblings (the root): always `into`\n */\nexport function dropPositionForPointer(args: {\n ratio: number\n /** Whether `into` is structurally possible (target declares an insert slot). */\n nestable: boolean\n /** Whether before/after exist (target has a parent slot). */\n edges: boolean\n}): DropPosition {\n const { ratio, nestable, edges } = args\n if (!edges) return 'into'\n if (nestable) {\n if (ratio < 0.25) return 'before'\n if (ratio > 0.75) return 'after'\n return 'into'\n }\n return ratio < 0.5 ? 'before' : 'after'\n}\n\n// ---------------------------------------------------------------------------\n// Indicator state (shared visual contract)\n// ---------------------------------------------------------------------------\n\n/**\n * The visual state a surface shows for the current resolution. `blocked` is\n * the explicit rejection state (never a silent failed drop); `null` means show\n * nothing (a no-op drop is inert, not an error).\n */\nexport type DropIndicator = DropPosition | 'blocked' | null\n\nexport function dropIndicatorFor(\n resolution: DropResolution,\n position: DropPosition,\n): DropIndicator {\n if (resolution.eligible) return position\n return resolution.code === 'no-op' ? null : 'blocked'\n}\n"]}
1
+ {"version":3,"file":"drop-rules.js","sourceRoot":"","sources":["../../../src/lib/dnd/drop-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_SLOT,\n OUTLET_SLOT,\n allowAt,\n getNode,\n getParentInfo,\n gridContainerNode,\n isDescendantOf,\n isStaticProp,\n layoutContainerKind,\n placementRefusal as judgePlacement,\n removeNode,\n slotAllowList,\n updateNode,\n} from '@elytracms/core/project-graph'\nimport type {\n ComponentNode,\n GridContainerOptions,\n InsertTarget,\n LayoutContainerIds,\n PlacementScope,\n} from '@elytracms/core/project-graph'\nimport { manifestSlots } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\n\n/**\n * Drag & drop eligibility rules (EC-160 + EC-162, vision §3.1) — pure\n * graph-level functions with **no DOM or UI dependencies**. The canvas and the\n * page tree both resolve drops through `resolveDrop`, so the two surfaces can\n * never disagree about what a manifest slot allow-list permits, what the\n * composition field's top-level vocabulary permits at the root (EC-554), which\n * children a component accepts, or whether a move would create a cycle.\n *\n * The drop-zone model is position-based: `into` a slot, `before`/`after` a\n * sibling along the parent's flow axis, and the EC-162 gesture positions\n * `left`/`right` — dropping *beside* an element wraps both in a two-column\n * `Grid` container (see `applyWrap`) — the component that fills the\n * `layout.grid` role (EC-591); with no such component the gesture is off.\n */\n\n// ---------------------------------------------------------------------------\n// Inputs\n// ---------------------------------------------------------------------------\n\n/**\n * The manifest slice the rules evaluate. Structural (not the full\n * `ComponentManifest`) so tests and future callers can feed minimal lookups.\n */\nexport interface SlotRule {\n name: string\n /** Restrict which component ids may be inserted (omit to allow any). */\n allow?: string[]\n}\n\nexport interface ManifestRules {\n slots: SlotRule[]\n}\n\n/** Resolve a component id to its manifest slot rules (undefined = unknown component). */\nexport type ManifestLookup = (componentId: string) => ManifestRules | undefined\n\n/**\n * The canonical `ManifestRules` view of a full manifest (EC-386): the declared\n * `slots` merged with the blocks-prop-derived slots (`manifestSlots`, EC-190\n * \"slots are blocks props\" — a `blocks`-type prop IS a named child region whose\n * subtrees live in `node.slots[propName]`, carrying the prop's `allow` leash).\n * Every surface that feeds a real `ComponentManifest` into a {@link ManifestLookup}\n * or {@link primaryInsertSlot} must adapt through this — resolving against raw\n * `manifest.slots` silently degrades a `project.Section.body`-style region to a\n * sibling insert. One derivation, so tree insert, DnD, paste, and the canvas\n * affordances can never disagree about which regions exist or what they allow.\n */\nexport function manifestRules(manifest: ComponentManifest | undefined): ManifestRules | undefined {\n return manifest ? { slots: manifestSlots(manifest) } : undefined\n}\n\n/** What is being dragged. */\nexport type DragSubject =\n /** An existing node of the current page — a completed drop is a `move`. */\n | { kind: 'node'; nodeId: string; componentId: string }\n /** A new subtree (blocks-panel tile) — a completed drop is an `insert`. */\n | { kind: 'component'; componentId: string }\n\n/**\n * Where, relative to a reference node, the drop would land. `into`, `before`\n * and `after` are the EC-160 positions; `left`/`right` are the EC-162 gesture\n * positions — \"beside\" the reference, wrapping both elements in a two-column\n * `Grid` container.\n */\nexport type DropPosition = 'into' | 'before' | 'after' | 'left' | 'right'\n\n/** The side of the reference an EC-162 wrap gesture drops on. */\nexport type WrapSide = 'left' | 'right'\n\nexport interface DropLocation {\n /** The reference node id (the hovered node/row). */\n nodeId: string\n position: DropPosition\n}\n\n// ---------------------------------------------------------------------------\n// Outcomes\n// ---------------------------------------------------------------------------\n\nexport type DropRejectionCode =\n /** The reference node does not exist in the tree. */\n | 'missing-target'\n /** `into` on a component that declares no insertable slot. */\n | 'no-slot'\n /** The target slot's manifest allow-list rejects the dragged component. */\n | 'not-allowed'\n /**\n * EC-554: the composition FIELD's top-level `allow` rejects the component\n * landing as a direct child of the composition root (see {@link placementRefusal}).\n */\n | 'field-not-allowed'\n /**\n * EC-584: the component is not canvas-eligible (server-only, `clientRenderable:\n * false`) — refused wherever it would land, by the same judge the reducer runs.\n */\n | 'server-only'\n /** Moving a node into itself or its own subtree (cycle). */\n | 'own-subtree'\n /** The page root cannot be moved, and nothing can drop beside it. */\n | 'root'\n /** Structurally valid but changes nothing (dropping a node onto its own position). */\n | 'no-op'\n /**\n * EC-591: the wrap-beside gesture needs the component that fills the\n * `layout.grid` role, and the project's component list has none — the gesture\n * is off (the canvas's role notice says so), never a hardcoded fallback id.\n */\n | 'no-grid'\n\nexport interface DropRejection {\n eligible: false\n code: DropRejectionCode\n reason: string\n}\n\n/** An eligible drop: exactly one builder action (move, insert, or wrap) commits it. */\nexport type DropPlan =\n | { eligible: true; kind: 'move'; nodeId: string; target: InsertTarget }\n | { eligible: true; kind: 'insert'; target: InsertTarget }\n /**\n * EC-162 wrap gesture: the reference node and the dragged subject become the\n * two columns of a new `Grid` container, created atomically (`applyWrap`).\n * `nodeId` is present when the subject is an existing node (a move-wrap);\n * absent for blocks-panel components (an insert-wrap via `buildNode`).\n */\n | { eligible: true; kind: 'wrap'; referenceId: string; side: WrapSide; nodeId?: string }\n\nexport type DropResolution = DropPlan | DropRejection\n\nfunction rejected(code: DropRejectionCode, reason: string): DropRejection {\n return { eligible: false, code, reason }\n}\n\n// ---------------------------------------------------------------------------\n// Slot rules\n// ---------------------------------------------------------------------------\n\n/**\n * The slot an `into` drop inserts into: the default `children` slot when\n * declared, else the first authored slot that is not the layout `outlet`.\n * Mirrors the shell's insert-context resolution so click-insert and\n * drag-insert land in the same place.\n */\nexport function primaryInsertSlot(manifest: ManifestRules | undefined): SlotRule | undefined {\n if (!manifest) return undefined\n return (\n manifest.slots.find((s) => s.name === DEFAULT_SLOT) ??\n manifest.slots.find((s) => s.name !== OUTLET_SLOT)\n )\n}\n\n/** True when a component can receive `into` drops at all (declares an insertable slot). */\nexport function canNestInto(getManifest: ManifestLookup, componentId: string): boolean {\n return primaryInsertSlot(getManifest(componentId)) !== undefined\n}\n\n/**\n * Which rule refuses `componentId` landing under `parentId` in `slot` — `null` when\n * the placement is admitted. EC-584: this is a READER of the one core placement judge\n * (`@elytracms/core/project-graph` `placementRefusal` over `allowAt`'s vocabulary —\n * the field's at the composition root, keyed on node identity; the parent's slot\n * below), the SAME call the builder reducer enforces and the publish gate applies.\n * Every insertion surface (the insert offer, drag-and-drop on the canvas and the page\n * tree incl. the wrap gesture, paste) pre-checks here so its indicator says what the\n * sink will do; the reducer refuses whatever a surface forgot.\n *\n * `'field'` / `'slot'` name the vocabulary that refused (the field is the declaration\n * the author sees at the root — the synthetic root's own slot is invisible);\n * `'server-only'` is canvas eligibility, wherever it would land.\n */\nexport function placementRefusal(args: {\n root: ComponentNode\n /** The node the placement lands under (the insertion target's `parentId`). */\n parentId: string\n slot: string\n placement: PlacementScope\n componentId: string\n}): 'field' | 'slot' | 'server-only' | null {\n const { root, parentId, slot, placement, componentId } = args\n const code = judgePlacement({\n componentId,\n view: placement.components.get(componentId),\n allow: allowAt(root, parentId, slot, placement),\n })\n if (code === 'server-only-component') return 'server-only'\n if (code === 'forbidden-component') return parentId === root.id ? 'field' : 'slot'\n return null\n}\n\n/** The rejection a refused placement shows during a drag (EC-584: the judge's verdict). */\nfunction placementRejection(\n refusal: 'field' | 'slot' | 'server-only',\n componentId: string,\n slotName: string,\n): DropRejection {\n if (refusal === 'server-only') {\n return rejected('server-only', `\"${componentId}\" is server-only and cannot be placed on the canvas.`)\n }\n if (refusal === 'field') {\n return rejected('field-not-allowed', `The field's top-level vocabulary does not allow \"${componentId}\".`)\n }\n return rejected('not-allowed', `Slot \"${slotName}\" does not allow \"${componentId}\".`)\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a prospective drop into either an executable plan (one `move` or\n * `insert` builder action → one operations command → one undo step) or a\n * structured rejection the UI shows *during* the drag. Pure: same inputs,\n * same answer, on both surfaces.\n */\nexport function resolveDrop(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n /**\n * The components filling the layout roles (EC-591, `ComponentRegistry.layout`) —\n * the wrap gesture builds the `layout.grid` one, and is OFF when nobody fills it.\n * Required so no surface can forget it.\n */\n layout: LayoutContainerIds\n /**\n * EC-584: what the placement judge reads — the manifest lookup and the composition\n * field's effective top-level vocabulary (the builder store's `placement`).\n * Required so no surface can forget it; the reducer judges the same scope.\n */\n placement: PlacementScope\n subject: DragSubject\n location: DropLocation\n}): DropResolution {\n const { root, getManifest, layout, placement, subject, location } = args\n\n if (subject.kind === 'node' && subject.nodeId === root.id) {\n return rejected('root', 'The page root cannot be moved.')\n }\n\n const reference = getNode(root, location.nodeId)\n if (!reference) {\n return rejected('missing-target', `Node \"${location.nodeId}\" is not on this page.`)\n }\n\n // EC-162: dropping beside the reference wraps both in a two-column Grid.\n if (location.position === 'left' || location.position === 'right') {\n return resolveWrap({ root, getManifest, layout, placement, subject, reference, side: location.position })\n }\n\n // Resolve the concrete insertion point: parent node, slot, index.\n let parentId: string\n let slotName: string\n let index: number\n\n if (location.position === 'into') {\n const manifest = getManifest(reference.componentId)\n const slot = primaryInsertSlot(manifest)\n if (!slot) {\n return rejected('no-slot', `\"${reference.componentId}\" does not accept children.`)\n }\n parentId = reference.id\n slotName = slot.name\n index = (reference.slots?.[slot.name] ?? []).length\n } else {\n const info = getParentInfo(root, reference.id)\n if (!info) {\n return rejected('root', 'Nothing can be dropped beside the page root.')\n }\n parentId = info.parent.id\n slotName = info.slot\n index = info.index + (location.position === 'after' ? 1 : 0)\n }\n\n // EC-584: a move inside its own slot is a reorder — the placement does not change,\n // so the judge (and the reducer) leave it alone.\n const current = subject.kind === 'node' ? getParentInfo(root, subject.nodeId) : null\n const relocates = !current || current.parent.id !== parentId || current.slot !== slotName\n if (relocates) {\n const refusal = placementRefusal({ root, parentId, slot: slotName, placement, componentId: subject.componentId })\n if (refusal) return placementRejection(refusal, subject.componentId, slotName)\n }\n\n const target: InsertTarget = { parentId, slot: slotName, index }\n\n if (subject.kind === 'component') {\n return { eligible: true, kind: 'insert', target }\n }\n\n // Cycle prevention: a node can never land inside its own subtree.\n if (subject.nodeId === parentId || isDescendantOf(root, subject.nodeId, parentId)) {\n return rejected('own-subtree', 'A node cannot be dropped into its own subtree.')\n }\n\n // No-op detection: the final position (after same-slot removal compensation,\n // matching `moveNode`) equals the node's current position.\n if (current && current.parent.id === parentId && current.slot === slotName) {\n const finalIndex = index > current.index ? index - 1 : index\n if (finalIndex === current.index) {\n return rejected('no-op', 'Dropping here would not move the node.')\n }\n }\n\n return { eligible: true, kind: 'move', nodeId: subject.nodeId, target }\n}\n\n// ---------------------------------------------------------------------------\n// Wrap gesture (EC-162): drop beside → two-column Grid container\n// ---------------------------------------------------------------------------\n\n/**\n * The container a \"drop beside\" gesture creates. Canonical choice (EC-161\n * semantics): a `Grid` with two columns, **not** a horizontal `Stack` —\n * EC-161 gives `Grid` the canonical column vocabulary (`columns` is a\n * validated graph value the contextual inspector refines from 2 → n), while\n * a horizontal Stack expresses flow order, not column tracks. The values\n * mirror the Grid manifest defaults (`columns: 2`, `gap: '4'`), so the\n * gesture's graph is exactly what composing the same layout manually would\n * produce.\n */\nexport const WRAP_GRID_OPTIONS: GridContainerOptions = { columns: 2, gap: '4' }\n\n/** Eligibility for a `left`/`right` (wrap-beside) drop. */\nfunction resolveWrap(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n layout: LayoutContainerIds\n placement: PlacementScope\n subject: DragSubject\n reference: ComponentNode\n side: WrapSide\n}): DropResolution {\n const { root, getManifest, layout, placement, subject, reference, side } = args\n\n // EC-591: the gesture builds the `layout.grid` component — a role nobody fills\n // switches it off (the canvas shows the role notice), never a hidden default id.\n const gridId = layout.grid\n if (gridId === undefined) {\n return rejected('no-grid', 'No component fills the layout.grid role — side-by-side columns are unavailable.')\n }\n\n // The Grid takes the reference's place in its parent slot — so the\n // reference needs a parent (the root is the page anchor, never wrapped)\n // and that slot's allow-list must accept a Grid. EC-554: beside a direct\n // child of the composition root the Grid lands AT the root, so the field's\n // top-level vocabulary must accept it too.\n const info = getParentInfo(root, reference.id)\n if (!info) {\n return rejected('root', 'The page root cannot be wrapped in a container.')\n }\n const refusal = placementRefusal({\n root,\n parentId: info.parent.id,\n slot: info.slot,\n placement,\n componentId: gridId,\n })\n if (refusal === 'field') {\n return rejected('field-not-allowed', \"The field's top-level vocabulary does not allow a Grid container.\")\n }\n if (refusal === 'slot') {\n return rejected('not-allowed', `Slot \"${info.slot}\" does not allow a Grid container.`)\n }\n if (refusal === 'server-only') return placementRejection(refusal, gridId, info.slot)\n\n // Both columns land in the Grid's children slot — judged by the same core judge.\n const gridSlot = primaryInsertSlot(getManifest(gridId))\n const gridSlotAllow = slotAllowList(\n placement.components.get(gridId)?.slots?.find((s) => s.name === (gridSlot?.name ?? DEFAULT_SLOT)),\n )\n for (const componentId of [reference.componentId, subject.componentId]) {\n const code = judgePlacement({ componentId, view: placement.components.get(componentId), allow: gridSlotAllow })\n if (code === 'server-only-component') return placementRejection('server-only', componentId, gridSlot?.name ?? DEFAULT_SLOT)\n if (code === 'forbidden-component') {\n return rejected('not-allowed', `A Grid container does not allow \"${componentId}\".`)\n }\n }\n\n if (subject.kind === 'component') {\n return { eligible: true, kind: 'wrap', referenceId: reference.id, side }\n }\n if (subject.nodeId === reference.id) {\n return rejected('no-op', 'A node cannot be placed beside itself.')\n }\n // The reference must not live inside the dragged subtree (the subject is\n // lifted out first — taking the reference with it would orphan the wrap).\n if (isDescendantOf(root, subject.nodeId, reference.id)) {\n return rejected('own-subtree', 'A node cannot wrap an element of its own subtree.')\n }\n return { eligible: true, kind: 'wrap', referenceId: reference.id, side, nodeId: subject.nodeId }\n}\n\n/** What lands beside the reference: an existing node (moved) or a built subtree (inserted). */\nexport type WrapPartner =\n | { kind: 'node'; nodeId: string }\n | { kind: 'subtree'; node: ComponentNode }\n\nexport interface WrapResult {\n root: ComponentNode\n /** The id of the created Grid container. */\n containerId: string\n}\n\n/**\n * Execute a wrap plan as **one** structural edit: lift the partner out of the\n * tree (when it is an existing node) and replace the reference with\n * `gridContainerNode(containerId, WRAP_GRID_OPTIONS, [left, right])` in a\n * single pass. The caller commits the result as one builder action → one\n * operations command → one undo step; EC-161 normalization then runs on the\n * *committed* tree, so the two-child container can never be observed (or\n * persisted) in a half-built state. Returns `null` for structurally invalid\n * input (missing nodes, root reference, reference inside the partner).\n *\n * Determinism (EC-162): the produced container subtree is exactly the\n * `gridContainerNode` constructor output — identical to manual composition.\n */\nexport function applyWrap(args: {\n root: ComponentNode\n /** The `layout.grid` component's id (EC-591) — the container the gesture builds. */\n gridComponentId: string\n referenceId: string\n side: WrapSide\n partner: WrapPartner\n containerId: string\n}): WrapResult | null {\n const { root, gridComponentId, referenceId, side, partner, containerId } = args\n if (referenceId === root.id || !getNode(root, referenceId)) return null\n\n let working = root\n let partnerNode: ComponentNode\n if (partner.kind === 'node') {\n if (partner.nodeId === referenceId || partner.nodeId === root.id) return null\n const node = getNode(root, partner.nodeId)\n if (!node || isDescendantOf(root, partner.nodeId, referenceId)) return null\n partnerNode = node\n working = removeNode(root, partner.nodeId)\n } else {\n partnerNode = partner.node\n }\n\n const next = updateNode(working, referenceId, (reference) =>\n gridContainerNode(\n gridComponentId,\n containerId,\n WRAP_GRID_OPTIONS,\n side === 'left' ? [partnerNode, reference] : [reference, partnerNode],\n ),\n )\n return { root: next, containerId }\n}\n\n// ---------------------------------------------------------------------------\n// Pointer → position bands (shared hit-testing model)\n// ---------------------------------------------------------------------------\n\n/** The axis along which a container lays out its children. */\nexport type FlowAxis = 'vertical' | 'horizontal'\n\nfunction staticPropValue(node: ComponentNode, name: string): unknown {\n const prop = node.props?.[name]\n return isStaticProp(prop) ? prop.value : undefined\n}\n\n/**\n * The flow axis of a container — the axis its children stack along, and\n * therefore the hit-band axis for drops over those children (EC-162 ledger\n * fix: bands follow the parent's flow direction, on both surfaces). Mirrors\n * the renderer: a horizontal Stack flows horizontally; a Grid flows\n * horizontally when it actually has ≥ 2 column tracks (an absent/invalid\n * `columns` renders one column). Everything else is block flow (vertical).\n */\nexport function containerFlowAxis(node: ComponentNode | undefined, layout: LayoutContainerIds): FlowAxis {\n if (!node) return 'vertical'\n const kind = layoutContainerKind(node.componentId, layout)\n if (kind === 'stack') {\n return staticPropValue(node, 'direction') === 'horizontal' ? 'horizontal' : 'vertical'\n }\n if (kind === 'grid') {\n const columns = staticPropValue(node, 'columns')\n return typeof columns === 'number' && columns >= 2 ? 'horizontal' : 'vertical'\n }\n return 'vertical'\n}\n\n/** Cross-axis side-band width (fraction of the box) that triggers the wrap gesture. */\nconst WRAP_BAND = 0.18\n\n/**\n * Map a 2D pointer offset within a drop target's box onto a `DropPosition`\n * (EC-162). `flow` is the flow axis of the target's *parent* — before/after\n * bands run along it. On vertically flowing targets, narrow side bands on the\n * cross axis produce `left`/`right` (the wrap-into-columns gesture, when\n * `wrappable`); children of horizontal containers get left/right bands for\n * plain reorder instead, so \"beside\" already means before/after there.\n */\nexport function dropPositionForPoint(args: {\n /** Pointer offset across the target's width, 0..1. */\n x: number\n /** Pointer offset across the target's height, 0..1. */\n y: number\n /** Flow axis of the target's parent (the axis siblings stack along). */\n flow: FlowAxis\n nestable: boolean\n edges: boolean\n /** Enable the wrap side-bands (the gesture surface opts in). */\n wrappable?: boolean\n}): DropPosition {\n const { x, y, flow, nestable, edges, wrappable = false } = args\n if (!edges) return 'into'\n if (wrappable && flow === 'vertical') {\n if (x < WRAP_BAND) return 'left'\n if (x > 1 - WRAP_BAND) return 'right'\n }\n return dropPositionForPointer({ ratio: flow === 'horizontal' ? x : y, nestable, edges })\n}\n\n/**\n * Map a pointer offset within a drop target's box onto a `DropPosition`.\n * `ratio` is the pointer's offset along the target's main axis, 0..1 — the\n * caller picks the axis (the parent's flow axis; see `containerFlowAxis`).\n *\n * - nestable targets with siblings: edge quarters insert before/after, the\n * middle half nests into the slot\n * - leaf targets: top/bottom halves insert before/after\n * - targets without siblings (the root): always `into`\n */\nexport function dropPositionForPointer(args: {\n ratio: number\n /** Whether `into` is structurally possible (target declares an insert slot). */\n nestable: boolean\n /** Whether before/after exist (target has a parent slot). */\n edges: boolean\n}): DropPosition {\n const { ratio, nestable, edges } = args\n if (!edges) return 'into'\n if (nestable) {\n if (ratio < 0.25) return 'before'\n if (ratio > 0.75) return 'after'\n return 'into'\n }\n return ratio < 0.5 ? 'before' : 'after'\n}\n\n// ---------------------------------------------------------------------------\n// Indicator state (shared visual contract)\n// ---------------------------------------------------------------------------\n\n/**\n * The visual state a surface shows for the current resolution. `blocked` is\n * the explicit rejection state (never a silent failed drop); `null` means show\n * nothing (a no-op drop is inert, not an error).\n */\nexport type DropIndicator = DropPosition | 'blocked' | null\n\nexport function dropIndicatorFor(\n resolution: DropResolution,\n position: DropPosition,\n): DropIndicator {\n if (resolution.eligible) return position\n return resolution.code === 'no-op' ? null : 'blocked'\n}\n"]}
@@ -13,4 +13,4 @@
13
13
  * (The convex-side stamp does NOT read this mirror — `convex/delivery.ts` imports the
14
14
  * package.json declaration directly; its bundler allows what tsc's rootDir forbids.)
15
15
  */
16
- export declare const ENGINE_VERSION = "0.0.92";
16
+ export declare const ENGINE_VERSION = "0.0.94";
@@ -13,5 +13,5 @@
13
13
  * (The convex-side stamp does NOT read this mirror — `convex/delivery.ts` imports the
14
14
  * package.json declaration directly; its bundler allows what tsc's rootDir forbids.)
15
15
  */
16
- export const ENGINE_VERSION = '0.0.92';
16
+ export const ENGINE_VERSION = '0.0.94';
17
17
  //# sourceMappingURL=engine-version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../../src/lib/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAA","sourcesContent":["/**\n * The engine version this studio build was compiled from (EC-552) — the client-side\n * half of the deployed-functions version handshake: the boot gate compares it against\n * what `delivery:engineVersion` answers from the deployment.\n *\n * A MIRROR of `package.json`'s `version`, not the declaration itself: client source\n * cannot import `../../package.json` (outside the tsc build's `rootDir`; moving the\n * root would reshape `dist` and every export path). The parity test beside this file\n * fs-reads the real declaration and fails `pnpm verify` the moment the two diverge,\n * so a release cannot ship with a stale mirror. `pnpm release:bump` writes this\n * alongside the five `package.json` versions (EC-608).\n *\n * (The convex-side stamp does NOT read this mirror — `convex/delivery.ts` imports the\n * package.json declaration directly; its bundler allows what tsc's rootDir forbids.)\n */\nexport const ENGINE_VERSION = '0.0.92'\n"]}
1
+ {"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../../src/lib/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAA","sourcesContent":["/**\n * The engine version this studio build was compiled from (EC-552) — the client-side\n * half of the deployed-functions version handshake: the boot gate compares it against\n * what `delivery:engineVersion` answers from the deployment.\n *\n * A MIRROR of `package.json`'s `version`, not the declaration itself: client source\n * cannot import `../../package.json` (outside the tsc build's `rootDir`; moving the\n * root would reshape `dist` and every export path). The parity test beside this file\n * fs-reads the real declaration and fails `pnpm verify` the moment the two diverge,\n * so a release cannot ship with a stale mirror. `pnpm release:bump` writes this\n * alongside the five `package.json` versions (EC-608).\n *\n * (The convex-side stamp does NOT read this mirror — `convex/delivery.ts` imports the\n * package.json declaration directly; its bundler allows what tsc's rootDir forbids.)\n */\nexport const ENGINE_VERSION = '0.0.94'\n"]}
@@ -1,6 +1,6 @@
1
1
  import { useMemo, useSyncExternalStore } from 'react';
2
2
  import { resolveLabel } from '@elytracms/core/cms-core';
3
- import { resolveDeploymentI18n } from '@elytracms/studio/lib/workspace';
3
+ import { resolveDeploymentI18n } from '../workspace/index.js';
4
4
  import { EN_MESSAGES } from './messages.en.js';
5
5
  /**
6
6
  * Studio-UI localization runtime (EC-241). Resolves the active UI locale, then
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../../src/lib/i18n/i18n.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useSyncExternalStore } from 'react'\nimport { resolveLabel } from '@elytracms/core/cms-core'\nimport type { LocalizedString } from '@elytracms/core/cms-core'\nimport { resolveDeploymentI18n } from '@elytracms/studio/lib/workspace'\nimport { EN_MESSAGES } from './messages.en'\nimport type { MessageCatalog, SourceCatalog } from './types'\n\n/**\n * Studio-UI localization runtime (EC-241). Resolves the active UI locale, then\n * translates message keys with an active-locale → English → visible-fallback chain.\n *\n * **Locale resolution is client-side** (EC-513, owner-ratified 2026-08-01: \"the studio\n * is primarily client-side and locales should be too\"). Two mechanisms, in precedence\n * order:\n *\n * 1. **`defineStudio({ i18n: { locale } })` — the supported way to fix the UI language.**\n * A pin is read from *config*, which is imported identically on server and client, so\n * SSR and the browser render the same locale with no header plumbing and no flash.\n * Both production consumers pin `de`.\n * 2. **Browser detection** (`navigator.languages`) when nothing is pinned. It is\n * **deferred until after hydration** — see {@link subscribeToUiLocale}. The server\n * has no `navigator`, so it renders the default/pinned locale; the first client\n * render must produce byte-identical output or React discards the whole tree with a\n * hydration error (measured EC-513 under `de-DE`: the auth gate's \"Checking session…\").\n *\n * There is deliberately **no server-side resolver seam**. `setServerLanguagesResolver` +\n * `parseAcceptLanguage` shipped exported and unit-tested from EC-241 but were never\n * called by anything in production — not the demo, not the scaffold template, neither\n * consumer — and EC-241 itself had already measured that TanStack Start's request-header\n * helper is unreachable from a normal SSR route. EC-513 deleted both.\n *\n * No persisted locale selector in v1 (that rides EC-240's deferred account settings).\n */\n\nconst BUILTIN_DEFAULT_LOCALE = 'en'\n\n/**\n * Pick the best available locale for the browser's ordered language preferences\n * (EC-241): an exact tag match wins, else the base language (`de-DE` → `de`), else\n * `defaultLocale`. Pure + deterministic — the browser list is the only input.\n */\nexport function resolveActiveLocale(\n available: readonly string[],\n preferred: readonly string[],\n defaultLocale: string,\n): string {\n const lower = available.map((a) => a.toLowerCase())\n for (const pref of preferred) {\n const p = pref.toLowerCase()\n const base = p.split('-')[0] ?? p\n const at = lower.findIndex((a) => a === p || a === base)\n if (at >= 0) return available[at]!\n }\n return defaultLocale\n}\n\ninterface ResolvedI18n {\n locale: string\n defaultLocale: string\n /** The active-locale catalog (empty when active === default → the English source is used). */\n active: MessageCatalog\n source: SourceCatalog\n}\n\nlet localeOverride: string | null = null\n\n// --- post-hydration browser-locale detection (EC-513) ---\n\nlet browserDetectionEnabled = false\nconst localeListeners = new Set<() => void>()\n\n/**\n * Is `navigator` allowed to influence the resolved locale yet?\n *\n * In a **browser** it is not, until the first committed render of a locale consumer has\n * run — the hydration render has to reproduce the server's markup exactly, and the\n * server never sees `navigator`. Outside a browser (SSR, node tests) there is nothing\n * to defer: `navigatorLanguages()` there is either genuinely absent (server → `[]`, the\n * default locale, which is precisely what must be reproduced) or deliberately stubbed\n * by a test.\n */\nfunction browserDetectionReady(): boolean {\n return browserDetectionEnabled || typeof window === 'undefined'\n}\n\n/**\n * Subscribe to the resolved UI locale, and — on the first subscription — switch browser\n * detection on (EC-513).\n *\n * This is the whole post-hydration handover. `useSyncExternalStore` subscribes in a\n * commit effect, i.e. **after** React has hydrated the server markup, so every render up\n * to that point resolves the same default/pinned locale the server did. Flipping the flag\n * changes the snapshot, React re-renders, and from then on `navigator.languages` decides.\n *\n * The flip is one-way and global rather than per-subscriber because the runtime is a\n * per-boot singleton: 948 of the studio's ~956 localized render sites call the non-hook\n * {@link translate} (measured 2026-08-01), so a per-hook flag would leave almost the whole\n * chrome reading a locale nobody re-rendered for. The studio root subscribes once\n * (`router/root.tsx`), which re-renders the tree exactly once when the handover happens.\n *\n * Exported (module-level only — deliberately not on the `lib/i18n` barrel) so the\n * handover is testable without a React renderer, per the house test pattern.\n */\nexport function subscribeToUiLocale(listener: () => void): () => void {\n localeListeners.add(listener)\n if (!browserDetectionEnabled) {\n browserDetectionEnabled = true\n for (const l of [...localeListeners]) l()\n }\n return () => {\n localeListeners.delete(listener)\n }\n}\n\nfunction uiLocaleSnapshot(): string {\n return resolveStudioI18n().locale\n}\n\n// --- post-hydration rendering gate (EC-523) ---\n\nconst noopUnsubscribe = () => {}\nconst subscribeNever = () => noopUnsubscribe\nconst hydratedSnapshot = () => true\nconst preHydrationSnapshot = () => false\n\n/**\n * Has React finished hydrating? `false` for every server render **and** for the\n * hydration render itself; `true` from the first committed client render onwards.\n *\n * **Gate any localized text that can reach the prerendered shell on this** (EC-523).\n * The studio builds as a static SPA, so `dist/client/_shell.html` is rendered at BUILD\n * time: no request, no `Accept-Language`, no session. Every localized string baked into\n * that file is therefore guaranteed wrong for some visitor — and that includes\n * `aria-label` / `sr-only` text, because an accessible name is still a string and a\n * screen-reader user cannot even notice it flip. The honest shape is to render no words\n * at all until hydration, then announce in the visitor's real locale.\n *\n * Mechanism, and why it cannot itself cause a mismatch: `useSyncExternalStore` reads\n * `getServerSnapshot` for the prerender *and* for the hydration render, so the two agree\n * (a text mismatch makes React discard the whole tree — EC-513), then switches to\n * `getSnapshot` in the commit that follows. The store never changes again, so `subscribe`\n * is an inert module-level constant; a fresh closure per render would resubscribe on\n * every render.\n *\n * Lives beside the EC-513 locale handover deliberately: both answer the same question —\n * *is it safe to render locale-dependent output yet?* — and a contributor chasing \"why is\n * my string baked into the shell?\" lands here.\n */\nexport function useHydrated(): boolean {\n return useSyncExternalStore(subscribeNever, hydratedSnapshot, preHydrationSnapshot)\n}\n\nfunction navigatorLanguages(): string[] {\n if (!browserDetectionReady()) return []\n if (typeof navigator !== 'undefined') {\n if (navigator.languages && navigator.languages.length > 0) return [...navigator.languages]\n if (navigator.language) return [navigator.language]\n }\n return []\n}\n\nconst warned = new Set<string>()\nfunction warnOnce(message: string): void {\n if (warned.has(message)) return\n warned.add(message)\n if (typeof console !== 'undefined') console.warn(message)\n}\n\n/**\n * Resolve the studio i18n from the config holder + the browser locale (EC-241).\n *\n * NOT cached: the studio config holder (`createStudio`) is seeded at the mount, which\n * runs AFTER the consumer's `studio.config` module built its sidebar — so an eager\n * `translate` during that sidebar build (e.g. a built-in view label) sees an empty\n * config. Caching the first result would poison every later render-time call with\n * that empty-config English. Re-resolving each call is cheap (a few array ops) and\n * always reflects the config once the mount has run. `translate`/`localizedLabel`\n * are the hot callers; built-in view labels are lazy getters so they re-resolve too.\n */\nexport function resolveStudioI18n(): ResolvedI18n {\n const config = resolveDeploymentI18n() ?? {}\n const defaultLocale = config.defaultLocale ?? BUILTIN_DEFAULT_LOCALE\n const locales = config.locales ?? {}\n const available = [defaultLocale, ...Object.keys(locales).filter((l) => l !== defaultLocale)]\n // A config-pinned locale (EC-241) takes precedence over browser detection: it is read\n // from config — identical on server and client — so SSR and hydration agree (no\n // navigator-vs-server split). An unavailable pin is ignored with a visible signal\n // (\"failures are first-class\"), and detection resumes.\n let pinned: string | undefined\n if (config.locale !== undefined) {\n if (available.includes(config.locale)) pinned = config.locale\n else\n warnOnce(\n `[i18n] pinned locale \"${config.locale}\" is not available (default \"${defaultLocale}\" + ${JSON.stringify(Object.keys(locales))}); using browser detection.`,\n )\n }\n const locale =\n localeOverride ?? pinned ?? resolveActiveLocale(available, navigatorLanguages(), defaultLocale)\n const active = locale === defaultLocale ? {} : (locales[locale] ?? {})\n // Coverage lint: warn once when the active locale doesn't cover every English key.\n if (locale !== defaultLocale && locales[locale]) {\n const missing = Object.keys(EN_MESSAGES).filter((k) => !(k in active))\n if (missing.length > 0) {\n warnOnce(\n `[i18n] locale \"${locale}\" is missing ${missing.length} message(s); falling back to English for those.`,\n )\n }\n }\n return { locale, defaultLocale, active, source: EN_MESSAGES }\n}\n\nfunction interpolate(text: string, vars: Record<string, string | number>): string {\n return text.replace(/\\{(\\w+)\\}/g, (whole, key: string) => (key in vars ? String(vars[key]) : whole))\n}\n\n/**\n * Translate a message key for the active UI locale (EC-241): active-locale string\n * ?? English source ?? **the key itself** (a visible fallback + a one-time console\n * signal — \"failures are first-class\", never a silent blank). Non-hook — reads the\n * per-boot singleton, so config-build and pure code can localize too; {@link useT}\n * is the React-idiomatic wrapper.\n */\nexport function translate(key: string, vars?: Record<string, string | number>): string {\n const i18n = resolveStudioI18n()\n const hit = i18n.active[key] ?? i18n.source[key]?.message\n if (hit === undefined) {\n warnOnce(`[i18n] missing message key: \"${key}\"`)\n return key\n }\n return vars ? interpolate(hit, vars) : hit\n}\n\n/**\n * A **catalog-backed** schema label (EC-241): a label whose text comes from the message\n * catalog by `key`, not a hard-coded string or a per-locale map. This is how the\n * engine's own built-in schemas (the redirects/users rails) localize — their default\n * labels resolve through the *same* consumer catalog as the rest of the chrome, so one\n * German catalog covers them with no per-rail override and no per-language data in\n * engine code. Consumers never author these (their labels are plain strings or\n * `{ en, de }` maps); it is an engine-internal seam.\n *\n * Encoded as a sentinel-prefixed string so it is a valid {@link LocalizedString} (no\n * change to the serializable schema type) and survives zod parse / object spread — it\n * is decoded only at the final render site by {@link resolveMsgLabel}. On the delivery\n * side (no catalog) `resolveLabel` returns it verbatim, which is harmless: the built-in\n * rails never render there.\n */\nconst MSG_LABEL_SENTINEL = '@@msg:'\nexport function msgLabel(key: string): LocalizedString {\n return MSG_LABEL_SENTINEL + key\n}\n\n/**\n * Decode a {@link msgLabel} to its translated string for the active locale, or\n * `undefined` when the value is not a catalog-backed label (a plain string / locale\n * map / absent). Every studio label read site checks this first, then falls back to\n * {@link resolveLabel} for ordinary labels.\n */\nexport function resolveMsgLabel(value: unknown): string | undefined {\n return typeof value === 'string' && value.startsWith(MSG_LABEL_SENTINEL)\n ? translate(value.slice(MSG_LABEL_SENTINEL.length))\n : undefined\n}\n\n/**\n * Resolve a {@link LocalizedString} (field/collection/option label) against the\n * active UI locale (EC-241) — **non-hook**, reading the per-boot singleton, so it is\n * safe to call anywhere: a component body, a nested render helper, or config-build\n * code. Returns `fallback` (default `''`) when the value is absent. Prefer this over\n * `resolveLabel(x, useUiLocale())` at render sites — it localizes without a hook, so\n * it can't trip the rules-of-hooks in a conditional/nested position. A catalog-backed\n * {@link msgLabel} (the built-in rails) resolves through the message catalog.\n */\nexport function localizedLabel(value: LocalizedString | undefined, fallback = ''): string {\n const fromCatalog = resolveMsgLabel(value)\n if (fromCatalog !== undefined) return fromCatalog\n const { locale, defaultLocale } = resolveStudioI18n()\n return resolveLabel(value, locale, defaultLocale) ?? fallback\n}\n\n// --- React hooks ---\n\n/**\n * The active studio-UI locale — config-pinned, else browser-resolved.\n *\n * Subscribing (EC-513) is what makes the post-hydration handover visible: the hydration\n * render gets the server's locale, and the component re-renders once when browser\n * detection takes over. `router/root.tsx` calls this at the root so the whole chrome —\n * including the ~948 non-hook {@link translate} sites — re-renders with it.\n */\nexport function useUiLocale(): string {\n return useSyncExternalStore(subscribeToUiLocale, uiLocaleSnapshot, uiLocaleSnapshot)\n}\n\n/** A `t(key, vars?)` translator bound to the active locale. */\nexport function useT(): (key: string, vars?: Record<string, string | number>) => string {\n useUiLocale() // re-render this component when the locale handover happens (EC-513)\n return translate\n}\n\n/**\n * Resolve a {@link LocalizedString} label (field/collection/option) for the active\n * locale. Re-reads the locale each render (keyed into the memo) so it never captures\n * a locale resolved before the mount seeded the i18n config.\n */\nexport function useLabel(): (value: LocalizedString | undefined, fallback?: string) => string {\n const locale = useUiLocale() // subscribes to the EC-513 post-hydration handover\n const { defaultLocale } = resolveStudioI18n()\n return useMemo(\n () => (value, fallback) =>\n resolveMsgLabel(value) ?? resolveLabel(value, locale, defaultLocale) ?? fallback ?? '',\n [locale, defaultLocale],\n )\n}\n\n/** Test seam: force the active locale. Never used by app code. */\nexport function setUiLocaleForTests(locale: string | null): void {\n localeOverride = locale\n warned.clear()\n}\n\n/**\n * Test seam: reset i18n state — once-per-boot warnings + the EC-513 browser-detection\n * handover flag and its subscribers. Never used by app code.\n */\nexport function resetStudioI18nForTests(): void {\n warned.clear()\n browserDetectionEnabled = false\n localeListeners.clear()\n}\n"]}
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../../src/lib/i18n/i18n.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useSyncExternalStore } from 'react'\nimport { resolveLabel } from '@elytracms/core/cms-core'\nimport type { LocalizedString } from '@elytracms/core/cms-core'\nimport { resolveDeploymentI18n } from '../workspace'\nimport { EN_MESSAGES } from './messages.en'\nimport type { MessageCatalog, SourceCatalog } from './types'\n\n/**\n * Studio-UI localization runtime (EC-241). Resolves the active UI locale, then\n * translates message keys with an active-locale → English → visible-fallback chain.\n *\n * **Locale resolution is client-side** (EC-513, owner-ratified 2026-08-01: \"the studio\n * is primarily client-side and locales should be too\"). Two mechanisms, in precedence\n * order:\n *\n * 1. **`defineStudio({ i18n: { locale } })` — the supported way to fix the UI language.**\n * A pin is read from *config*, which is imported identically on server and client, so\n * SSR and the browser render the same locale with no header plumbing and no flash.\n * Both production consumers pin `de`.\n * 2. **Browser detection** (`navigator.languages`) when nothing is pinned. It is\n * **deferred until after hydration** — see {@link subscribeToUiLocale}. The server\n * has no `navigator`, so it renders the default/pinned locale; the first client\n * render must produce byte-identical output or React discards the whole tree with a\n * hydration error (measured EC-513 under `de-DE`: the auth gate's \"Checking session…\").\n *\n * There is deliberately **no server-side resolver seam**. `setServerLanguagesResolver` +\n * `parseAcceptLanguage` shipped exported and unit-tested from EC-241 but were never\n * called by anything in production — not the demo, not the scaffold template, neither\n * consumer — and EC-241 itself had already measured that TanStack Start's request-header\n * helper is unreachable from a normal SSR route. EC-513 deleted both.\n *\n * No persisted locale selector in v1 (that rides EC-240's deferred account settings).\n */\n\nconst BUILTIN_DEFAULT_LOCALE = 'en'\n\n/**\n * Pick the best available locale for the browser's ordered language preferences\n * (EC-241): an exact tag match wins, else the base language (`de-DE` → `de`), else\n * `defaultLocale`. Pure + deterministic — the browser list is the only input.\n */\nexport function resolveActiveLocale(\n available: readonly string[],\n preferred: readonly string[],\n defaultLocale: string,\n): string {\n const lower = available.map((a) => a.toLowerCase())\n for (const pref of preferred) {\n const p = pref.toLowerCase()\n const base = p.split('-')[0] ?? p\n const at = lower.findIndex((a) => a === p || a === base)\n if (at >= 0) return available[at]!\n }\n return defaultLocale\n}\n\ninterface ResolvedI18n {\n locale: string\n defaultLocale: string\n /** The active-locale catalog (empty when active === default → the English source is used). */\n active: MessageCatalog\n source: SourceCatalog\n}\n\nlet localeOverride: string | null = null\n\n// --- post-hydration browser-locale detection (EC-513) ---\n\nlet browserDetectionEnabled = false\nconst localeListeners = new Set<() => void>()\n\n/**\n * Is `navigator` allowed to influence the resolved locale yet?\n *\n * In a **browser** it is not, until the first committed render of a locale consumer has\n * run — the hydration render has to reproduce the server's markup exactly, and the\n * server never sees `navigator`. Outside a browser (SSR, node tests) there is nothing\n * to defer: `navigatorLanguages()` there is either genuinely absent (server → `[]`, the\n * default locale, which is precisely what must be reproduced) or deliberately stubbed\n * by a test.\n */\nfunction browserDetectionReady(): boolean {\n return browserDetectionEnabled || typeof window === 'undefined'\n}\n\n/**\n * Subscribe to the resolved UI locale, and — on the first subscription — switch browser\n * detection on (EC-513).\n *\n * This is the whole post-hydration handover. `useSyncExternalStore` subscribes in a\n * commit effect, i.e. **after** React has hydrated the server markup, so every render up\n * to that point resolves the same default/pinned locale the server did. Flipping the flag\n * changes the snapshot, React re-renders, and from then on `navigator.languages` decides.\n *\n * The flip is one-way and global rather than per-subscriber because the runtime is a\n * per-boot singleton: 948 of the studio's ~956 localized render sites call the non-hook\n * {@link translate} (measured 2026-08-01), so a per-hook flag would leave almost the whole\n * chrome reading a locale nobody re-rendered for. The studio root subscribes once\n * (`router/root.tsx`), which re-renders the tree exactly once when the handover happens.\n *\n * Exported (module-level only — deliberately not on the `lib/i18n` barrel) so the\n * handover is testable without a React renderer, per the house test pattern.\n */\nexport function subscribeToUiLocale(listener: () => void): () => void {\n localeListeners.add(listener)\n if (!browserDetectionEnabled) {\n browserDetectionEnabled = true\n for (const l of [...localeListeners]) l()\n }\n return () => {\n localeListeners.delete(listener)\n }\n}\n\nfunction uiLocaleSnapshot(): string {\n return resolveStudioI18n().locale\n}\n\n// --- post-hydration rendering gate (EC-523) ---\n\nconst noopUnsubscribe = () => {}\nconst subscribeNever = () => noopUnsubscribe\nconst hydratedSnapshot = () => true\nconst preHydrationSnapshot = () => false\n\n/**\n * Has React finished hydrating? `false` for every server render **and** for the\n * hydration render itself; `true` from the first committed client render onwards.\n *\n * **Gate any localized text that can reach the prerendered shell on this** (EC-523).\n * The studio builds as a static SPA, so `dist/client/_shell.html` is rendered at BUILD\n * time: no request, no `Accept-Language`, no session. Every localized string baked into\n * that file is therefore guaranteed wrong for some visitor — and that includes\n * `aria-label` / `sr-only` text, because an accessible name is still a string and a\n * screen-reader user cannot even notice it flip. The honest shape is to render no words\n * at all until hydration, then announce in the visitor's real locale.\n *\n * Mechanism, and why it cannot itself cause a mismatch: `useSyncExternalStore` reads\n * `getServerSnapshot` for the prerender *and* for the hydration render, so the two agree\n * (a text mismatch makes React discard the whole tree — EC-513), then switches to\n * `getSnapshot` in the commit that follows. The store never changes again, so `subscribe`\n * is an inert module-level constant; a fresh closure per render would resubscribe on\n * every render.\n *\n * Lives beside the EC-513 locale handover deliberately: both answer the same question —\n * *is it safe to render locale-dependent output yet?* — and a contributor chasing \"why is\n * my string baked into the shell?\" lands here.\n */\nexport function useHydrated(): boolean {\n return useSyncExternalStore(subscribeNever, hydratedSnapshot, preHydrationSnapshot)\n}\n\nfunction navigatorLanguages(): string[] {\n if (!browserDetectionReady()) return []\n if (typeof navigator !== 'undefined') {\n if (navigator.languages && navigator.languages.length > 0) return [...navigator.languages]\n if (navigator.language) return [navigator.language]\n }\n return []\n}\n\nconst warned = new Set<string>()\nfunction warnOnce(message: string): void {\n if (warned.has(message)) return\n warned.add(message)\n if (typeof console !== 'undefined') console.warn(message)\n}\n\n/**\n * Resolve the studio i18n from the config holder + the browser locale (EC-241).\n *\n * NOT cached: the studio config holder (`createStudio`) is seeded at the mount, which\n * runs AFTER the consumer's `studio.config` module built its sidebar — so an eager\n * `translate` during that sidebar build (e.g. a built-in view label) sees an empty\n * config. Caching the first result would poison every later render-time call with\n * that empty-config English. Re-resolving each call is cheap (a few array ops) and\n * always reflects the config once the mount has run. `translate`/`localizedLabel`\n * are the hot callers; built-in view labels are lazy getters so they re-resolve too.\n */\nexport function resolveStudioI18n(): ResolvedI18n {\n const config = resolveDeploymentI18n() ?? {}\n const defaultLocale = config.defaultLocale ?? BUILTIN_DEFAULT_LOCALE\n const locales = config.locales ?? {}\n const available = [defaultLocale, ...Object.keys(locales).filter((l) => l !== defaultLocale)]\n // A config-pinned locale (EC-241) takes precedence over browser detection: it is read\n // from config — identical on server and client — so SSR and hydration agree (no\n // navigator-vs-server split). An unavailable pin is ignored with a visible signal\n // (\"failures are first-class\"), and detection resumes.\n let pinned: string | undefined\n if (config.locale !== undefined) {\n if (available.includes(config.locale)) pinned = config.locale\n else\n warnOnce(\n `[i18n] pinned locale \"${config.locale}\" is not available (default \"${defaultLocale}\" + ${JSON.stringify(Object.keys(locales))}); using browser detection.`,\n )\n }\n const locale =\n localeOverride ?? pinned ?? resolveActiveLocale(available, navigatorLanguages(), defaultLocale)\n const active = locale === defaultLocale ? {} : (locales[locale] ?? {})\n // Coverage lint: warn once when the active locale doesn't cover every English key.\n if (locale !== defaultLocale && locales[locale]) {\n const missing = Object.keys(EN_MESSAGES).filter((k) => !(k in active))\n if (missing.length > 0) {\n warnOnce(\n `[i18n] locale \"${locale}\" is missing ${missing.length} message(s); falling back to English for those.`,\n )\n }\n }\n return { locale, defaultLocale, active, source: EN_MESSAGES }\n}\n\nfunction interpolate(text: string, vars: Record<string, string | number>): string {\n return text.replace(/\\{(\\w+)\\}/g, (whole, key: string) => (key in vars ? String(vars[key]) : whole))\n}\n\n/**\n * Translate a message key for the active UI locale (EC-241): active-locale string\n * ?? English source ?? **the key itself** (a visible fallback + a one-time console\n * signal — \"failures are first-class\", never a silent blank). Non-hook — reads the\n * per-boot singleton, so config-build and pure code can localize too; {@link useT}\n * is the React-idiomatic wrapper.\n */\nexport function translate(key: string, vars?: Record<string, string | number>): string {\n const i18n = resolveStudioI18n()\n const hit = i18n.active[key] ?? i18n.source[key]?.message\n if (hit === undefined) {\n warnOnce(`[i18n] missing message key: \"${key}\"`)\n return key\n }\n return vars ? interpolate(hit, vars) : hit\n}\n\n/**\n * A **catalog-backed** schema label (EC-241): a label whose text comes from the message\n * catalog by `key`, not a hard-coded string or a per-locale map. This is how the\n * engine's own built-in schemas (the redirects/users rails) localize — their default\n * labels resolve through the *same* consumer catalog as the rest of the chrome, so one\n * German catalog covers them with no per-rail override and no per-language data in\n * engine code. Consumers never author these (their labels are plain strings or\n * `{ en, de }` maps); it is an engine-internal seam.\n *\n * Encoded as a sentinel-prefixed string so it is a valid {@link LocalizedString} (no\n * change to the serializable schema type) and survives zod parse / object spread — it\n * is decoded only at the final render site by {@link resolveMsgLabel}. On the delivery\n * side (no catalog) `resolveLabel` returns it verbatim, which is harmless: the built-in\n * rails never render there.\n */\nconst MSG_LABEL_SENTINEL = '@@msg:'\nexport function msgLabel(key: string): LocalizedString {\n return MSG_LABEL_SENTINEL + key\n}\n\n/**\n * Decode a {@link msgLabel} to its translated string for the active locale, or\n * `undefined` when the value is not a catalog-backed label (a plain string / locale\n * map / absent). Every studio label read site checks this first, then falls back to\n * {@link resolveLabel} for ordinary labels.\n */\nexport function resolveMsgLabel(value: unknown): string | undefined {\n return typeof value === 'string' && value.startsWith(MSG_LABEL_SENTINEL)\n ? translate(value.slice(MSG_LABEL_SENTINEL.length))\n : undefined\n}\n\n/**\n * Resolve a {@link LocalizedString} (field/collection/option label) against the\n * active UI locale (EC-241) — **non-hook**, reading the per-boot singleton, so it is\n * safe to call anywhere: a component body, a nested render helper, or config-build\n * code. Returns `fallback` (default `''`) when the value is absent. Prefer this over\n * `resolveLabel(x, useUiLocale())` at render sites — it localizes without a hook, so\n * it can't trip the rules-of-hooks in a conditional/nested position. A catalog-backed\n * {@link msgLabel} (the built-in rails) resolves through the message catalog.\n */\nexport function localizedLabel(value: LocalizedString | undefined, fallback = ''): string {\n const fromCatalog = resolveMsgLabel(value)\n if (fromCatalog !== undefined) return fromCatalog\n const { locale, defaultLocale } = resolveStudioI18n()\n return resolveLabel(value, locale, defaultLocale) ?? fallback\n}\n\n// --- React hooks ---\n\n/**\n * The active studio-UI locale — config-pinned, else browser-resolved.\n *\n * Subscribing (EC-513) is what makes the post-hydration handover visible: the hydration\n * render gets the server's locale, and the component re-renders once when browser\n * detection takes over. `router/root.tsx` calls this at the root so the whole chrome —\n * including the ~948 non-hook {@link translate} sites — re-renders with it.\n */\nexport function useUiLocale(): string {\n return useSyncExternalStore(subscribeToUiLocale, uiLocaleSnapshot, uiLocaleSnapshot)\n}\n\n/** A `t(key, vars?)` translator bound to the active locale. */\nexport function useT(): (key: string, vars?: Record<string, string | number>) => string {\n useUiLocale() // re-render this component when the locale handover happens (EC-513)\n return translate\n}\n\n/**\n * Resolve a {@link LocalizedString} label (field/collection/option) for the active\n * locale. Re-reads the locale each render (keyed into the memo) so it never captures\n * a locale resolved before the mount seeded the i18n config.\n */\nexport function useLabel(): (value: LocalizedString | undefined, fallback?: string) => string {\n const locale = useUiLocale() // subscribes to the EC-513 post-hydration handover\n const { defaultLocale } = resolveStudioI18n()\n return useMemo(\n () => (value, fallback) =>\n resolveMsgLabel(value) ?? resolveLabel(value, locale, defaultLocale) ?? fallback ?? '',\n [locale, defaultLocale],\n )\n}\n\n/** Test seam: force the active locale. Never used by app code. */\nexport function setUiLocaleForTests(locale: string | null): void {\n localeOverride = locale\n warned.clear()\n}\n\n/**\n * Test seam: reset i18n state — once-per-boot warnings + the EC-513 browser-detection\n * handover flag and its subscribers. Never used by app code.\n */\nexport function resetStudioI18nForTests(): void {\n warned.clear()\n browserDetectionEnabled = false\n localeListeners.clear()\n}\n"]}
@@ -147,6 +147,7 @@ export const deMessages = {
147
147
  'shell.playground.badge': 'Playground',
148
148
  'shell.playground.hint': 'Playground-Modus: Daten liegen im Arbeitsspeicher dieses Geräts und werden beim Neuladen zurückgesetzt. Es wird nichts in einem Backend gespeichert.',
149
149
  'shell.deliveryHealth.dismiss': 'Schließen',
150
+ 'shell.rolePolicyDrift.title': 'Diese Deployment-Umgebung setzt eine andere Rollenrichtlinie durch, als das Projekt deklariert – Berechtigungen sind möglicherweise nicht wie erwartet.',
150
151
  // ─────────────────────────────────────────────────────────────────────────
151
152
  // Customization issues — the section-rail foot (`NavIssues`)
152
153
  // ─────────────────────────────────────────────────────────────────────────
@@ -248,6 +249,8 @@ export const deMessages = {
248
249
  // EC-589 — die Gegenrichtung von `strict: true`: Veröffentlichen verweigert, solange ein striktes Ziel fehlt oder unveröffentlicht ist.
249
250
  'operations.error.strictTargetsUnpublishedOne': 'Das Dokument „{target}“ kann nicht veröffentlicht werden: Ein Ziel einer strikten Beziehung fehlt oder ist unveröffentlicht — veröffentlichen Sie es zuerst.',
250
251
  'operations.error.strictTargetsUnpublished': 'Das Dokument „{target}“ kann nicht veröffentlicht werden: Ziele strikter Beziehungen fehlen oder sind unveröffentlicht — veröffentlichen Sie sie zuerst.',
252
+ 'operations.error.publishNeedsLocales': '„{target}“ kann nicht veröffentlicht werden: Seine Sammlung deklariert lokalisierte Felder ({fields}), und es wurde keine Sprachkonfiguration übergeben, um deren ausgeblendete Werte zu beurteilen.',
253
+ 'operations.error.versionNotPinnable': 'Version {version} von „{target}“ wurde nicht durch eine Veröffentlichung erfasst und kann daher nicht live geschaltet werden — stellen Sie sie im Entwurf wieder her und veröffentlichen Sie diesen.',
251
254
  'operations.errorDetail.andMore': ' und {count} weitere',
252
255
  // EC-494 — Fehler von Inhaltsabfragen (Filterleiste + Listing-Hinweis im Canvas).
253
256
  // „filterable“, „asc“, „desc“, „in“, „empty“ sind Schlüsselwörter aus der Konfiguration
@@ -493,6 +496,7 @@ export const deMessages = {
493
496
  'cms.issue.hidden-predicate-error': 'Fehlerhafte Sichtbarkeitsregel',
494
497
  'cms.issue.unaddressable-url': 'Nicht erreichbare URL',
495
498
  'cms.issue.unknown-version': 'Unbekannte Version',
499
+ 'cms.issue.invalid-stored-document': 'Unlesbares gespeichertes Dokument',
496
500
  // EC-443 — issue-card body templates (see messages.en.ts for the fallback contract).
497
501
  'cms.issueBody.missing-required-field': 'Das Pflichtfeld „{field}“ ist nicht ausgefüllt.',
498
502
  'cms.issueBody.missing-localized-value.default': 'Das Pflichtfeld „{field}“ hat keinen Wert für die Standardsprache „{locale}“.',
@@ -595,6 +599,7 @@ export const deMessages = {
595
599
  'cms.issueMessage.routeBoundDocumentNotFound': 'Das an die Route gebundene Dokument „{collection}:{id}“ wurde nicht gefunden.',
596
600
  'cms.issueMessage.routeParamNotCaptured': 'Die Route „{route}“ bindet ihr Dokument an den URL-Parameter „:{param}“, den das Muster „{pattern}“ nicht erfasst.',
597
601
  'cms.issueMessage.collectionNotDefined': 'Die Sammlung „{collection}“ ist nicht definiert.',
602
+ 'cms.issueMessage.storedDocumentUnreadable': 'Das gespeicherte Dokument {document} konnte nicht gelesen werden und wird nicht ausgeliefert ({reason}).',
598
603
  'cms.issueMessage.unaddressableLocaleCollision': 'Keine URL erreicht „{id}“ in der Sprache „{locale}“: die Adresse wäre „{url}“, aber das erste Segment „{segment}“ ist ein deklarierter Sprachcode — die Website liest diese URL daher als Sprache „{segment}“, Pfad „{readPath}“. Benennen Sie dieses Segment um — an diesem Dokument oder an der übergeordneten Seite, zu der es gehört — damit es nicht mit einem Sprachcode kollidiert.',
599
604
  'cms.issueDetail.expectsString': 'erwartet eine Zeichenkette',
600
605
  'cms.issueDetail.expectsRichText': 'erwartet Rich-Text-Inhalt (eine Zeichenkette oder einen Rich-Text-Wert)',
@@ -676,6 +681,9 @@ export const deMessages = {
676
681
  'cms.version.textUnchanged': 'Text unverändert – Formatierung oder Leerzeichen unterscheiden sich.',
677
682
  'cms.version.restoreBlock': 'Diesen Block aus v{version} wiederherstellen',
678
683
  'cms.version.nestedContentChanged': 'Verschachtelter Inhalt geändert.',
684
+ // EC-593 — wo die Diff-Zeile einer verschachtelten Komponente sitzt.
685
+ 'cms.version.nestedInProse': 'im Text',
686
+ 'cms.version.nestedIn': 'in {path}',
679
687
  'cms.version.blockAdded': 'Block hinzugefügt.',
680
688
  'cms.version.blockRemoved': 'Block entfernt.',
681
689
  'cms.version.emptyValue': 'leer',
@@ -776,7 +784,17 @@ export const deMessages = {
776
784
  'cms.asset.altLabel': 'Alt:',
777
785
  'cms.asset.notSet': 'nicht gesetzt',
778
786
  'cms.asset.imageOptions.title': 'Bildoptionen',
779
- 'cms.asset.imageOptions.description': 'Fokuspunkt und Metadaten — für jede Verwendung dieses Bildes gemeinsam.',
787
+ 'cms.asset.imageOptions.description': 'Was zu dieser Verwendung des Bildes gehört, getrennt von dem, was überall für das Bild gilt.',
788
+ 'cms.asset.imageOptions.placementTitle': 'An dieser Stelle',
789
+ 'cms.asset.imageOptions.placementHint': 'Nur diese Verwendung des Bildes — eine andere Seite kann es anders zeigen.',
790
+ 'cms.asset.imageOptions.assetTitle': 'Über dieses Bild',
791
+ 'cms.asset.imageOptions.assetHint': 'Gilt überall, wo dieses Bild verwendet wird — eine Änderung hier ändert jede Verwendung.',
792
+ 'cms.asset.imageOptions.whereUsed': 'Wo es verwendet wird',
793
+ 'cms.asset.replaceFile': 'Datei ersetzen',
794
+ 'cms.asset.replacing': 'Wird ersetzt …',
795
+ 'cms.asset.replaceWrongKind': '„{name}“ nicht verwendet — der Ersatz muss {types} sein, wie die aktuelle Datei.',
796
+ 'cms.asset.replaceSaveFirst': 'Speichern oder verwerfen Sie zuerst die Änderungen an den Metadaten.',
797
+ 'cms.asset.replacedCheckFocal': 'Die Datei wurde ersetzt — prüfen Sie, ob der Fokuspunkt das Motiv noch erfasst.',
780
798
  'cms.asset.focalPoint': 'Fokuspunkt',
781
799
  'cms.asset.resetToCentre': 'Auf Mitte zurücksetzen',
782
800
  'cms.asset.focalPointHint': 'Klicken oder ziehen Sie auf das Bild, um den Fokuspunkt für den Zuschnitt festzulegen.',
@@ -900,6 +918,9 @@ export const deMessages = {
900
918
  // ─────────────────────────────────────────────────────────────────────────
901
919
  // Redirects (section)
902
920
  // ─────────────────────────────────────────────────────────────────────────
921
+ 'records.loadFailed': '{label} konnten nicht geladen werden',
922
+ 'records.loading': '{label} werden geladen…',
923
+ 'records.untitled': '(neu: {label})',
903
924
  'redirects.loadFailed': 'Weiterleitungen konnten nicht geladen werden',
904
925
  'redirects.loading': 'Weiterleitungen werden geladen…',
905
926
  // Built-in redirects rail — schema labels via the catalog (msgLabel), see above.
@@ -1044,6 +1065,11 @@ export const deMessages = {
1044
1065
  'builder.clipboard.notAllowed': '{component} kann nicht in {destination} eingefügt werden — der Slot lässt es nicht zu.',
1045
1066
  'builder.clipboard.notAllowedByField': '{component} kann nicht auf oberster Ebene von „{field}“ eingefügt werden — das Feld lässt es nicht zu.',
1046
1067
  'builder.clipboard.issueComponent': 'Komponente',
1068
+ // EC-584 — eine abgelehnte Platzierung (der eine Platzierungsrichter im Builder-Reducer).
1069
+ 'builder.placement.notAllowedByField': '{component} ist auf oberster Ebene von „{field}“ nicht erlaubt — das Feld lässt es nicht zu. Nichts wurde geändert.',
1070
+ 'builder.placement.notAllowedInSlot': '{component} ist in {destination} nicht erlaubt — der Slot lässt es nicht zu. Nichts wurde geändert.',
1071
+ 'builder.placement.serverOnly': '{component} wird nur auf dem Server gerendert und kann nicht auf die Arbeitsfläche gesetzt werden. Nichts wurde geändert.',
1072
+ 'builder.placement.dismiss': 'Hinweis schließen',
1047
1073
  // EC-494 — Zwischenablage-Fehler aus der Engine plus der studio-seitige Einfüge-Diff.
1048
1074
  'builder.clipboard.error.corruptJson': 'Der Inhalt der Zwischenablage ist kein gültiges JSON: {detail}',
1049
1075
  'builder.clipboard.error.notEnvelope': 'Der Inhalt der Zwischenablage ist kein Elytra-Fragment-Umschlag.',
@@ -1081,7 +1107,6 @@ export const deMessages = {
1081
1107
  'builder.inspector.slots': 'Slots',
1082
1108
  'builder.inspector.slotsHint': 'Slot-Elemente in der Seitenstruktur hinzufügen oder umsortieren.',
1083
1109
  'builder.inspector.propertiesPane': 'Eigenschaften',
1084
- 'builder.inspector.invalidValue': 'Der Wert entspricht nicht dem Schema der Eigenschaft — die Komponente zeigt ihren eigenen Standard, bis er korrigiert ist.',
1085
1110
  'builder.inspector.invalidJson': 'Ungültiges JSON',
1086
1111
  'builder.layout.title': 'Layout',
1087
1112
  'builder.layout.direction': 'Richtung',
@@ -1126,7 +1151,11 @@ export const deMessages = {
1126
1151
  'builder.canvasTheme.stylesheetErrorNotice': 'Das Canvas-Stylesheet konnte nicht geladen werden — Blöcke werden ungestylt dargestellt. Prüfen Sie den canvas.css-Build des Studios.',
1127
1152
  'builder.canvasTheme.noThemeTokensNotice': 'Das Canvas-Stylesheet definiert keine Theme-Tokens. canvas.css sollte den Full-Tailwind-Theme-Einstieg der Site importieren (siehe docs/theming.md).',
1128
1153
  'builder.canvasTheme.noStylesheetNotice': 'Es ist kein Canvas-Stylesheet konfiguriert — Blöcke werden ungestylt dargestellt. Die App muss eines über setCanvasStylesheetUrl bereitstellen (das create-elytra-Template verdrahtet das).',
1129
- 'builder.composition.preservedNotice': 'Diese Komposition enthält {count} Textelement(e), die der Canvas noch nicht darstellen kann. Sie behalten ihre Position zwischen den Blöcken, bleiben von Canvas-Änderungen unberührt und werden normal veröffentlicht.',
1154
+ // EC-549 — ein Textabschnitt zwischen den Blöcken.
1155
+ 'builder.run.title': 'Text',
1156
+ 'builder.run.items': '{count} Element(e)',
1157
+ 'builder.run.hint': 'Geschriebener Text zwischen den Blöcken — hier bearbeiten; er behält seinen Platz zwischen den Blöcken und wird so veröffentlicht, wie er hier steht.',
1158
+ 'builder.run.delete': 'Text löschen',
1130
1159
  'builder.spacing.gapAfterChild': 'Abstand nach Element {index}',
1131
1160
  'builder.richText.toolbarLabel': 'Textformatierung',
1132
1161
  'builder.richText.linkPlaceholder': 'https://… (leer entfernt)',
@@ -1262,6 +1291,11 @@ export const deMessages = {
1262
1291
  'ai.translate.proposalTitle': 'Übersetzung von {collection}',
1263
1292
  'ai.translate.diffBefore': 'Aktuell',
1264
1293
  'ai.translate.diffAfter': 'Übersetzt',
1294
+ // EC-593 — was ein Übersetzungslauf nicht übersetzen konnte, wird gemeldet, nie stillschweigend übersprungen.
1295
+ 'ai.translate.skippedTitle': 'Nicht übersetzt ({count})',
1296
+ 'ai.translate.skipped.unreadable': 'der gespeicherte Wert konnte nicht gelesen werden',
1297
+ 'ai.translate.skipped.notReturned': 'der Assistent hat dafür keinen Text geliefert',
1298
+ 'ai.translate.skipped.structureChanged': 'die Übersetzung hat Formatierungen, Links oder eingebettete Elemente verloren — das Original wurde beibehalten',
1265
1299
  // EC-378 — the update (edit-existing) proposal
1266
1300
  'ai.update.proposalTitle': 'Änderungen an {collection}',
1267
1301
  'ai.update.diffBefore': 'Aktuell',
@@ -1275,7 +1309,14 @@ export const deMessages = {
1275
1309
  // EC-586 / EC-581 (wave-0092, zone A) — canonical values and the per-FieldType definition
1276
1310
  'cms.issueMessage.nonCanonicalValue': 'Das Feld „{field}“ ist in einer nicht kanonischen Form für ein Feld vom Typ „{type}“ gespeichert — es wurde am Schreibweg vorbei geschrieben, der je Feldtyp genau eine Form speichert. Es wird weiterhin korrekt gelesen; erneutes Speichern des Dokuments legt die kanonische Form ab.',
1277
1311
  'cms.issueMessage.assetHoldsUrl': 'Das Asset-Feld „{field}“ enthält eine URL — ein Asset-Wert ist ein Asset aus der Mediathek, nie eine URL. Laden Sie das Bild in die Mediathek hoch und wählen Sie es aus, oder geben Sie einer Komponente für ein externes Bild eine feste src.',
1312
+ // EC-584 — die AllowList-Prüfung (Konfiguration / Registrierung).
1313
+ 'cms.issueMessage.emptyAllow': 'Das Feld „{field}“ deklariert ein leeres „allow“ — eine Liste erlaubt genau die genannten IDs, eine leere also nichts. Lassen Sie „allow“ weg für die Standard-Auswahl des Feldtyps, oder nennen Sie die Komponenten-IDs.',
1314
+ 'cms.issueMessage.unknownAllowComponent': 'Das Feld „{field}“ erlaubt „{component}“, das keine Komponente dieses Projekts ist — prüfen Sie die ID oder entfernen Sie sie aus „allow“.',
1278
1315
  'cms.issueMessage.blocksNotTopLevel': 'Das Feld „{field}“ ist ein „blocks“-Feld innerhalb eines Objekts — ein blocks-Feld steht nur auf oberster Ebene (eine Arbeitsfläche in einer Wiederholung bricht das Formular, die Validierungspfade und den Auslieferungstyp). Legen Sie ein weiteres blocks-Feld auf oberster Ebene an oder geben Sie einer Komponente eine blocks-Eigenschaft (einen Slot).',
1316
+ 'cms.issueMessage.placementKeyReserved': 'Das Asset-Feld „{asset}“ deklariert das platzierungsbezogene Unterfeld „{field}“, einen Namen, der dem Asset selbst gehört — es käme nie bei einer Komponente an (das „{field}“ des Assets gewinnt). Benennen Sie es um; Alternativtext, Bildnachweis oder KI-Kennzeichnung eines Bildes gehören in die Asset-Sammlung.',
1317
+ 'cms.issueMessage.placementNotScalar': 'Das Asset-Feld „{asset}“ deklariert das platzierungsbezogene Unterfeld „{field}“ vom Typ „{type}“ — eine Platzierung enthält Angaben zu dieser Verwendung des Bildes (eine Bildunterschrift, ein Layout), niemals eine Referenz oder Rich Text.',
1318
+ 'cms.issueMessage.sourceNotOnAssetCollection': 'Das Feld „{field}“ deklariert die Quelle „{source}“ — eine Quelle für eingebettete Metadaten füllt nur ein Feld der obersten Ebene der Asset-Sammlung.',
1319
+ 'cms.issueMessage.sourceTypeMismatch': 'Das Feld „{field}“ deklariert die Quelle „{source}“, die kein Feld vom Typ „{type}“ füllen kann (jede Quelle füllt Text; iptc:digitalSourceType auch einen Wahrheitswert).',
1279
1320
  'cms.issueMessage.uniqueNotTopLevel': 'Das Unterfeld „{field}“ deklariert „unique“ — Eindeutigkeit wird über die Dokumente einer Sammlung hinweg beurteilt und gilt daher nur für Felder auf oberster Ebene.',
1280
1321
  'cms.issueMessage.localizedOnProp': 'Die Komponenten-Eigenschaft „{field}“ deklariert „localized“ — ein sprachabhängiger Wert wird je Sprache im Dokument gespeichert, daher kann nur ein Dokumentfeld auf oberster Ebene sprachabhängig sein (machen Sie das Feld sprachabhängig, das die Komposition enthält).',
1281
1322
  'cms.issueMessage.localizedOnSubField': 'Das Unterfeld „{field}“ deklariert „localized“ — ein sprachabhängiger Wert wird je Sprache im Dokument gespeichert, daher kann nur ein Feld auf oberster Ebene sprachabhängig sein (machen Sie das Feld sprachabhängig, das es enthält).',