@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":"sidebar.js","sourceRoot":"","sources":["../../../src/components/app-views/sidebar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport { resolveOverrides } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue, Project, SectionAvailability } from '@elytracms/core/studio-core'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport { resolveDeploymentCollections, resolveDeploymentSidebar } from '@elytracms/studio/lib/workspace'\nimport {\n firstAvailableView,\n moduleAvailability,\n viewNode,\n type AppViewModule,\n type NavPermission,\n type NavPermissionContext,\n type SidebarNode,\n type SidebarViewNode,\n} from './module'\nimport { DEFAULT_VIEWS, checkSidebarIdKeyspace } from './default-modules'\n\n/**\n * EC-237 — resolving the configurable section rail, an instance of the EC-236\n * customization seam.\n *\n * A consuming repo supplies its rail as a `SidebarNode[]` (built from `nav.*`);\n * omitted → the studio derives the default rail from its built-in views. The rail\n * IS the set of views — each view node carries its view object directly — so this\n * resolver does two things at once:\n *\n * 1. **Renders the rail.** Walk the nodes into display-ready entries (views,\n * groups, separators) with per-view availability (EC-221) and a route path.\n * 2. **Derives the router registry.** Collect every rail view + the always-on\n * built-ins (so deep-links resolve even for views a rail omits), keyed by id.\n *\n * Only VIEWS occupy the id-keyspace; groups + separators are presentation with no\n * id, so a group label can never collide with a view (the old duplicate-id bug).\n * Views resolve through the shared {@link resolveOverrides} core: a duplicate view\n * id or a view missing `render`/`label` is a visible {@link CustomizationIssue} and\n * is dropped — never silent. Ids the builders DERIVED are screened BEFORE that core\n * ({@link checkSidebarIdKeyspace}, EC-476/477): to a generic seam an id is just a\n * string, so a slug that happened to equal a built-in id read as an intentional\n * replacement and swallowed the surface. The active rail item is matched by the view id in the\n * URL, so two views backed by the same surface (Content vs. a focused Categories)\n * highlight independently.\n */\n\n// --- Resolved (rail-ready) shapes ---\n\nexport interface ResolvedRailView {\n kind: 'view'\n /** The view id — the route segment and the active-state key. */\n id: string\n label: string\n icon: LucideIcon\n /** The `_splat` to navigate to (the view's home route = its id). */\n path: string\n availability: SectionAvailability\n /**\n * EC-240 (AD-18): the view's OPTIONAL, default-open nav-permission predicate,\n * carried through resolution so the rail can evaluate it against the live role\n * (which resolution — memoised on `project` alone — does not have). Absent →\n * always visible.\n */\n visible?: NavPermission\n /**\n * EC-293: nested sub-views (e.g. a CMS view's collections), rendered indented\n * under this view. Present only on a parent that expanded; leaves omit it.\n */\n children?: ResolvedRailView[]\n}\nexport interface ResolvedRailGroup {\n kind: 'group'\n label: string\n views: ResolvedRailView[]\n}\nexport interface ResolvedRailSeparator {\n kind: 'separator'\n}\nexport type ResolvedRailNode = ResolvedRailView | ResolvedRailGroup | ResolvedRailSeparator\n\n/** The router registry derived from the rail: id → view, plus the landing order. */\nexport interface ViewRegistry {\n /** Resolve a view id to its module (rail views + always-on built-ins). */\n get(id: string): AppViewModule | undefined\n /** Views in landing order (rail order first, then non-rail built-ins). */\n views: AppViewModule[]\n}\n\nexport interface StudioNav {\n /** The display-ready rail (for {@link import('../section-nav').SectionNav}). */\n nodes: ResolvedRailNode[]\n /** The router registry (for the screen resolver + breadcrumb). */\n registry: ViewRegistry\n /** Customization issues (duplicate view id, invalid view, derived-id collision) — visible, never silent. */\n issues: CustomizationIssue[]\n}\n\n/** The default rail: every non-hidden built-in view as a plain entry, in canonical order. */\nfunction defaultRail(): SidebarNode[] {\n return DEFAULT_VIEWS.filter((view) => !view.hidden).map(viewNode)\n}\n\n/**\n * The sub-views a view node contributes (EC-293): explicit `children`, else\n * `expand(collections)` (CMS auto-expansion), else none. Groups never nest, so this\n * applies to top-level view nodes only.\n */\nfunction childNodesOf(\n node: SidebarViewNode,\n collections: readonly CollectionDef[],\n): SidebarViewNode[] {\n return node.children ?? node.expand?.(collections) ?? []\n}\n\n/**\n * Every view the rail references, in rail order: each top-level view, then its nested\n * children, then group children (groups are flat — their children never nest). This\n * is the set fed to `resolveOverrides` + the derived registry.\n */\nfunction railViews(\n nodes: readonly SidebarNode[],\n collections: readonly CollectionDef[],\n): AppViewModule[] {\n const views: AppViewModule[] = []\n for (const node of nodes) {\n if (node.kind === 'view') {\n views.push(node.view)\n for (const child of childNodesOf(node, collections)) views.push(child.view)\n } else if (node.kind === 'group') {\n for (const child of node.children) views.push(child.view)\n }\n }\n return views\n}\n\n/**\n * Resolve a consumer rail (or the default rail) into the display tree + the router\n * registry through the EC-236 seam. Views are resolved over the built-in defaults\n * (so an omitted built-in stays routable and a rail `cms` REPLACES the default\n * one); groups/separators pass through as presentation.\n *\n * EC-293: a view node may nest. `collections` (config-sourced) materialises a CMS\n * parent's per-collection children; every child view joins the override pass + the\n * derived registry, so a nested `cms:<id>` lens routes and highlights like any view.\n */\nexport function resolveSidebar(\n entries: readonly SidebarNode[] | undefined,\n project: Project,\n collections: readonly CollectionDef[] = [],\n): StudioNav {\n const nodes = entries ?? defaultRail()\n const railOrderViews = railViews(nodes, collections)\n\n // EC-476/477: an id a builder DERIVED is not a request to replace whatever it happens\n // to equal. Screened BEFORE the seam, which sees only strings and so cannot tell an\n // intentional override from a coincidence of spelling (see `checkSidebarIdKeyspace`).\n const keyspace = checkSidebarIdKeyspace(railOrderViews, collections)\n\n // Resolve the rail views over the built-in registry: dedup by id + contract\n // check. Built-ins are the defaults (so omitted ones stay routable); rail views\n // are overrides (a same-id rail view replaces the default; a new id is added).\n const resolved = resolveOverrides<AppViewModule>({\n point: 'sidebar',\n defaults: DEFAULT_VIEWS.map((view) => ({ id: view.id, value: view })),\n overrides: keyspace.views.map((view) => ({ id: view.id, value: view })),\n allowNew: true,\n // EC-478: a rejection carries its catalog key as well as its English, so the\n // `{reason}` half of `invalid-override` localizes with the sentence around it.\n validate: (entry) => {\n const view = entry.value\n if (typeof view.render !== 'function')\n return {\n message: 'the view has no render function',\n key: 'customization.issue.reason.viewNoRender',\n }\n if (typeof view.label !== 'string' || view.label.trim().length === 0)\n return { message: 'the view has no label', key: 'customization.issue.reason.viewNoLabel' }\n return null\n },\n })\n\n // Rail order for landing: each rail view (resolved), deduped, then the built-ins\n // not in the rail (so Settings — always available — is the final landing fallback).\n const inRail = new Set<string>()\n const landing: AppViewModule[] = []\n for (const view of railOrderViews) {\n const resolvedView = resolved.get(view.id)\n if (resolvedView && !inRail.has(resolvedView.id)) {\n inRail.add(resolvedView.id)\n landing.push(resolvedView)\n }\n }\n for (const view of DEFAULT_VIEWS) if (!inRail.has(view.id)) landing.push(view)\n\n const registry: ViewRegistry = { get: (id) => resolved.get(id), views: landing }\n\n // Build the display tree. Each view id is emitted at most once (the first of a\n // duplicate wins, matching `resolveOverrides`); a view dropped by validation is\n // skipped here too.\n const emitted = new Set<string>()\n const toRailView = (view: AppViewModule): ResolvedRailView | null => {\n const resolvedView = resolved.get(view.id)\n if (!resolvedView || emitted.has(resolvedView.id)) return null\n emitted.add(resolvedView.id)\n return {\n kind: 'view',\n id: resolvedView.id,\n label: resolvedView.label,\n icon: resolvedView.icon,\n path: resolvedView.id,\n availability: moduleAvailability(project, resolvedView),\n ...(resolvedView.visible ? { visible: resolvedView.visible } : {}),\n }\n }\n\n const railNodes: ResolvedRailNode[] = []\n for (const node of nodes) {\n if (node.kind === 'separator') {\n railNodes.push({ kind: 'separator' })\n continue\n }\n if (node.kind === 'group') {\n const views = node.children.map((child) => toRailView(child.view)).filter(isRailView)\n if (views.length > 0) railNodes.push({ kind: 'group', label: node.label, views })\n continue\n }\n // A top-level view, with its nested children (CMS collections) attached. The\n // parent is emitted first so its id wins any duplicate; children follow in order.\n const view = toRailView(node.view)\n if (!view) continue\n const children = childNodesOf(node, collections)\n .map((child) => toRailView(child.view))\n .filter(isRailView)\n if (children.length > 0) view.children = children\n railNodes.push(view)\n }\n\n // Keyspace findings first: a derived-id collision is the CAUSE of any duplicate that\n // follows it, so it reads before the symptom at the rail foot.\n return { nodes: railNodes, registry, issues: [...keyspace.issues, ...resolved.issues] }\n}\n\nfunction isRailView(view: ResolvedRailView | null): view is ResolvedRailView {\n return view !== null\n}\n\n/**\n * Evaluate a rail view's OPTIONAL nav-permission predicate (EC-240, AD-18):\n * default-open — absent → visible; present → the predicate decides against the\n * live role. UI reflection only; the server chokepoint is the real boundary and\n * a hidden view stays URL-addressable (EC-221).\n */\nexport function isNavViewVisible(\n view: Pick<ResolvedRailView, 'visible'>,\n ctx: NavPermissionContext,\n): boolean {\n return view.visible ? view.visible(ctx) : true\n}\n\n/** The active view id parsed from a studio pathname `/<projectId>/<viewId>[/…]`. */\nexport function activeViewIdFromPath(pathname: string): string | null {\n return pathname.split('/')[2] || null\n}\n\n/** The view a bare project URL should open to: the first navigable rail view (EC-221). */\nexport function landingViewId(nav: StudioNav, project: Project): string {\n return firstAvailableView(nav.registry.views, project)?.id ?? 'settings'\n}\n\n/**\n * The active project's resolved studio nav — the default rail or\n * `studioConfig.sidebar`, resolved over the built-in views through the EC-236 seam\n * (static per studio boot).\n */\nexport function useStudioNav(project: Project): StudioNav {\n // `resolveDeploymentSidebar`/`resolveDeploymentCollections` are config-holder reads\n // (stable per studio boot), so keying the memo on `project` alone is correct.\n return useMemo(\n () => resolveSidebar(resolveDeploymentSidebar(), project, resolveDeploymentCollections()),\n [project],\n )\n}\n"]}
1
+ {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../../../src/components/app-views/sidebar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport { resolveOverrides } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue, Project, SectionAvailability } from '@elytracms/core/studio-core'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport { resolveDeploymentCollections, resolveDeploymentSidebar } from '../../lib/workspace'\nimport {\n firstAvailableView,\n moduleAvailability,\n viewNode,\n type AppViewModule,\n type NavPermission,\n type NavPermissionContext,\n type SidebarNode,\n type SidebarViewNode,\n} from './module'\nimport { DEFAULT_VIEWS, checkSidebarIdKeyspace } from './default-modules'\n\n/**\n * EC-237 — resolving the configurable section rail, an instance of the EC-236\n * customization seam.\n *\n * A consuming repo supplies its rail as a `SidebarNode[]` (built from `nav.*`);\n * omitted → the studio derives the default rail from its built-in views. The rail\n * IS the set of views — each view node carries its view object directly — so this\n * resolver does two things at once:\n *\n * 1. **Renders the rail.** Walk the nodes into display-ready entries (views,\n * groups, separators) with per-view availability (EC-221) and a route path.\n * 2. **Derives the router registry.** Collect every rail view + the always-on\n * built-ins (so deep-links resolve even for views a rail omits), keyed by id.\n *\n * Only VIEWS occupy the id-keyspace; groups + separators are presentation with no\n * id, so a group label can never collide with a view (the old duplicate-id bug).\n * Views resolve through the shared {@link resolveOverrides} core: a duplicate view\n * id or a view missing `render`/`label` is a visible {@link CustomizationIssue} and\n * is dropped — never silent. Ids the builders DERIVED are screened BEFORE that core\n * ({@link checkSidebarIdKeyspace}, EC-476/477): to a generic seam an id is just a\n * string, so a slug that happened to equal a built-in id read as an intentional\n * replacement and swallowed the surface. The active rail item is matched by the view id in the\n * URL, so two views backed by the same surface (Content vs. a focused Categories)\n * highlight independently.\n */\n\n// --- Resolved (rail-ready) shapes ---\n\nexport interface ResolvedRailView {\n kind: 'view'\n /** The view id — the route segment and the active-state key. */\n id: string\n label: string\n icon: LucideIcon\n /** The `_splat` to navigate to (the view's home route = its id). */\n path: string\n availability: SectionAvailability\n /**\n * EC-240 (AD-18): the view's OPTIONAL, default-open nav-permission predicate,\n * carried through resolution so the rail can evaluate it against the live role\n * (which resolution — memoised on `project` alone — does not have). Absent →\n * always visible.\n */\n visible?: NavPermission\n /**\n * EC-293: nested sub-views (e.g. a CMS view's collections), rendered indented\n * under this view. Present only on a parent that expanded; leaves omit it.\n */\n children?: ResolvedRailView[]\n}\nexport interface ResolvedRailGroup {\n kind: 'group'\n label: string\n views: ResolvedRailView[]\n}\nexport interface ResolvedRailSeparator {\n kind: 'separator'\n}\nexport type ResolvedRailNode = ResolvedRailView | ResolvedRailGroup | ResolvedRailSeparator\n\n/** The router registry derived from the rail: id → view, plus the landing order. */\nexport interface ViewRegistry {\n /** Resolve a view id to its module (rail views + always-on built-ins). */\n get(id: string): AppViewModule | undefined\n /** Views in landing order (rail order first, then non-rail built-ins). */\n views: AppViewModule[]\n}\n\nexport interface StudioNav {\n /** The display-ready rail (for {@link import('../section-nav').SectionNav}). */\n nodes: ResolvedRailNode[]\n /** The router registry (for the screen resolver + breadcrumb). */\n registry: ViewRegistry\n /** Customization issues (duplicate view id, invalid view, derived-id collision) — visible, never silent. */\n issues: CustomizationIssue[]\n}\n\n/** The default rail: every non-hidden built-in view as a plain entry, in canonical order. */\nfunction defaultRail(): SidebarNode[] {\n return DEFAULT_VIEWS.filter((view) => !view.hidden).map(viewNode)\n}\n\n/**\n * The sub-views a view node contributes (EC-293): explicit `children`, else\n * `expand(collections)` (CMS auto-expansion), else none. Groups never nest, so this\n * applies to top-level view nodes only.\n */\nfunction childNodesOf(\n node: SidebarViewNode,\n collections: readonly CollectionDef[],\n): SidebarViewNode[] {\n return node.children ?? node.expand?.(collections) ?? []\n}\n\n/**\n * Every view the rail references, in rail order: each top-level view, then its nested\n * children, then group children (groups are flat — their children never nest). This\n * is the set fed to `resolveOverrides` + the derived registry.\n */\nfunction railViews(\n nodes: readonly SidebarNode[],\n collections: readonly CollectionDef[],\n): AppViewModule[] {\n const views: AppViewModule[] = []\n for (const node of nodes) {\n if (node.kind === 'view') {\n views.push(node.view)\n for (const child of childNodesOf(node, collections)) views.push(child.view)\n } else if (node.kind === 'group') {\n for (const child of node.children) views.push(child.view)\n }\n }\n return views\n}\n\n/**\n * Resolve a consumer rail (or the default rail) into the display tree + the router\n * registry through the EC-236 seam. Views are resolved over the built-in defaults\n * (so an omitted built-in stays routable and a rail `cms` REPLACES the default\n * one); groups/separators pass through as presentation.\n *\n * EC-293: a view node may nest. `collections` (config-sourced) materialises a CMS\n * parent's per-collection children; every child view joins the override pass + the\n * derived registry, so a nested `cms:<id>` lens routes and highlights like any view.\n */\nexport function resolveSidebar(\n entries: readonly SidebarNode[] | undefined,\n project: Project,\n collections: readonly CollectionDef[] = [],\n): StudioNav {\n const nodes = entries ?? defaultRail()\n const railOrderViews = railViews(nodes, collections)\n\n // EC-476/477: an id a builder DERIVED is not a request to replace whatever it happens\n // to equal. Screened BEFORE the seam, which sees only strings and so cannot tell an\n // intentional override from a coincidence of spelling (see `checkSidebarIdKeyspace`).\n const keyspace = checkSidebarIdKeyspace(railOrderViews, collections)\n\n // Resolve the rail views over the built-in registry: dedup by id + contract\n // check. Built-ins are the defaults (so omitted ones stay routable); rail views\n // are overrides (a same-id rail view replaces the default; a new id is added).\n const resolved = resolveOverrides<AppViewModule>({\n point: 'sidebar',\n defaults: DEFAULT_VIEWS.map((view) => ({ id: view.id, value: view })),\n overrides: keyspace.views.map((view) => ({ id: view.id, value: view })),\n allowNew: true,\n // EC-478: a rejection carries its catalog key as well as its English, so the\n // `{reason}` half of `invalid-override` localizes with the sentence around it.\n validate: (entry) => {\n const view = entry.value\n if (typeof view.render !== 'function')\n return {\n message: 'the view has no render function',\n key: 'customization.issue.reason.viewNoRender',\n }\n if (typeof view.label !== 'string' || view.label.trim().length === 0)\n return { message: 'the view has no label', key: 'customization.issue.reason.viewNoLabel' }\n return null\n },\n })\n\n // Rail order for landing: each rail view (resolved), deduped, then the built-ins\n // not in the rail (so Settings — always available — is the final landing fallback).\n const inRail = new Set<string>()\n const landing: AppViewModule[] = []\n for (const view of railOrderViews) {\n const resolvedView = resolved.get(view.id)\n if (resolvedView && !inRail.has(resolvedView.id)) {\n inRail.add(resolvedView.id)\n landing.push(resolvedView)\n }\n }\n for (const view of DEFAULT_VIEWS) if (!inRail.has(view.id)) landing.push(view)\n\n const registry: ViewRegistry = { get: (id) => resolved.get(id), views: landing }\n\n // Build the display tree. Each view id is emitted at most once (the first of a\n // duplicate wins, matching `resolveOverrides`); a view dropped by validation is\n // skipped here too.\n const emitted = new Set<string>()\n const toRailView = (view: AppViewModule): ResolvedRailView | null => {\n const resolvedView = resolved.get(view.id)\n if (!resolvedView || emitted.has(resolvedView.id)) return null\n emitted.add(resolvedView.id)\n return {\n kind: 'view',\n id: resolvedView.id,\n label: resolvedView.label,\n icon: resolvedView.icon,\n path: resolvedView.id,\n availability: moduleAvailability(project, resolvedView),\n ...(resolvedView.visible ? { visible: resolvedView.visible } : {}),\n }\n }\n\n const railNodes: ResolvedRailNode[] = []\n for (const node of nodes) {\n if (node.kind === 'separator') {\n railNodes.push({ kind: 'separator' })\n continue\n }\n if (node.kind === 'group') {\n const views = node.children.map((child) => toRailView(child.view)).filter(isRailView)\n if (views.length > 0) railNodes.push({ kind: 'group', label: node.label, views })\n continue\n }\n // A top-level view, with its nested children (CMS collections) attached. The\n // parent is emitted first so its id wins any duplicate; children follow in order.\n const view = toRailView(node.view)\n if (!view) continue\n const children = childNodesOf(node, collections)\n .map((child) => toRailView(child.view))\n .filter(isRailView)\n if (children.length > 0) view.children = children\n railNodes.push(view)\n }\n\n // Keyspace findings first: a derived-id collision is the CAUSE of any duplicate that\n // follows it, so it reads before the symptom at the rail foot.\n return { nodes: railNodes, registry, issues: [...keyspace.issues, ...resolved.issues] }\n}\n\nfunction isRailView(view: ResolvedRailView | null): view is ResolvedRailView {\n return view !== null\n}\n\n/**\n * Evaluate a rail view's OPTIONAL nav-permission predicate (EC-240, AD-18):\n * default-open — absent → visible; present → the predicate decides against the\n * live role. UI reflection only; the server chokepoint is the real boundary and\n * a hidden view stays URL-addressable (EC-221).\n */\nexport function isNavViewVisible(\n view: Pick<ResolvedRailView, 'visible'>,\n ctx: NavPermissionContext,\n): boolean {\n return view.visible ? view.visible(ctx) : true\n}\n\n/** The active view id parsed from a studio pathname `/<projectId>/<viewId>[/…]`. */\nexport function activeViewIdFromPath(pathname: string): string | null {\n return pathname.split('/')[2] || null\n}\n\n/** The view a bare project URL should open to: the first navigable rail view (EC-221). */\nexport function landingViewId(nav: StudioNav, project: Project): string {\n return firstAvailableView(nav.registry.views, project)?.id ?? 'settings'\n}\n\n/**\n * The active project's resolved studio nav — the default rail or\n * `studioConfig.sidebar`, resolved over the built-in views through the EC-236 seam\n * (static per studio boot).\n */\nexport function useStudioNav(project: Project): StudioNav {\n // `resolveDeploymentSidebar`/`resolveDeploymentCollections` are config-holder reads\n // (stable per studio boot), so keying the memo on `project` alone is correct.\n return useMemo(\n () => resolveSidebar(resolveDeploymentSidebar(), project, resolveDeploymentCollections()),\n [project],\n )\n}\n"]}
@@ -1,10 +1,11 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { FieldDef } from '@elytracms/core/cms-core';
3
- import type { StudioI18nConfig } from '@elytracms/studio/lib/i18n';
4
- import type { StudioAiConfig } from '@elytracms/studio/lib/ai';
3
+ import type { StudioI18nConfig } from '../../lib/i18n/index.js';
4
+ import type { StudioAiConfig } from '../../lib/ai/index.js';
5
5
  import type { GeneratedPreviewLookup } from '../builder/blocks-model.js';
6
6
  import type { StudioReuseConfig } from '../builder/reuse.js';
7
7
  import type { SidebarNode } from './module.js';
8
+ import type { DocumentPane } from '../inspector/document-panes.js';
8
9
  /**
9
10
  * EC-236 — the studio customization config: the single, studio-only home for
10
11
  * everything a consuming repo customizes about the builder UI.
@@ -107,6 +108,14 @@ export interface StudioConfig {
107
108
  * validation issue, never a silent fallback to guessing.
108
109
  */
109
110
  reuse?: StudioReuseConfig;
111
+ /**
112
+ * Panes contributed to the document editor's inspector (EC-618 — AD-19's edit-shell
113
+ * slot), beside the frame's own Details / Versions / Issues: direct values, built with
114
+ * `documentPane({ id, label, render })` from `@elytracms/studio/addon` or exported by an
115
+ * addon (`analyticsDocumentPane`). CMS documents only; a pane's `collections` narrows it.
116
+ * Studio-only, like every key here.
117
+ */
118
+ documentPanes?: DocumentPane[];
110
119
  }
111
120
  /**
112
121
  * The generated-preview seam (EC-439 AD-2). `generated` is a PURE lookup —
@@ -1 +1 @@
1
- {"version":3,"file":"studio-config.js","sourceRoot":"","sources":["../../../src/components/app-views/studio-config.ts"],"names":[],"mappings":"AA8JA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import type { ReactNode } from 'react'\nimport type { FieldDef } from '@elytracms/core/cms-core'\nimport type { StudioI18nConfig } from '@elytracms/studio/lib/i18n'\nimport type { StudioAiConfig } from '@elytracms/studio/lib/ai'\nimport type { GeneratedPreviewLookup } from '../builder/blocks-model'\nimport type { StudioReuseConfig } from '../builder/reuse'\nimport type { SidebarNode } from './module'\n\n/**\n * EC-236 — the studio customization config: the single, studio-only home for\n * everything a consuming repo customizes about the builder UI.\n *\n * This is the \"separate studio-only config solution\": it lives in the consumer's\n * `studio.config.tsx`, is `defineStudio({...})`-typed, and is passed to\n * `createStudio(config, studioConfig)` at the mount — NEVER mixed into the shared\n * `elytra.config.ts`, so the live React it carries (views, icons, field renderers,\n * future branding) never reaches the `frontend/` delivery bundle.\n *\n * The customization PRINCIPLE it embodies: customization is **code passed\n * directly**, not data referenced by a string key. Every field here holds live\n * values/components by reference — `sidebar` is built from `nav.*` view objects,\n * `fieldRenderers` maps a control name to a component. The only string indirection\n * in the whole surface is `fieldRenderers`' key, and ONLY because its other end\n * (`form.control` on a field) lives in the delivery-shared serializable schema and\n * cannot import studio code. This config grows by adding direct-value keys\n * (branding/logo/panels/theme), never by adding registries.\n */\nexport interface StudioConfig {\n /**\n * The section rail (EC-237) as an ordered list of `nav.*` nodes — views, groups,\n * and separators. Omitted → the studio derives the default rail from its built-in\n * views. A rail node carries its view object directly; there are no string ids to\n * keep in sync.\n */\n sidebar?: SidebarNode[]\n /**\n * Custom field renderers (EC-238) keyed by CONTROL name (`form.control`), each a\n * React component replacing the built-in document-editor control. A field opts in\n * per-field via `form: { control: '<name>' }` — so a renderer targets exactly the\n * fields that ask for it, not every field of a datatype. This is the one\n * boundary-crossing seam: the field names its control by string in the shared\n * schema, this map binds that name to a studio-only component.\n */\n fieldRenderers?: Record<string, CustomFieldRenderer>\n /**\n * Studio-UI localization (EC-241): the default locale + imported translation\n * catalogs. English is the built-in source; a consumer adds e.g.\n * `i18n: { locales: { de: deMessages } }` and the studio chrome renders in the\n * browser's preferred locale (no persisted selector in v1). Studio-only — never\n * reaches the delivery bundle. Schema labels localize separately, via a\n * `LocalizedString` on the field/collection def.\n */\n i18n?: StudioI18nConfig\n /**\n * AI platform (EC-331): provider, project knowledge, and soft limits. The block's\n * PRESENCE enables the AI feature; its ABSENCE means the feature is fully absent —\n * no AI UI and no AI dependencies on the critical path (the runtime lives behind a\n * lazy boundary in `@elytracms/studio/lib/ai`, only reached when configured).\n * `enabled: false` is a temporary kill-switch. Studio-only live code (holds the\n * provider adapter + knowledge functions) — never reaches the delivery bundle.\n * Validated at load by `resolveAiConfig` (capability gate: tool calling + structured\n * output required, fail-loud).\n */\n ai?: StudioAiConfig\n /**\n * Canvas page-surface theming (EC-389): make the preview iframe wear the SITE's\n * chrome. Direct values (no registries): body classes + raw CSS, injected LAST\n * into the canvas iframe — after the studio defaults — so consumer values win\n * the cascade. This themes the canvas\n * PAGE surface only, never the studio chrome (branding/logo theming is a future,\n * separate key).\n */\n canvas?: StudioCanvasTheme\n /**\n * Insert-menu preview artifacts (EC-439, the seam EC-440 fills): where the\n * studio's blocks panel finds GENERATED preview images for component\n * fixtures. Studio-only (from `defineStudio`, never the delivery bundle) and\n * fully optional — absent, tiles fall back from a curated\n * `manifest.previewImage` straight to the icon+name placeholder. The EC-440\n * CLI (`elytra previews`) renders fixtures headlessly against the consumer\n * theme and writes a previews manifest; the consumer adapts that manifest\n * into `previews.generated` here.\n */\n previews?: StudioPreviewsConfig\n /**\n * Reusable elements (EC-467, AD-b): WHICH component is this project's\n * reusable-element embed. One line, and only when needed —\n * `reuse: { component: 'project.Reusable' }`.\n *\n * **ABSENCE MEANS INFER, not off.** Unlike the `ai?` precedent, omitting this\n * block does not disable anything: the studio derives the embed from config as\n * it always has (a slot-less component with one cardinality-one `relation` prop\n * whose target collection holds a `blocks` field), so the zero-config promise is\n * untouched and no existing consumer needs to change anything. Declare it only\n * when the inference cannot decide — the studio then says so, by name, in the\n * rail's issue list rather than silently switching the reuse gestures off.\n *\n * **Reusable elements are singular per project (AD-a):** exactly one embed\n * component and exactly one reusable collection. This is one value, never a\n * list. The collection and the composition field DERIVE from the declared\n * component (its sole single-document relation prop → that prop's target\n * collection → that collection's `blocks` field), which is why the declaration\n * stays a single identifier.\n *\n * Studio-only, like every key here — the whole feature is studio-side (canvas\n * gestures + publish integrity), so it has zero delivery-wire surface and never\n * reaches the `frontend/` bundle. A declaration that cannot resolve is a visible\n * validation issue, never a silent fallback to guessing.\n */\n reuse?: StudioReuseConfig\n}\n\n/**\n * The generated-preview seam (EC-439 AD-2). `generated` is a PURE lookup —\n * component id + fixture name → image URL (`undefined` = no artifact). A\n * fixture-less component's single implicit variant is looked up under the\n * fixture name `'Default'`. Curated `manifest.previewImage` always wins over\n * this lookup; the lookup wins over the placeholder.\n */\nexport interface StudioPreviewsConfig {\n generated?: GeneratedPreviewLookup\n}\n\n/**\n * The canvas page-surface theme (EC-389). Both knobs target the preview iframe\n * document — the isolated page the builder renders into — so a site whose theme\n * styles `body` (background, color, font) previews exactly as it delivers.\n */\nexport interface StudioCanvasTheme {\n /**\n * REPLACES the studio's default canvas body classes\n * (`bg-background text-foreground font-sans`). The default's utility classes\n * out-cascade a site theme's `@layer base body` rules (Tailwind layers lose to\n * later layers/un-layered styles), which is exactly how a site's page background\n * used to paint white in the canvas — pass `''` to drop the default entirely and\n * let the site's own `body` rules (from the canvas stylesheet) take over, or\n * pass your own classes. Omitted → the studio default stays.\n */\n bodyClassName?: string\n /**\n * Raw CSS injected into the canvas iframe as the LAST style element — after the\n * canvas stylesheet and the studio editing chrome — so it wins the cascade over\n * both (an un-layered `body { background: … }` or a `:root { --token: … }`\n * override both work).\n */\n tokensCss?: string\n}\n\n/** The props a custom field renderer receives — identical to the built-in control. */\nexport interface CustomFieldRendererProps {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}\nexport type CustomFieldRenderer = (props: CustomFieldRendererProps) => ReactNode\n\n/**\n * Typed identity helper for `studio.config.tsx` — keeps the config declarative\n * (like {@link import('@elytracms/core/config').defineElytraConfig}). No\n * validation here; the studio resolves + validates the sidebar and field renderers\n * through the EC-236 seam at mount, surfacing every issue visibly (never throws).\n *\n * @example\n * ```tsx\n * // studio.config.tsx\n * import { defineStudio, nav } from '@elytracms/studio/customization'\n * import { ColorField } from './fields/color'\n *\n * export default defineStudio({\n * sidebar: [\n * nav.cms({ label: 'Content' }),\n * nav.group('Catalog', [\n * nav.cms({ label: 'Catalog', collections: ['product', 'productCategory'] }),\n * nav.cms({ label: 'Categories', collection: 'productCategory' }),\n * ]),\n * nav.separator(),\n * nav.assets(),\n * nav.settings(),\n * ],\n * fieldRenderers: { color: ColorField },\n * })\n * ```\n */\nexport function defineStudio(config: StudioConfig): StudioConfig {\n return config\n}\n"]}
1
+ {"version":3,"file":"studio-config.js","sourceRoot":"","sources":["../../../src/components/app-views/studio-config.ts"],"names":[],"mappings":"AAuKA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import type { ReactNode } from 'react'\nimport type { FieldDef } from '@elytracms/core/cms-core'\nimport type { StudioI18nConfig } from '../../lib/i18n'\nimport type { StudioAiConfig } from '../../lib/ai'\nimport type { GeneratedPreviewLookup } from '../builder/blocks-model'\nimport type { StudioReuseConfig } from '../builder/reuse'\nimport type { SidebarNode } from './module'\nimport type { DocumentPane } from '../inspector/document-panes'\n\n/**\n * EC-236 — the studio customization config: the single, studio-only home for\n * everything a consuming repo customizes about the builder UI.\n *\n * This is the \"separate studio-only config solution\": it lives in the consumer's\n * `studio.config.tsx`, is `defineStudio({...})`-typed, and is passed to\n * `createStudio(config, studioConfig)` at the mount — NEVER mixed into the shared\n * `elytra.config.ts`, so the live React it carries (views, icons, field renderers,\n * future branding) never reaches the `frontend/` delivery bundle.\n *\n * The customization PRINCIPLE it embodies: customization is **code passed\n * directly**, not data referenced by a string key. Every field here holds live\n * values/components by reference — `sidebar` is built from `nav.*` view objects,\n * `fieldRenderers` maps a control name to a component. The only string indirection\n * in the whole surface is `fieldRenderers`' key, and ONLY because its other end\n * (`form.control` on a field) lives in the delivery-shared serializable schema and\n * cannot import studio code. This config grows by adding direct-value keys\n * (branding/logo/panels/theme), never by adding registries.\n */\nexport interface StudioConfig {\n /**\n * The section rail (EC-237) as an ordered list of `nav.*` nodes — views, groups,\n * and separators. Omitted → the studio derives the default rail from its built-in\n * views. A rail node carries its view object directly; there are no string ids to\n * keep in sync.\n */\n sidebar?: SidebarNode[]\n /**\n * Custom field renderers (EC-238) keyed by CONTROL name (`form.control`), each a\n * React component replacing the built-in document-editor control. A field opts in\n * per-field via `form: { control: '<name>' }` — so a renderer targets exactly the\n * fields that ask for it, not every field of a datatype. This is the one\n * boundary-crossing seam: the field names its control by string in the shared\n * schema, this map binds that name to a studio-only component.\n */\n fieldRenderers?: Record<string, CustomFieldRenderer>\n /**\n * Studio-UI localization (EC-241): the default locale + imported translation\n * catalogs. English is the built-in source; a consumer adds e.g.\n * `i18n: { locales: { de: deMessages } }` and the studio chrome renders in the\n * browser's preferred locale (no persisted selector in v1). Studio-only — never\n * reaches the delivery bundle. Schema labels localize separately, via a\n * `LocalizedString` on the field/collection def.\n */\n i18n?: StudioI18nConfig\n /**\n * AI platform (EC-331): provider, project knowledge, and soft limits. The block's\n * PRESENCE enables the AI feature; its ABSENCE means the feature is fully absent —\n * no AI UI and no AI dependencies on the critical path (the runtime lives behind a\n * lazy boundary in `@elytracms/studio/lib/ai`, only reached when configured).\n * `enabled: false` is a temporary kill-switch. Studio-only live code (holds the\n * provider adapter + knowledge functions) — never reaches the delivery bundle.\n * Validated at load by `resolveAiConfig` (capability gate: tool calling + structured\n * output required, fail-loud).\n */\n ai?: StudioAiConfig\n /**\n * Canvas page-surface theming (EC-389): make the preview iframe wear the SITE's\n * chrome. Direct values (no registries): body classes + raw CSS, injected LAST\n * into the canvas iframe — after the studio defaults — so consumer values win\n * the cascade. This themes the canvas\n * PAGE surface only, never the studio chrome (branding/logo theming is a future,\n * separate key).\n */\n canvas?: StudioCanvasTheme\n /**\n * Insert-menu preview artifacts (EC-439, the seam EC-440 fills): where the\n * studio's blocks panel finds GENERATED preview images for component\n * fixtures. Studio-only (from `defineStudio`, never the delivery bundle) and\n * fully optional — absent, tiles fall back from a curated\n * `manifest.previewImage` straight to the icon+name placeholder. The EC-440\n * CLI (`elytra previews`) renders fixtures headlessly against the consumer\n * theme and writes a previews manifest; the consumer adapts that manifest\n * into `previews.generated` here.\n */\n previews?: StudioPreviewsConfig\n /**\n * Reusable elements (EC-467, AD-b): WHICH component is this project's\n * reusable-element embed. One line, and only when needed —\n * `reuse: { component: 'project.Reusable' }`.\n *\n * **ABSENCE MEANS INFER, not off.** Unlike the `ai?` precedent, omitting this\n * block does not disable anything: the studio derives the embed from config as\n * it always has (a slot-less component with one cardinality-one `relation` prop\n * whose target collection holds a `blocks` field), so the zero-config promise is\n * untouched and no existing consumer needs to change anything. Declare it only\n * when the inference cannot decide — the studio then says so, by name, in the\n * rail's issue list rather than silently switching the reuse gestures off.\n *\n * **Reusable elements are singular per project (AD-a):** exactly one embed\n * component and exactly one reusable collection. This is one value, never a\n * list. The collection and the composition field DERIVE from the declared\n * component (its sole single-document relation prop → that prop's target\n * collection → that collection's `blocks` field), which is why the declaration\n * stays a single identifier.\n *\n * Studio-only, like every key here — the whole feature is studio-side (canvas\n * gestures + publish integrity), so it has zero delivery-wire surface and never\n * reaches the `frontend/` bundle. A declaration that cannot resolve is a visible\n * validation issue, never a silent fallback to guessing.\n */\n reuse?: StudioReuseConfig\n /**\n * Panes contributed to the document editor's inspector (EC-618 — AD-19's edit-shell\n * slot), beside the frame's own Details / Versions / Issues: direct values, built with\n * `documentPane({ id, label, render })` from `@elytracms/studio/addon` or exported by an\n * addon (`analyticsDocumentPane`). CMS documents only; a pane's `collections` narrows it.\n * Studio-only, like every key here.\n */\n documentPanes?: DocumentPane[]\n}\n\n/**\n * The generated-preview seam (EC-439 AD-2). `generated` is a PURE lookup —\n * component id + fixture name → image URL (`undefined` = no artifact). A\n * fixture-less component's single implicit variant is looked up under the\n * fixture name `'Default'`. Curated `manifest.previewImage` always wins over\n * this lookup; the lookup wins over the placeholder.\n */\nexport interface StudioPreviewsConfig {\n generated?: GeneratedPreviewLookup\n}\n\n/**\n * The canvas page-surface theme (EC-389). Both knobs target the preview iframe\n * document — the isolated page the builder renders into — so a site whose theme\n * styles `body` (background, color, font) previews exactly as it delivers.\n */\nexport interface StudioCanvasTheme {\n /**\n * REPLACES the studio's default canvas body classes\n * (`bg-background text-foreground font-sans`). The default's utility classes\n * out-cascade a site theme's `@layer base body` rules (Tailwind layers lose to\n * later layers/un-layered styles), which is exactly how a site's page background\n * used to paint white in the canvas — pass `''` to drop the default entirely and\n * let the site's own `body` rules (from the canvas stylesheet) take over, or\n * pass your own classes. Omitted → the studio default stays.\n */\n bodyClassName?: string\n /**\n * Raw CSS injected into the canvas iframe as the LAST style element — after the\n * canvas stylesheet and the studio editing chrome — so it wins the cascade over\n * both (an un-layered `body { background: … }` or a `:root { --token: … }`\n * override both work).\n */\n tokensCss?: string\n}\n\n/** The props a custom field renderer receives — identical to the built-in control. */\nexport interface CustomFieldRendererProps {\n field: FieldDef\n value: unknown\n onChange: (value: unknown) => void\n readOnly: boolean\n id: string\n}\nexport type CustomFieldRenderer = (props: CustomFieldRendererProps) => ReactNode\n\n/**\n * Typed identity helper for `studio.config.tsx` — keeps the config declarative\n * (like {@link import('@elytracms/core/config').defineElytraConfig}). No\n * validation here; the studio resolves + validates the sidebar and field renderers\n * through the EC-236 seam at mount, surfacing every issue visibly (never throws).\n *\n * @example\n * ```tsx\n * // studio.config.tsx\n * import { defineStudio, nav } from '@elytracms/studio/customization'\n * import { ColorField } from './fields/color'\n *\n * export default defineStudio({\n * sidebar: [\n * nav.cms({ label: 'Content' }),\n * nav.group('Catalog', [\n * nav.cms({ label: 'Catalog', collections: ['product', 'productCategory'] }),\n * nav.cms({ label: 'Categories', collection: 'productCategory' }),\n * ]),\n * nav.separator(),\n * nav.assets(),\n * nav.settings(),\n * ],\n * fieldRenderers: { color: ColorField },\n * })\n * ```\n */\nexport function defineStudio(config: StudioConfig): StudioConfig {\n return config\n}\n"]}
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useMemo, useState } from 'react';
3
3
  import { AlertTriangle, Link2, RotateCcw, Trash2 } from 'lucide-react';
4
4
  import { Link, STUDIO_PROJECT_ROUTE, useNavigate } from '@elytracms/studio/router';
5
- import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
6
- import { buildAssetReferenceImpact, loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references';
5
+ import { localizedLabel, translate } from '../../lib/i18n/index.js';
6
+ import { buildAssetReferenceImpact, loadInboundAssetReferenceViews } from '../../lib/references/index.js';
7
7
  import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Field, Heading, Panel, PanelBody, PanelHeader, Row, Spacer, Stack, Text, } from '@elytracms/studio/ui';
8
8
  import { FieldControl } from '../cms/field-control.js';
9
9
  import { fieldLabel } from '../cms/cms-labels.js';
@@ -12,8 +12,9 @@ import { ReferencesPanel } from '../references/references-panel.js';
12
12
  import { useSetBreadcrumb } from '../breadcrumb-context.js';
13
13
  import { useCollectionView } from '../collection-view/collection-view-context.js';
14
14
  import { FocalCropPreviews, FocalPointPicker, useAssetRecord } from './asset-thumb.js';
15
- import { assetMetaFieldsOf, formatBytes, pruneMeta } from './asset-metadata.js';
15
+ import { assetMetaFieldsOf, assetMetaSeed, formatBytes, pruneMeta } from './asset-metadata.js';
16
16
  import { useAssets } from './assets-store.js';
17
+ import { ReplaceFileButton } from './replace-file.js';
17
18
  /**
18
19
  * The assets rail's DETAIL surface (EC-319). Pragmatic call: assets don't fit
19
20
  * the portable `PortableDetailEditor` shell cleanly — there's no dirty/Save/
@@ -69,14 +70,23 @@ function AssetDetailBody({ projectId, record, collection, }) {
69
70
  // no dirty/Save document model of its own to piggyback on. The focal point
70
71
  // commits immediately (it drives a live visual), matching the field's options
71
72
  // dialog (`field-control.tsx`).
72
- const [meta, setMeta] = useState(() => ({
73
- ...(record.alt !== undefined ? { alt: record.alt } : {}),
74
- ...(record.meta ?? {}),
75
- }));
73
+ // EC-587: the bag the editor starts from has a legacy `alt` column lifted in, so a
74
+ // not-yet-migrated asset shows its alt and the save moves it to `meta.alt`.
75
+ const [meta, setMeta] = useState(() => assetMetaSeed(record));
76
76
  const [focal, setFocal] = useState(record.focalPoint);
77
77
  const [dirty, setDirty] = useState(false);
78
78
  const [saving, setSaving] = useState(false);
79
79
  const [saveError, setSaveError] = useState(null);
80
+ // EC-568: after a file replace the focal point is KEPT (the ticket's recommendation),
81
+ // but it was chosen on the old pixels — say so beside the picker.
82
+ const [replaced, setReplaced] = useState(false);
83
+ function onReplaced(asset) {
84
+ // The new file may have brought metadata: start the buffer from the stored record.
85
+ setMeta(assetMetaSeed(asset));
86
+ setFocal(asset.focalPoint);
87
+ setDirty(false);
88
+ setReplaced(true);
89
+ }
80
90
  function setField(name, value) {
81
91
  setDirty(true);
82
92
  setMeta((m) => ({ ...m, [name]: value }));
@@ -95,13 +105,13 @@ function AssetDetailBody({ projectId, record, collection, }) {
95
105
  setFocal(point ?? undefined);
96
106
  void store.updateMetadata(record.id, { focalPoint: point });
97
107
  }
98
- return (_jsxs(Stack, { gap: "4", className: "mx-auto max-w-3xl p-4 pb-10 sm:p-6", children: [_jsx(AssetDetailHeader, { projectId: projectId, record: record }), _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsxs(Row, { align: "center", className: "w-full", children: [_jsx(Text, { size: "xs", weight: "semibold", tone: "muted", children: translate('cms.asset.focalPoint') }), _jsx(Spacer, {}), focal ? (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => commitFocal(null), children: [_jsx(RotateCcw, {}), " ", translate('cms.asset.resetToCentre')] })) : null] }) }), _jsx(PanelBody, { children: _jsxs(Stack, { gap: "2", children: [_jsx(FocalPointPicker, { record: record, point: focal, onChange: commitFocal, onPreview: setFocal }), _jsx(Text, { size: "xs", tone: "subtle", children: translate('cms.asset.focalPointHint') }), _jsx(FocalCropPreviews, { record: record, point: focal })] }) })] }), _jsxs(Panel, { "data-testid": "asset-detail-meta", children: [_jsx(PanelHeader, { children: _jsx(Text, { size: "xs", weight: "semibold", tone: "muted", children: translate('assets.detail.metadataHeading') }) }), _jsx(PanelBody, { children: _jsxs(Stack, { gap: "3", children: [metaFields.map((f) => (_jsx(Field, { label: fieldLabel(f), hint: localizedLabel(f.form?.description) || undefined, children: _jsx(FieldControl, { id: `asset-detail-${f.name}`, field: f, value: meta[f.name], readOnly: false, onChange: (v) => setField(f.name, v) }) }, f.name))), metaFields.length === 0 ? (_jsx(Text, { size: "sm", tone: "subtle", children: translate('cms.asset.noMetaFields', { code1: 'alt', code2: 'copyright' }) })) : null, saveError ? (_jsx(Text, { size: "sm", className: "text-destructive", "data-testid": "asset-detail-save-error", children: saveError })) : null, metaFields.length > 0 ? (_jsxs(Row, { gap: "2", children: [_jsx(Spacer, {}), _jsx(Button, { size: "sm", onClick: () => void save(), disabled: !dirty || saving, "data-testid": "asset-detail-save", children: saving ? translate('common.saving') : translate('common.save') })] })) : null] }) })] })] }));
108
+ return (_jsxs(Stack, { gap: "4", className: "mx-auto max-w-3xl p-4 pb-10 sm:p-6", children: [_jsx(AssetDetailHeader, { projectId: projectId, record: record, replace: _jsx(ReplaceFileButton, { record: record, disabled: dirty, disabledHint: translate('cms.asset.replaceSaveFirst'), onReplaced: onReplaced }) }), _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsxs(Row, { align: "center", className: "w-full", children: [_jsx(Text, { size: "xs", weight: "semibold", tone: "muted", children: translate('cms.asset.focalPoint') }), _jsx(Spacer, {}), focal ? (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => commitFocal(null), children: [_jsx(RotateCcw, {}), " ", translate('cms.asset.resetToCentre')] })) : null] }) }), _jsx(PanelBody, { children: _jsxs(Stack, { gap: "2", children: [_jsx(FocalPointPicker, { record: record, point: focal, onChange: commitFocal, onPreview: setFocal }), replaced ? (_jsx(Text, { size: "xs", className: "font-medium", "data-testid": "asset-replaced-check-focal", children: translate('cms.asset.replacedCheckFocal') })) : null, _jsx(Text, { size: "xs", tone: "subtle", children: translate('cms.asset.focalPointHint') }), _jsx(FocalCropPreviews, { record: record, point: focal })] }) })] }), _jsxs(Panel, { "data-testid": "asset-detail-meta", children: [_jsx(PanelHeader, { children: _jsx(Text, { size: "xs", weight: "semibold", tone: "muted", children: translate('assets.detail.metadataHeading') }) }), _jsx(PanelBody, { children: _jsxs(Stack, { gap: "3", children: [metaFields.map((f) => (_jsx(Field, { label: fieldLabel(f), hint: localizedLabel(f.form?.description) || undefined, children: _jsx(FieldControl, { id: `asset-detail-${f.name}`, field: f, value: meta[f.name], readOnly: false, onChange: (v) => setField(f.name, v) }) }, f.name))), metaFields.length === 0 ? (_jsx(Text, { size: "sm", tone: "subtle", children: translate('cms.asset.noMetaFields', { code1: 'alt', code2: 'copyright' }) })) : null, saveError ? (_jsx(Text, { size: "sm", className: "text-destructive", "data-testid": "asset-detail-save-error", children: saveError })) : null, metaFields.length > 0 ? (_jsxs(Row, { gap: "2", children: [_jsx(Spacer, {}), _jsx(Button, { size: "sm", onClick: () => void save(), disabled: !dirty || saving, "data-testid": "asset-detail-save", children: saving ? translate('common.saving') : translate('common.save') })] })) : null] }) })] })] }));
99
109
  }
100
110
  /** Filename/title heading + system info (content type, dimensions, size) + actions. */
101
- function AssetDetailHeader({ projectId, record }) {
111
+ function AssetDetailHeader({ projectId, record, replace, }) {
102
112
  const dims = record.width && record.height ? `${record.width}×${record.height}` : null;
103
113
  const info = [record.contentType, dims, formatBytes(record.byteSize)].filter(Boolean).join(' · ');
104
- return (_jsxs(Row, { gap: "3", align: "start", wrap: true, children: [_jsxs(Stack, { gap: "1", className: "min-w-0 flex-1", children: [_jsx(Heading, { level: 2, size: "h2", className: "truncate", title: record.title ?? record.filename, children: record.title ?? record.filename }), record.title ? (_jsx(Text, { size: "sm", tone: "muted", className: "truncate", children: record.filename })) : null, _jsx(Text, { size: "xs", tone: "subtle", children: info })] }), _jsxs(Row, { gap: "2", children: [_jsx(AssetReferencesButton, { projectId: projectId, record: record }), _jsx(AssetDeleteButton, { projectId: projectId, record: record })] })] }));
114
+ return (_jsxs(Row, { gap: "3", align: "start", wrap: true, children: [_jsxs(Stack, { gap: "1", className: "min-w-0 flex-1", children: [_jsx(Heading, { level: 2, size: "h2", className: "truncate", title: record.title ?? record.filename, children: record.title ?? record.filename }), record.title ? (_jsx(Text, { size: "sm", tone: "muted", className: "truncate", children: record.filename })) : null, _jsx(Text, { size: "xs", tone: "subtle", children: info })] }), _jsxs(Row, { gap: "2", wrap: true, children: [replace, _jsx(AssetReferencesButton, { projectId: projectId, record: record }), _jsx(AssetDeleteButton, { projectId: projectId, record: record })] })] }));
105
115
  }
106
116
  /** "Where is this asset used?" (EC-172): the shared references panel, EC-155-backed. */
107
117
  function AssetReferencesButton({ projectId, record }) {
@@ -1 +1 @@
1
- {"version":3,"file":"asset-detail.js","sourceRoot":"","sources":["../../../src/components/assets/asset-detail.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAGtE,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,yBAAyB,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAA;AAE5G,OAAO,EACL,MAAM,EACN,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,KAAK,EACL,OAAO,EACP,KAAK,EACL,SAAS,EACT,WAAW,EACX,GAAG,EACH,MAAM,EACN,KAAK,EACL,IAAI,GACL,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAC9E,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnF,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,SAAS,EAAE,OAAO,EAA0C;IACxF,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IACtC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAA;IAEhC,MAAM,eAAe,GAAG,cAAc,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC,CAAA;IAC7E,gBAAgB,CACd,OAAO,CAAC,GAAG,EAAE;QACX,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAO,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAA;QACtE,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAA;QAC1D,OAAO;YACL,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE;YACpG,EAAE,KAAK,EAAE,KAAK,EAAE;SACjB,CAAA;IACH,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CACnE,CAAA;IAED,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,CACL,KAAC,KAAK,IAAC,SAAS,EAAC,+BAA+B,EAAC,KAAK,EAAC,QAAQ,EAAC,OAAO,EAAC,QAAQ,YAC9E,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,gBAAgB,CAAC,GACvB,GACD,CACT,CAAA;QACH,CAAC;QACD,OAAO,CACL,cAAK,SAAS,EAAC,oCAAoC,YACjD,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,aAAa,aACnD,KAAC,OAAO,IAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAC,IAAI,YACzB,SAAS,CAAC,0BAA0B,CAAC,GAC9B,EACT,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,CAC/B,KAAC,IAAI,IACH,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAC9C,SAAS,EAAC,sCAAsC,YAE/C,SAAS,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,GAC1D,CACR,CAAC,CAAC,CAAC,IAAI,IACF,GACJ,CACP,CAAA;IACH,CAAC;IAED,4EAA4E;IAC5E,wEAAwE;IACxE,+DAA+D;IAC/D,OAAO,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,GAAI,CAAA;AAC7F,CAAC;AAED,SAAS,eAAe,CAAC,EACvB,SAAS,EACT,MAAM,EACN,UAAU,GAKX;IACC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAE7E,+EAA+E;IAC/E,2EAA2E;IAC3E,8EAA8E;IAC9E,gCAAgC;IAChC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAA0B,GAAG,EAAE,CAAC,CAAC;QAC/D,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;KACvB,CAAC,CAAC,CAAA;IACH,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAyB,MAAM,CAAC,UAAU,CAAC,CAAA;IAC7E,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAE/D,SAAS,QAAQ,CAAC,IAAY,EAAE,KAAc;QAC5C,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IAC3C,CAAC;IAED,KAAK,UAAU,IAAI;QACjB,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,YAAY,CAAC,IAAI,CAAC,CAAA;QAClB,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC/E,SAAS,CAAC,KAAK,CAAC,CAAA;QAChB,IAAI,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;;YACzB,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,CAAC;IAED,SAAS,WAAW,CAAC,KAAwB;QAC3C,QAAQ,CAAC,KAAK,IAAI,SAAS,CAAC,CAAA;QAC5B,KAAK,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAA;IAC7D,CAAC;IAED,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,oCAAoC,aAC3D,KAAC,iBAAiB,IAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAI,EAE3D,MAAC,KAAK,eACJ,KAAC,WAAW,cACV,MAAC,GAAG,IAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,QAAQ,aACpC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,EAAC,IAAI,EAAC,OAAO,YAC3C,SAAS,CAAC,sBAAsB,CAAC,GAC7B,EACP,KAAC,MAAM,KAAG,EACT,KAAK,CAAC,CAAC,CAAC,CACP,MAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,aAChE,KAAC,SAAS,KAAG,OAAE,SAAS,CAAC,yBAAyB,CAAC,IAC5C,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,GACM,EACd,KAAC,SAAS,cACR,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACZ,KAAC,gBAAgB,IAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,GAAI,EAC9F,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,SAAS,CAAC,0BAA0B,CAAC,GACjC,EACP,KAAC,iBAAiB,IAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAI,IAC7C,GACE,IACN,EAER,MAAC,KAAK,mBAAa,mBAAmB,aACpC,KAAC,WAAW,cACV,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,EAAC,IAAI,EAAC,OAAO,YAC3C,SAAS,CAAC,+BAA+B,CAAC,GACtC,GACK,EACd,KAAC,SAAS,cACR,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACX,UAAU,CAAC,GAAG,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC,CAC/B,KAAC,KAAK,IAEJ,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EACpB,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,SAAS,YAEtD,KAAC,YAAY,IACX,EAAE,EAAE,gBAAgB,CAAC,CAAC,IAAI,EAAE,EAC5B,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EACnB,QAAQ,EAAE,KAAK,EACf,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GACpC,IAVG,CAAC,CAAC,IAAI,CAWL,CACT,CAAC,EACD,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACzB,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,SAAS,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,GACrE,CACR,CAAC,CAAC,CAAC,IAAI,EACP,SAAS,CAAC,CAAC,CAAC,CACX,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,kBAAkB,iBAAa,yBAAyB,YAC/E,SAAS,GACL,CACR,CAAC,CAAC,CAAC,IAAI,EACP,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACvB,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,MAAM,KAAG,EACV,KAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAC1B,QAAQ,EAAE,CAAC,KAAK,IAAI,MAAM,iBACd,mBAAmB,YAE9B,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,GACxD,IACL,CACP,CAAC,CAAC,CAAC,IAAI,IACF,GACE,IACN,IACF,CACT,CAAA;AACH,CAAC;AAED,uFAAuF;AACvF,SAAS,iBAAiB,CAAC,EAAE,SAAS,EAAE,MAAM,EAA8C;IAC1F,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IACtF,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACjG,OAAO,CACL,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,OAAO,EAAC,IAAI,mBAC7B,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,gBAAgB,aACvC,KAAC,OAAO,IAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,YACrF,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,GACxB,EACT,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CACd,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,UAAU,YAC9C,MAAM,CAAC,QAAQ,GACX,CACR,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,IAAI,GACA,IACD,EACR,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,qBAAqB,IAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAI,EAC/D,KAAC,iBAAiB,IAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAI,IACvD,IACF,CACP,CAAA;AACH,CAAC;AAED,wFAAwF;AACxF,SAAS,qBAAqB,CAAC,EAAE,SAAS,EAAE,MAAM,EAA8C;IAC9F,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,OAAO,CACL,8BACE,MAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,iBAChB,yBAAyB,aAErC,KAAC,KAAK,KAAG,OAAE,SAAS,CAAC,+BAA+B,CAAC,IAC9C,EACT,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,YACvC,MAAC,aAAa,IAAC,SAAS,EAAC,UAAU,aACjC,MAAC,YAAY,eACX,KAAC,WAAW,cACT,SAAS,CAAC,yBAAyB,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC,GACpE,EACd,KAAC,iBAAiB,cAAE,SAAS,CAAC,+BAA+B,CAAC,GAAqB,IACtE,EACd,IAAI,CAAC,CAAC,CAAC,CACN,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,GAAI,CACpF,CAAC,CAAC,CAAC,IAAI,IACM,GACT,IACR,CACJ,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,EAAE,SAAS,EAAE,MAAM,EAA8C;IAC1F,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAA8B,IAAI,CAAC,CAAA;IACvE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEvC,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;QAC5B,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,IAAI,CAAC;YACH,SAAS,CAAC,MAAM,8BAA8B,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;QACvE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAClE,CAAC;IACH,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,6EAA6E;QAC7E,wEAAwE;QACxE,sEAAsE;QACtE,kEAAkE;QAClE,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAA;QACpE,OAAO,CAAC,KAAK,CAAC,CAAA;QACd,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;YACnE,OAAO,CAAC,KAAK,CAAC,CAAA;YACd,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAClC,KAAK,QAAQ,CAAC,EAAE,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;YAC7F,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACxC,QAAQ,CACN,SAAS,CACP,MAAM,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,+BAA+B,EACzF,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,CAC7B,CACF,CAAA;QACH,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAChE,MAAM,UAAU,GAAG,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAA;IAC7C,OAAO,CACL,8BACE,MAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,UAAU,EAAE,iBACpB,qBAAqB,aAEjC,KAAC,MAAM,KAAG,OAAE,SAAS,CAAC,eAAe,CAAC,IAC/B,EACT,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,YACvC,MAAC,aAAa,IAAC,SAAS,EAAC,uCAAuC,aAC9D,MAAC,YAAY,eACX,KAAC,WAAW,cAAE,SAAS,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAe,EAC5F,KAAC,iBAAiB,cAAE,SAAS,CAAC,2BAA2B,CAAC,GAAqB,IAClE,EACd,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CACnC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,wBAAwB,CAAC,GAC/B,CACR,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CACnB,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,iBAAa,qBAAqB,aAC9C,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,aAAa,IAAC,SAAS,EAAC,qBAAqB,GAAG,EACjD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,YAC9B,SAAS,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,2BAA2B,EAAE;gDACrF,KAAK,EAAE,UAAU;6CAClB,CAAC,GACG,IACH,EACN,KAAC,mBAAmB,IAClB,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAO,CAAC,MAAM,EACtB,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAChC,EACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,6BAA6B,CAAC,GACpC,IACD,CACT,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,4BAA4B,CAAC,GACnC,CACR,EACA,KAAK,CAAC,CAAC,CAAC,CACP,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,kBAAkB,iBAAa,oBAAoB,YAC1E,KAAK,GACD,CACR,CAAC,CAAC,CAAC,IAAI,EACR,MAAC,YAAY,eACX,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,YAC5E,SAAS,CAAC,eAAe,CAAC,GACpB,EACT,KAAC,MAAM,IACL,OAAO,EAAC,aAAa,EACrB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,OAAO,EAAE,EAC7B,QAAQ,EAAE,IAAI,IAAI,MAAM,KAAK,IAAI,iBACrB,sBAAsB,YAEjC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,GAChF,IACI,IACD,GACT,IACR,CACJ,CAAA;AACH,CAAC","sourcesContent":["import { useMemo, useState } from 'react'\nimport { AlertTriangle, Link2, RotateCcw, Trash2 } from 'lucide-react'\nimport type { CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Link, STUDIO_PROJECT_ROUTE, useNavigate } from '@elytracms/studio/router'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { buildAssetReferenceImpact, loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Heading,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { FieldControl } from '../cms/field-control'\nimport { fieldLabel } from '../cms/cms-labels'\nimport { ReferenceImpactRows } from '../cms/reference-confirm'\nimport { ReferencesPanel } from '../references/references-panel'\nimport { useSetBreadcrumb } from '../breadcrumb-context'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { FocalCropPreviews, FocalPointPicker, useAssetRecord } from './asset-thumb'\nimport type { FocalPoint } from './focal-point'\nimport { assetMetaFieldsOf, formatBytes, pruneMeta } from './asset-metadata'\nimport { useAssets } from './assets-store'\n\n/**\n * The assets rail's DETAIL surface (EC-319). Pragmatic call: assets don't fit\n * the portable `PortableDetailEditor` shell cleanly — there's no dirty/Save/\n * Publish document model here (an asset record isn't a `cmsDocument`), and the\n * focal-point picker is a bespoke drag interaction over the image, not a\n * scalar field value a generic form row can host without contortions. So this\n * is a bespoke surface (mirrors the CMS document editor's shape: a header +\n * panels), reached via the SAME rail-owned `detailPath`/`listPath` seam every\n * other rail uses (AD-19) — only the body differs.\n *\n * Unifies what were two divergent asset-edit UIs before this task: the plain\n * alt/title dialog on the old asset library card, and the richer schema-driven\n * + focal-point \"image options\" dialog opened from an `asset` field elsewhere\n * (`field-control.tsx`). This page reuses the LATTER (richer) content, so\n * editing an asset here is never a downgrade from editing it via a field.\n *\n * Deletion keeps the EC-202 reference-index safe-delete path (`assets-store.tsx`\n * → `deleteAsset`, keyed on `{ type: 'asset' }`). Since EC-433 the universal\n * table's row/bulk delete runs the SAME asset-shaped reference check (the\n * source's `referenceTarget`) and the same confirmed store delete — the two\n * surfaces share one delete semantics, not two divergent ones.\n */\nexport function AssetDetail({ projectId, assetId }: { projectId: string; assetId: string }) {\n const source = useCollectionView()\n const store = useAssets()\n const record = useAssetRecord(assetId)\n const collection = source.schema\n\n const collectionLabel = localizedLabel(collection.form?.label, collection.id)\n useSetBreadcrumb(\n useMemo(() => {\n if (source.listPath === undefined) return [{ label: collectionLabel }]\n const title = record?.title ?? record?.filename ?? assetId\n return [\n { label: collectionLabel, to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } },\n { label: title },\n ]\n }, [collectionLabel, source.listPath, record, projectId, assetId]),\n )\n\n if (record === undefined) {\n if (store.status === 'loading') {\n return (\n <Stack className=\"h-full place-items-center p-8\" align=\"center\" justify=\"center\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.loading')}\n </Text>\n </Stack>\n )\n }\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"text-center\">\n <Heading level={2} size=\"h2\">\n {translate('cms.editor.notFoundTitle')}\n </Heading>\n {source.listPath !== undefined ? (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: source.listPath }}\n className=\"text-sm text-primary hover:underline\"\n >\n {translate('cms.editor.backToList', { label: collectionLabel })}\n </Link>\n ) : null}\n </Stack>\n </div>\n )\n }\n\n // Remount per asset (the surface's own key, set by the caller) resets every\n // local buffer below cleanly when navigating from one asset's detail to\n // another — no stale-state carryover to guard against by hand.\n return <AssetDetailBody projectId={projectId} record={record} collection={source.schema} />\n}\n\nfunction AssetDetailBody({\n projectId,\n record,\n collection,\n}: {\n projectId: string\n record: AssetRecord\n collection: CollectionDef\n}) {\n const store = useAssets()\n const metaFields = useMemo(() => assetMetaFieldsOf(collection), [collection])\n\n // Metadata buffers locally and commits explicitly (Save) — an asset record has\n // no dirty/Save document model of its own to piggyback on. The focal point\n // commits immediately (it drives a live visual), matching the field's options\n // dialog (`field-control.tsx`).\n const [meta, setMeta] = useState<Record<string, unknown>>(() => ({\n ...(record.alt !== undefined ? { alt: record.alt } : {}),\n ...(record.meta ?? {}),\n }))\n const [focal, setFocal] = useState<FocalPoint | undefined>(record.focalPoint)\n const [dirty, setDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<string | null>(null)\n\n function setField(name: string, value: unknown) {\n setDirty(true)\n setMeta((m) => ({ ...m, [name]: value }))\n }\n\n async function save() {\n setSaving(true)\n setSaveError(null)\n const result = await store.updateMetadata(record.id, { meta: pruneMeta(meta) })\n setSaving(false)\n if (result.ok) setDirty(false)\n else setSaveError(result.message)\n }\n\n function commitFocal(point: FocalPoint | null) {\n setFocal(point ?? undefined)\n void store.updateMetadata(record.id, { focalPoint: point })\n }\n\n return (\n <Stack gap=\"4\" className=\"mx-auto max-w-3xl p-4 pb-10 sm:p-6\">\n <AssetDetailHeader projectId={projectId} record={record} />\n\n <Panel>\n <PanelHeader>\n <Row align=\"center\" className=\"w-full\">\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('cms.asset.focalPoint')}\n </Text>\n <Spacer />\n {focal ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => commitFocal(null)}>\n <RotateCcw /> {translate('cms.asset.resetToCentre')}\n </Button>\n ) : null}\n </Row>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"2\">\n <FocalPointPicker record={record} point={focal} onChange={commitFocal} onPreview={setFocal} />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('cms.asset.focalPointHint')}\n </Text>\n <FocalCropPreviews record={record} point={focal} />\n </Stack>\n </PanelBody>\n </Panel>\n\n <Panel data-testid=\"asset-detail-meta\">\n <PanelHeader>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('assets.detail.metadataHeading')}\n </Text>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"3\">\n {metaFields.map((f: FieldDef) => (\n <Field\n key={f.name}\n label={fieldLabel(f)}\n hint={localizedLabel(f.form?.description) || undefined}\n >\n <FieldControl\n id={`asset-detail-${f.name}`}\n field={f}\n value={meta[f.name]}\n readOnly={false}\n onChange={(v) => setField(f.name, v)}\n />\n </Field>\n ))}\n {metaFields.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n {translate('cms.asset.noMetaFields', { code1: 'alt', code2: 'copyright' })}\n </Text>\n ) : null}\n {saveError ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-detail-save-error\">\n {saveError}\n </Text>\n ) : null}\n {metaFields.length > 0 ? (\n <Row gap=\"2\">\n <Spacer />\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={!dirty || saving}\n data-testid=\"asset-detail-save\"\n >\n {saving ? translate('common.saving') : translate('common.save')}\n </Button>\n </Row>\n ) : null}\n </Stack>\n </PanelBody>\n </Panel>\n </Stack>\n )\n}\n\n/** Filename/title heading + system info (content type, dimensions, size) + actions. */\nfunction AssetDetailHeader({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const dims = record.width && record.height ? `${record.width}×${record.height}` : null\n const info = [record.contentType, dims, formatBytes(record.byteSize)].filter(Boolean).join(' · ')\n return (\n <Row gap=\"3\" align=\"start\" wrap>\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <Heading level={2} size=\"h2\" className=\"truncate\" title={record.title ?? record.filename}>\n {record.title ?? record.filename}\n </Heading>\n {record.title ? (\n <Text size=\"sm\" tone=\"muted\" className=\"truncate\">\n {record.filename}\n </Text>\n ) : null}\n <Text size=\"xs\" tone=\"subtle\">\n {info}\n </Text>\n </Stack>\n <Row gap=\"2\">\n <AssetReferencesButton projectId={projectId} record={record} />\n <AssetDeleteButton projectId={projectId} record={record} />\n </Row>\n </Row>\n )\n}\n\n/** \"Where is this asset used?\" (EC-172): the shared references panel, EC-155-backed. */\nfunction AssetReferencesButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setOpen(true)}\n data-testid=\"asset-detail-references\"\n >\n <Link2 /> {translate('assets.references.buttonLabel')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>\n {translate('assets.references.title', { name: record.title ?? record.filename })}\n </DialogTitle>\n <DialogDescription>{translate('assets.references.description')}</DialogDescription>\n </DialogHeader>\n {open ? (\n <ReferencesPanel projectId={projectId} target={{ type: 'asset', id: record.id }} />\n ) : null}\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/**\n * Reference-respecting delete (EC-151, EC-202): enumerates the exact inbound\n * references — index-accurate, with deep links to the field/node — before\n * removing. Deleting a referenced asset is weak-by-default (allowed with an\n * explicit \"delete anyway\"); every reference dangles into a visible validation\n * issue rather than silently breaking. On success, returns to the list.\n */\nfunction AssetDeleteButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const source = useCollectionView()\n const store = useAssets()\n const navigate = useNavigate()\n const [open, setOpen] = useState(false)\n const [usages, setUsages] = useState<ReferenceEntryView[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = async () => {\n setOpen(true)\n setError(null)\n setUsages(null)\n try {\n setUsages(await loadInboundAssetReferenceViews(projectId, record.id))\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n }\n }\n\n const confirm = async () => {\n setBusy(true)\n setError(null)\n // The dialog has already enumerated usages (or shown there are none) and the\n // user is confirming either way — confirm unconditionally, skipping the\n // redundant inbound re-check (EC-301 perf). Weak-by-default: a still-\n // referenced asset deletes anyway, dangling into a visible issue.\n const result = await store.remove(record.id, { confirmInUse: true })\n setBusy(false)\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n setOpen(false)\n if (source.listPath !== undefined) {\n void navigate({ to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } })\n }\n } else if (result.outcome === 'blocked') {\n setError(\n translate(\n result.usageCount === 1 ? 'assets.delete.raceError.one' : 'assets.delete.raceError.other',\n { count: result.usageCount },\n ),\n )\n } else {\n setError(result.message)\n }\n }\n\n const impact = usages ? buildAssetReferenceImpact(usages) : null\n const usageCount = impact?.broken.length ?? 0\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void openDialog()}\n data-testid=\"asset-detail-delete\"\n >\n <Trash2 /> {translate('common.delete')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-h-[80vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{translate('assets.delete.title', { filename: record.filename })}</DialogTitle>\n <DialogDescription>{translate('assets.delete.description')}</DialogDescription>\n </DialogHeader>\n {usages === null && error === null ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.checking')}\n </Text>\n ) : usageCount > 0 ? (\n <Stack gap=\"2\" data-testid=\"asset-usage-warning\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate(usageCount === 1 ? 'assets.delete.inUse.one' : 'assets.delete.inUse.other', {\n count: usageCount,\n })}\n </Text>\n </Row>\n <ReferenceImpactRows\n projectId={projectId}\n broken={impact!.broken}\n onNavigate={() => setOpen(false)}\n />\n <Text size=\"xs\" tone=\"muted\">\n {translate('assets.delete.brokenWarning')}\n </Text>\n </Stack>\n ) : (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.noReferences')}\n </Text>\n )}\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-delete-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n {translate('common.cancel')}\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n onClick={() => void confirm()}\n disabled={busy || usages === null}\n data-testid=\"asset-delete-confirm\"\n >\n {usageCount > 0 ? translate('assets.delete.confirmAnyway') : translate('common.delete')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n"]}
1
+ {"version":3,"file":"asset-detail.js","sourceRoot":"","sources":["../../../src/components/assets/asset-detail.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEzC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAGtE,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,yBAAyB,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAA;AAEhG,OAAO,EACL,MAAM,EACN,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,KAAK,EACL,OAAO,EACP,KAAK,EACL,SAAS,EACT,WAAW,EACX,GAAG,EACH,MAAM,EACN,KAAK,EACL,IAAI,GACL,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAC9E,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnF,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC3F,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAElD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,SAAS,EAAE,OAAO,EAA0C;IACxF,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IACtC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAA;IAEhC,MAAM,eAAe,GAAG,cAAc,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC,CAAA;IAC7E,gBAAgB,CACd,OAAO,CAAC,GAAG,EAAE;QACX,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAO,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAA;QACtE,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAA;QAC1D,OAAO;YACL,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE;YACpG,EAAE,KAAK,EAAE,KAAK,EAAE;SACjB,CAAA;IACH,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CACnE,CAAA;IAED,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,CACL,KAAC,KAAK,IAAC,SAAS,EAAC,+BAA+B,EAAC,KAAK,EAAC,QAAQ,EAAC,OAAO,EAAC,QAAQ,YAC9E,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,gBAAgB,CAAC,GACvB,GACD,CACT,CAAA;QACH,CAAC;QACD,OAAO,CACL,cAAK,SAAS,EAAC,oCAAoC,YACjD,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,aAAa,aACnD,KAAC,OAAO,IAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAC,IAAI,YACzB,SAAS,CAAC,0BAA0B,CAAC,GAC9B,EACT,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,CAC/B,KAAC,IAAI,IACH,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAC9C,SAAS,EAAC,sCAAsC,YAE/C,SAAS,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,GAC1D,CACR,CAAC,CAAC,CAAC,IAAI,IACF,GACJ,CACP,CAAA;IACH,CAAC;IAED,4EAA4E;IAC5E,wEAAwE;IACxE,+DAA+D;IAC/D,OAAO,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,GAAI,CAAA;AAC7F,CAAC;AAED,SAAS,eAAe,CAAC,EACvB,SAAS,EACT,MAAM,EACN,UAAU,GAKX;IACC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAE7E,+EAA+E;IAC/E,2EAA2E;IAC3E,8EAA8E;IAC9E,gCAAgC;IAChC,mFAAmF;IACnF,4EAA4E;IAC5E,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAA0B,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACtF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAyB,MAAM,CAAC,UAAU,CAAC,CAAA;IAC7E,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAC/D,sFAAsF;IACtF,kEAAkE;IAClE,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAE/C,SAAS,UAAU,CAAC,KAAkB;QACpC,mFAAmF;QACnF,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7B,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;QAC1B,QAAQ,CAAC,KAAK,CAAC,CAAA;QACf,WAAW,CAAC,IAAI,CAAC,CAAA;IACnB,CAAC;IAED,SAAS,QAAQ,CAAC,IAAY,EAAE,KAAc;QAC5C,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IAC3C,CAAC;IAED,KAAK,UAAU,IAAI;QACjB,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,YAAY,CAAC,IAAI,CAAC,CAAA;QAClB,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC/E,SAAS,CAAC,KAAK,CAAC,CAAA;QAChB,IAAI,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;;YACzB,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,CAAC;IAED,SAAS,WAAW,CAAC,KAAwB;QAC3C,QAAQ,CAAC,KAAK,IAAI,SAAS,CAAC,CAAA;QAC5B,KAAK,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAA;IAC7D,CAAC;IAED,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,oCAAoC,aAC3D,KAAC,iBAAiB,IAChB,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EACL,KAAC,iBAAiB,IAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,KAAK,EACf,YAAY,EAAE,SAAS,CAAC,4BAA4B,CAAC,EACrD,UAAU,EAAE,UAAU,GACtB,GAEJ,EAEF,MAAC,KAAK,eACJ,KAAC,WAAW,cACV,MAAC,GAAG,IAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,QAAQ,aACpC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,EAAC,IAAI,EAAC,OAAO,YAC3C,SAAS,CAAC,sBAAsB,CAAC,GAC7B,EACP,KAAC,MAAM,KAAG,EACT,KAAK,CAAC,CAAC,CAAC,CACP,MAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,aAChE,KAAC,SAAS,KAAG,OAAE,SAAS,CAAC,yBAAyB,CAAC,IAC5C,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,GACM,EACd,KAAC,SAAS,cACR,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACZ,KAAC,gBAAgB,IAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,GAAI,EAC7F,QAAQ,CAAC,CAAC,CAAC,CACV,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,aAAa,iBAAa,4BAA4B,YAC7E,SAAS,CAAC,8BAA8B,CAAC,GACrC,CACR,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,SAAS,CAAC,0BAA0B,CAAC,GACjC,EACP,KAAC,iBAAiB,IAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAI,IAC7C,GACE,IACN,EAER,MAAC,KAAK,mBAAa,mBAAmB,aACpC,KAAC,WAAW,cACV,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,EAAC,IAAI,EAAC,OAAO,YAC3C,SAAS,CAAC,+BAA+B,CAAC,GACtC,GACK,EACd,KAAC,SAAS,cACR,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACX,UAAU,CAAC,GAAG,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC,CAC/B,KAAC,KAAK,IAEJ,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EACpB,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,SAAS,YAEtD,KAAC,YAAY,IACX,EAAE,EAAE,gBAAgB,CAAC,CAAC,IAAI,EAAE,EAC5B,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EACnB,QAAQ,EAAE,KAAK,EACf,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GACpC,IAVG,CAAC,CAAC,IAAI,CAWL,CACT,CAAC,EACD,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACzB,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,SAAS,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,GACrE,CACR,CAAC,CAAC,CAAC,IAAI,EACP,SAAS,CAAC,CAAC,CAAC,CACX,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,kBAAkB,iBAAa,yBAAyB,YAC/E,SAAS,GACL,CACR,CAAC,CAAC,CAAC,IAAI,EACP,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACvB,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,MAAM,KAAG,EACV,KAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAC1B,QAAQ,EAAE,CAAC,KAAK,IAAI,MAAM,iBACd,mBAAmB,YAE9B,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,GACxD,IACL,CACP,CAAC,CAAC,CAAC,IAAI,IACF,GACE,IACN,IACF,CACT,CAAA;AACH,CAAC;AAED,uFAAuF;AACvF,SAAS,iBAAiB,CAAC,EACzB,SAAS,EACT,MAAM,EACN,OAAO,GAKR;IACC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IACtF,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACjG,OAAO,CACL,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,OAAO,EAAC,IAAI,mBAC7B,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,gBAAgB,aACvC,KAAC,OAAO,IAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,YACrF,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,GACxB,EACT,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CACd,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,UAAU,YAC9C,MAAM,CAAC,QAAQ,GACX,CACR,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,IAAI,GACA,IACD,EACR,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,mBACd,OAAO,EACR,KAAC,qBAAqB,IAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAI,EAC/D,KAAC,iBAAiB,IAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAI,IACvD,IACF,CACP,CAAA;AACH,CAAC;AAED,wFAAwF;AACxF,SAAS,qBAAqB,CAAC,EAAE,SAAS,EAAE,MAAM,EAA8C;IAC9F,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,OAAO,CACL,8BACE,MAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,iBAChB,yBAAyB,aAErC,KAAC,KAAK,KAAG,OAAE,SAAS,CAAC,+BAA+B,CAAC,IAC9C,EACT,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,YACvC,MAAC,aAAa,IAAC,SAAS,EAAC,UAAU,aACjC,MAAC,YAAY,eACX,KAAC,WAAW,cACT,SAAS,CAAC,yBAAyB,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC,GACpE,EACd,KAAC,iBAAiB,cAAE,SAAS,CAAC,+BAA+B,CAAC,GAAqB,IACtE,EACd,IAAI,CAAC,CAAC,CAAC,CACN,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,GAAI,CACpF,CAAC,CAAC,CAAC,IAAI,IACM,GACT,IACR,CACJ,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,EAAE,SAAS,EAAE,MAAM,EAA8C;IAC1F,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;IACzB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAA8B,IAAI,CAAC,CAAA;IACvE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEvC,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;QAC5B,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,IAAI,CAAC;YACH,SAAS,CAAC,MAAM,8BAA8B,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;QACvE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAClE,CAAC;IACH,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,OAAO,CAAC,IAAI,CAAC,CAAA;QACb,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,6EAA6E;QAC7E,wEAAwE;QACxE,sEAAsE;QACtE,kEAAkE;QAClE,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAA;QACpE,OAAO,CAAC,KAAK,CAAC,CAAA;QACd,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;YACnE,OAAO,CAAC,KAAK,CAAC,CAAA;YACd,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAClC,KAAK,QAAQ,CAAC,EAAE,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;YAC7F,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACxC,QAAQ,CACN,SAAS,CACP,MAAM,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,+BAA+B,EACzF,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,CAC7B,CACF,CAAA;QACH,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAChE,MAAM,UAAU,GAAG,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAA;IAC7C,OAAO,CACL,8BACE,MAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,UAAU,EAAE,iBACpB,qBAAqB,aAEjC,KAAC,MAAM,KAAG,OAAE,SAAS,CAAC,eAAe,CAAC,IAC/B,EACT,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,YACvC,MAAC,aAAa,IAAC,SAAS,EAAC,uCAAuC,aAC9D,MAAC,YAAY,eACX,KAAC,WAAW,cAAE,SAAS,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAe,EAC5F,KAAC,iBAAiB,cAAE,SAAS,CAAC,2BAA2B,CAAC,GAAqB,IAClE,EACd,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CACnC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,wBAAwB,CAAC,GAC/B,CACR,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CACnB,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,iBAAa,qBAAqB,aAC9C,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,aAAa,IAAC,SAAS,EAAC,qBAAqB,GAAG,EACjD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,YAC9B,SAAS,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,2BAA2B,EAAE;gDACrF,KAAK,EAAE,UAAU;6CAClB,CAAC,GACG,IACH,EACN,KAAC,mBAAmB,IAClB,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAO,CAAC,MAAM,EACtB,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAChC,EACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,6BAA6B,CAAC,GACpC,IACD,CACT,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,4BAA4B,CAAC,GACnC,CACR,EACA,KAAK,CAAC,CAAC,CAAC,CACP,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,kBAAkB,iBAAa,oBAAoB,YAC1E,KAAK,GACD,CACR,CAAC,CAAC,CAAC,IAAI,EACR,MAAC,YAAY,eACX,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,IAAI,YAC5E,SAAS,CAAC,eAAe,CAAC,GACpB,EACT,KAAC,MAAM,IACL,OAAO,EAAC,aAAa,EACrB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,OAAO,EAAE,EAC7B,QAAQ,EAAE,IAAI,IAAI,MAAM,KAAK,IAAI,iBACrB,sBAAsB,YAEjC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,GAChF,IACI,IACD,GACT,IACR,CACJ,CAAA;AACH,CAAC","sourcesContent":["import { useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { AlertTriangle, Link2, RotateCcw, Trash2 } from 'lucide-react'\nimport type { CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Link, STUDIO_PROJECT_ROUTE, useNavigate } from '@elytracms/studio/router'\nimport { localizedLabel, translate } from '../../lib/i18n'\nimport { buildAssetReferenceImpact, loadInboundAssetReferenceViews } from '../../lib/references'\nimport type { ReferenceEntryView } from '../../lib/references'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Heading,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { FieldControl } from '../cms/field-control'\nimport { fieldLabel } from '../cms/cms-labels'\nimport { ReferenceImpactRows } from '../cms/reference-confirm'\nimport { ReferencesPanel } from '../references/references-panel'\nimport { useSetBreadcrumb } from '../breadcrumb-context'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { FocalCropPreviews, FocalPointPicker, useAssetRecord } from './asset-thumb'\nimport type { FocalPoint } from './focal-point'\nimport { assetMetaFieldsOf, assetMetaSeed, formatBytes, pruneMeta } from './asset-metadata'\nimport { useAssets } from './assets-store'\nimport { ReplaceFileButton } from './replace-file'\n\n/**\n * The assets rail's DETAIL surface (EC-319). Pragmatic call: assets don't fit\n * the portable `PortableDetailEditor` shell cleanly — there's no dirty/Save/\n * Publish document model here (an asset record isn't a `cmsDocument`), and the\n * focal-point picker is a bespoke drag interaction over the image, not a\n * scalar field value a generic form row can host without contortions. So this\n * is a bespoke surface (mirrors the CMS document editor's shape: a header +\n * panels), reached via the SAME rail-owned `detailPath`/`listPath` seam every\n * other rail uses (AD-19) — only the body differs.\n *\n * Unifies what were two divergent asset-edit UIs before this task: the plain\n * alt/title dialog on the old asset library card, and the richer schema-driven\n * + focal-point \"image options\" dialog opened from an `asset` field elsewhere\n * (`field-control.tsx`). This page reuses the LATTER (richer) content, so\n * editing an asset here is never a downgrade from editing it via a field.\n *\n * Deletion keeps the EC-202 reference-index safe-delete path (`assets-store.tsx`\n * → `deleteAsset`, keyed on `{ type: 'asset' }`). Since EC-433 the universal\n * table's row/bulk delete runs the SAME asset-shaped reference check (the\n * source's `referenceTarget`) and the same confirmed store delete — the two\n * surfaces share one delete semantics, not two divergent ones.\n */\nexport function AssetDetail({ projectId, assetId }: { projectId: string; assetId: string }) {\n const source = useCollectionView()\n const store = useAssets()\n const record = useAssetRecord(assetId)\n const collection = source.schema\n\n const collectionLabel = localizedLabel(collection.form?.label, collection.id)\n useSetBreadcrumb(\n useMemo(() => {\n if (source.listPath === undefined) return [{ label: collectionLabel }]\n const title = record?.title ?? record?.filename ?? assetId\n return [\n { label: collectionLabel, to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } },\n { label: title },\n ]\n }, [collectionLabel, source.listPath, record, projectId, assetId]),\n )\n\n if (record === undefined) {\n if (store.status === 'loading') {\n return (\n <Stack className=\"h-full place-items-center p-8\" align=\"center\" justify=\"center\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.loading')}\n </Text>\n </Stack>\n )\n }\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"text-center\">\n <Heading level={2} size=\"h2\">\n {translate('cms.editor.notFoundTitle')}\n </Heading>\n {source.listPath !== undefined ? (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: source.listPath }}\n className=\"text-sm text-primary hover:underline\"\n >\n {translate('cms.editor.backToList', { label: collectionLabel })}\n </Link>\n ) : null}\n </Stack>\n </div>\n )\n }\n\n // Remount per asset (the surface's own key, set by the caller) resets every\n // local buffer below cleanly when navigating from one asset's detail to\n // another — no stale-state carryover to guard against by hand.\n return <AssetDetailBody projectId={projectId} record={record} collection={source.schema} />\n}\n\nfunction AssetDetailBody({\n projectId,\n record,\n collection,\n}: {\n projectId: string\n record: AssetRecord\n collection: CollectionDef\n}) {\n const store = useAssets()\n const metaFields = useMemo(() => assetMetaFieldsOf(collection), [collection])\n\n // Metadata buffers locally and commits explicitly (Save) — an asset record has\n // no dirty/Save document model of its own to piggyback on. The focal point\n // commits immediately (it drives a live visual), matching the field's options\n // dialog (`field-control.tsx`).\n // EC-587: the bag the editor starts from has a legacy `alt` column lifted in, so a\n // not-yet-migrated asset shows its alt and the save moves it to `meta.alt`.\n const [meta, setMeta] = useState<Record<string, unknown>>(() => assetMetaSeed(record))\n const [focal, setFocal] = useState<FocalPoint | undefined>(record.focalPoint)\n const [dirty, setDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<string | null>(null)\n // EC-568: after a file replace the focal point is KEPT (the ticket's recommendation),\n // but it was chosen on the old pixels — say so beside the picker.\n const [replaced, setReplaced] = useState(false)\n\n function onReplaced(asset: AssetRecord) {\n // The new file may have brought metadata: start the buffer from the stored record.\n setMeta(assetMetaSeed(asset))\n setFocal(asset.focalPoint)\n setDirty(false)\n setReplaced(true)\n }\n\n function setField(name: string, value: unknown) {\n setDirty(true)\n setMeta((m) => ({ ...m, [name]: value }))\n }\n\n async function save() {\n setSaving(true)\n setSaveError(null)\n const result = await store.updateMetadata(record.id, { meta: pruneMeta(meta) })\n setSaving(false)\n if (result.ok) setDirty(false)\n else setSaveError(result.message)\n }\n\n function commitFocal(point: FocalPoint | null) {\n setFocal(point ?? undefined)\n void store.updateMetadata(record.id, { focalPoint: point })\n }\n\n return (\n <Stack gap=\"4\" className=\"mx-auto max-w-3xl p-4 pb-10 sm:p-6\">\n <AssetDetailHeader\n projectId={projectId}\n record={record}\n replace={\n <ReplaceFileButton\n record={record}\n disabled={dirty}\n disabledHint={translate('cms.asset.replaceSaveFirst')}\n onReplaced={onReplaced}\n />\n }\n />\n\n <Panel>\n <PanelHeader>\n <Row align=\"center\" className=\"w-full\">\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('cms.asset.focalPoint')}\n </Text>\n <Spacer />\n {focal ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => commitFocal(null)}>\n <RotateCcw /> {translate('cms.asset.resetToCentre')}\n </Button>\n ) : null}\n </Row>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"2\">\n <FocalPointPicker record={record} point={focal} onChange={commitFocal} onPreview={setFocal} />\n {replaced ? (\n <Text size=\"xs\" className=\"font-medium\" data-testid=\"asset-replaced-check-focal\">\n {translate('cms.asset.replacedCheckFocal')}\n </Text>\n ) : null}\n <Text size=\"xs\" tone=\"subtle\">\n {translate('cms.asset.focalPointHint')}\n </Text>\n <FocalCropPreviews record={record} point={focal} />\n </Stack>\n </PanelBody>\n </Panel>\n\n <Panel data-testid=\"asset-detail-meta\">\n <PanelHeader>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('assets.detail.metadataHeading')}\n </Text>\n </PanelHeader>\n <PanelBody>\n <Stack gap=\"3\">\n {metaFields.map((f: FieldDef) => (\n <Field\n key={f.name}\n label={fieldLabel(f)}\n hint={localizedLabel(f.form?.description) || undefined}\n >\n <FieldControl\n id={`asset-detail-${f.name}`}\n field={f}\n value={meta[f.name]}\n readOnly={false}\n onChange={(v) => setField(f.name, v)}\n />\n </Field>\n ))}\n {metaFields.length === 0 ? (\n <Text size=\"sm\" tone=\"subtle\">\n {translate('cms.asset.noMetaFields', { code1: 'alt', code2: 'copyright' })}\n </Text>\n ) : null}\n {saveError ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-detail-save-error\">\n {saveError}\n </Text>\n ) : null}\n {metaFields.length > 0 ? (\n <Row gap=\"2\">\n <Spacer />\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={!dirty || saving}\n data-testid=\"asset-detail-save\"\n >\n {saving ? translate('common.saving') : translate('common.save')}\n </Button>\n </Row>\n ) : null}\n </Stack>\n </PanelBody>\n </Panel>\n </Stack>\n )\n}\n\n/** Filename/title heading + system info (content type, dimensions, size) + actions. */\nfunction AssetDetailHeader({\n projectId,\n record,\n replace,\n}: {\n projectId: string\n record: AssetRecord\n replace?: ReactNode\n}) {\n const dims = record.width && record.height ? `${record.width}×${record.height}` : null\n const info = [record.contentType, dims, formatBytes(record.byteSize)].filter(Boolean).join(' · ')\n return (\n <Row gap=\"3\" align=\"start\" wrap>\n <Stack gap=\"1\" className=\"min-w-0 flex-1\">\n <Heading level={2} size=\"h2\" className=\"truncate\" title={record.title ?? record.filename}>\n {record.title ?? record.filename}\n </Heading>\n {record.title ? (\n <Text size=\"sm\" tone=\"muted\" className=\"truncate\">\n {record.filename}\n </Text>\n ) : null}\n <Text size=\"xs\" tone=\"subtle\">\n {info}\n </Text>\n </Stack>\n <Row gap=\"2\" wrap>\n {replace}\n <AssetReferencesButton projectId={projectId} record={record} />\n <AssetDeleteButton projectId={projectId} record={record} />\n </Row>\n </Row>\n )\n}\n\n/** \"Where is this asset used?\" (EC-172): the shared references panel, EC-155-backed. */\nfunction AssetReferencesButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setOpen(true)}\n data-testid=\"asset-detail-references\"\n >\n <Link2 /> {translate('assets.references.buttonLabel')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>\n {translate('assets.references.title', { name: record.title ?? record.filename })}\n </DialogTitle>\n <DialogDescription>{translate('assets.references.description')}</DialogDescription>\n </DialogHeader>\n {open ? (\n <ReferencesPanel projectId={projectId} target={{ type: 'asset', id: record.id }} />\n ) : null}\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/**\n * Reference-respecting delete (EC-151, EC-202): enumerates the exact inbound\n * references — index-accurate, with deep links to the field/node — before\n * removing. Deleting a referenced asset is weak-by-default (allowed with an\n * explicit \"delete anyway\"); every reference dangles into a visible validation\n * issue rather than silently breaking. On success, returns to the list.\n */\nfunction AssetDeleteButton({ projectId, record }: { projectId: string; record: AssetRecord }) {\n const source = useCollectionView()\n const store = useAssets()\n const navigate = useNavigate()\n const [open, setOpen] = useState(false)\n const [usages, setUsages] = useState<ReferenceEntryView[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = async () => {\n setOpen(true)\n setError(null)\n setUsages(null)\n try {\n setUsages(await loadInboundAssetReferenceViews(projectId, record.id))\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n }\n }\n\n const confirm = async () => {\n setBusy(true)\n setError(null)\n // The dialog has already enumerated usages (or shown there are none) and the\n // user is confirming either way — confirm unconditionally, skipping the\n // redundant inbound re-check (EC-301 perf). Weak-by-default: a still-\n // referenced asset deletes anyway, dangling into a visible issue.\n const result = await store.remove(record.id, { confirmInUse: true })\n setBusy(false)\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n setOpen(false)\n if (source.listPath !== undefined) {\n void navigate({ to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: source.listPath } })\n }\n } else if (result.outcome === 'blocked') {\n setError(\n translate(\n result.usageCount === 1 ? 'assets.delete.raceError.one' : 'assets.delete.raceError.other',\n { count: result.usageCount },\n ),\n )\n } else {\n setError(result.message)\n }\n }\n\n const impact = usages ? buildAssetReferenceImpact(usages) : null\n const usageCount = impact?.broken.length ?? 0\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void openDialog()}\n data-testid=\"asset-detail-delete\"\n >\n <Trash2 /> {translate('common.delete')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-h-[80vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{translate('assets.delete.title', { filename: record.filename })}</DialogTitle>\n <DialogDescription>{translate('assets.delete.description')}</DialogDescription>\n </DialogHeader>\n {usages === null && error === null ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.checking')}\n </Text>\n ) : usageCount > 0 ? (\n <Stack gap=\"2\" data-testid=\"asset-usage-warning\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate(usageCount === 1 ? 'assets.delete.inUse.one' : 'assets.delete.inUse.other', {\n count: usageCount,\n })}\n </Text>\n </Row>\n <ReferenceImpactRows\n projectId={projectId}\n broken={impact!.broken}\n onNavigate={() => setOpen(false)}\n />\n <Text size=\"xs\" tone=\"muted\">\n {translate('assets.delete.brokenWarning')}\n </Text>\n </Stack>\n ) : (\n <Text size=\"sm\" tone=\"muted\">\n {translate('assets.delete.noReferences')}\n </Text>\n )}\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-delete-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n {translate('common.cancel')}\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n onClick={() => void confirm()}\n disabled={busy || usages === null}\n data-testid=\"asset-delete-confirm\"\n >\n {usageCount > 0 ? translate('assets.delete.confirmAnyway') : translate('common.delete')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n"]}
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo } from 'react';
3
3
  import { Image as ImageIcon } from 'lucide-react';
4
4
  import { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router';
5
- import { applyViewState } from '@elytracms/studio/lib/cms-views';
6
- import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
5
+ import { applyViewState } from '../../lib/cms-views/index.js';
6
+ import { localizedLabel, translate } from '../../lib/i18n/index.js';
7
7
  import { Code, Grid, Panel, Row, Separator, Spacer, Stack, Text } from '@elytracms/studio/ui';
8
8
  import { FilterControl } from '../cms/filter-builder.js';
9
9
  import { ViewTabs } from '../cms/view-bar.js';
@@ -1 +1 @@
1
- {"version":3,"file":"asset-grid.js","sourceRoot":"","sources":["../../../src/components/assets/asset-grid.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react'\nimport { useMemo } from 'react'\nimport { Image as ImageIcon } from 'lucide-react'\nimport type { CmsDocument } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router'\nimport { applyViewState } from '@elytracms/studio/lib/cms-views'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { Code, Grid, Panel, Row, Separator, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { FilterControl } from '../cms/filter-builder'\nimport { ViewTabs } from '../cms/view-bar'\nimport { useSetBreadcrumb } from '../breadcrumb-context'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { AssetThumb } from './asset-thumb'\nimport { formatBytes } from './asset-metadata'\nimport { useAssets } from './assets-store'\n\n/**\n * The assets rail's GRID surface (EC-319) — the thumbnail-first counterpart to\n * `LoadedCollectionTable`, over the SAME portable `CollectionViewSource` (the\n * same search/filter state, `applyViewState`, and count — genuine parity with\n * list mode, not a separate ad-hoc query). A media library reads naturally as\n * thumbnails; the universal TABLE (list mode) is still one toggle away for\n * columns/sort.\n */\nexport function AssetGridView({\n projectId,\n toolbarExtra,\n}: {\n projectId: string\n toolbarExtra?: ReactNode\n}) {\n const source = useCollectionView()\n const collection = source.schema\n const collectionId = source.collectionId\n useSetBreadcrumb([{ label: localizedLabel(collection.form?.label, collectionId) }])\n\n const viewState = source.viewState()\n const docs = source.documents()\n const { docs: visibleDocs } = useMemo(\n () => applyViewState(collection, docs, source.localeConfig, source.activeLocale, viewState),\n [collection, docs, source.localeConfig, source.activeLocale, viewState],\n )\n\n // Resolved once per render (O(n), not per-card `.find()`) — the card stays a\n // pure presentational component (record passed in, no store lookup of its own).\n const store = useAssets()\n const recordsById = useMemo(\n () => new Map(store.assets.map((record) => [record.id, record])),\n [store.assets],\n )\n\n const pluralLabel = localizedLabel(collection.form?.label, `${collectionId}s`)\n const singularLabel = localizedLabel(collection.form?.labelSingular, collectionId)\n const countLabel =\n visibleDocs.length === docs.length\n ? translate('cms.list.count', {\n count: docs.length,\n noun: docs.length === 1 ? singularLabel : pluralLabel,\n })\n : translate('cms.list.countFiltered', {\n visible: visibleDocs.length,\n total: docs.length,\n noun: pluralLabel,\n })\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 border-b border-border px-4 py-3 sm:px-6\">\n <Row gap=\"2\" wrap>\n <ViewTabs />\n <FilterControl collection={collection} />\n <Separator orientation=\"vertical\" className=\"h-5 self-center\" />\n <Text\n as=\"span\"\n size=\"xs\"\n tone=\"muted\"\n className=\"whitespace-nowrap tabular-nums\"\n data-testid=\"asset-grid-count\"\n >\n {countLabel}\n </Text>\n <Spacer />\n {toolbarExtra}\n </Row>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto p-4 sm:p-6\">\n {docs.length === 0 ? (\n <div className=\"grid h-full place-items-center p-8\">\n <Text tone=\"muted\">{translate('cms.list.emptyNone', { noun: pluralLabel.toLowerCase() })}</Text>\n </div>\n ) : visibleDocs.length === 0 ? (\n <div className=\"grid h-full place-items-center p-8\">\n <Text tone=\"muted\" data-testid=\"empty-filtered\">\n {translate('cms.list.emptyFiltered')}\n </Text>\n </div>\n ) : (\n <Grid minItemWidth=\"220px\" gap=\"3\" data-testid=\"asset-grid\">\n {visibleDocs.map((doc) => (\n <AssetGridCard\n key={doc.id}\n doc={doc}\n record={recordsById.get(doc.id)}\n projectId={projectId}\n />\n ))}\n </Grid>\n )}\n </div>\n </div>\n )\n}\n\n/**\n * One thumbnail card: image preview (focal-point aware, EC-248) + filename/title\n * + key record metadata (dimensions, content type, size). Links to the asset's\n * detail surface (`asset-detail.tsx`) — the same place list mode's row/\"…\" menu\n * opens — so edit/delete/references live in exactly one surface, not duplicated\n * per view mode. Pure presentational: the record is resolved once by the parent\n * grid, not looked up per card.\n */\nexport function AssetGridCard({\n doc,\n record,\n projectId,\n}: {\n doc: CmsDocument\n record: AssetRecord | undefined\n projectId: string\n}) {\n const source = useCollectionView()\n const title = record?.title ?? record?.filename ?? doc.id\n const dims = record?.width && record?.height ? `${record.width}×${record.height}` : null\n const kind = record?.contentType?.split('/')[1]?.toUpperCase()\n const splat = source.detailPath?.(doc.id)\n\n const card = (\n <Panel className=\"overflow-hidden\" data-testid=\"asset-grid-card\">\n <AssetThumb record={record} alt={title} className=\"aspect-video w-full rounded-none border-0\" />\n <Stack gap=\"1\" className=\"p-3\">\n <Text size=\"sm\" weight=\"medium\" className=\"truncate\" title={record?.filename ?? title}>\n {title}\n </Text>\n {record === undefined ? (\n <Row gap=\"1\" align=\"center\">\n <ImageIcon className=\"size-3.5 text-muted-foreground\" />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('assets.loading')}\n </Text>\n </Row>\n ) : (\n <Row gap=\"1\" wrap>\n {kind ? <Code tone=\"muted\">{kind}</Code> : null}\n {dims ? <Code tone=\"muted\">{dims}</Code> : null}\n <Code tone=\"muted\">{formatBytes(record.byteSize)}</Code>\n </Row>\n )}\n </Stack>\n </Panel>\n )\n\n if (splat === undefined) return card\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: splat }}\n className=\"block rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40\"\n >\n {card}\n </Link>\n )\n}\n"]}
1
+ {"version":3,"file":"asset-grid.js","sourceRoot":"","sources":["../../../src/components/assets/asset-grid.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react'\nimport { useMemo } from 'react'\nimport { Image as ImageIcon } from 'lucide-react'\nimport type { CmsDocument } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router'\nimport { applyViewState } from '../../lib/cms-views'\nimport { localizedLabel, translate } from '../../lib/i18n'\nimport { Code, Grid, Panel, Row, Separator, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { FilterControl } from '../cms/filter-builder'\nimport { ViewTabs } from '../cms/view-bar'\nimport { useSetBreadcrumb } from '../breadcrumb-context'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { AssetThumb } from './asset-thumb'\nimport { formatBytes } from './asset-metadata'\nimport { useAssets } from './assets-store'\n\n/**\n * The assets rail's GRID surface (EC-319) — the thumbnail-first counterpart to\n * `LoadedCollectionTable`, over the SAME portable `CollectionViewSource` (the\n * same search/filter state, `applyViewState`, and count — genuine parity with\n * list mode, not a separate ad-hoc query). A media library reads naturally as\n * thumbnails; the universal TABLE (list mode) is still one toggle away for\n * columns/sort.\n */\nexport function AssetGridView({\n projectId,\n toolbarExtra,\n}: {\n projectId: string\n toolbarExtra?: ReactNode\n}) {\n const source = useCollectionView()\n const collection = source.schema\n const collectionId = source.collectionId\n useSetBreadcrumb([{ label: localizedLabel(collection.form?.label, collectionId) }])\n\n const viewState = source.viewState()\n const docs = source.documents()\n const { docs: visibleDocs } = useMemo(\n () => applyViewState(collection, docs, source.localeConfig, source.activeLocale, viewState),\n [collection, docs, source.localeConfig, source.activeLocale, viewState],\n )\n\n // Resolved once per render (O(n), not per-card `.find()`) — the card stays a\n // pure presentational component (record passed in, no store lookup of its own).\n const store = useAssets()\n const recordsById = useMemo(\n () => new Map(store.assets.map((record) => [record.id, record])),\n [store.assets],\n )\n\n const pluralLabel = localizedLabel(collection.form?.label, `${collectionId}s`)\n const singularLabel = localizedLabel(collection.form?.labelSingular, collectionId)\n const countLabel =\n visibleDocs.length === docs.length\n ? translate('cms.list.count', {\n count: docs.length,\n noun: docs.length === 1 ? singularLabel : pluralLabel,\n })\n : translate('cms.list.countFiltered', {\n visible: visibleDocs.length,\n total: docs.length,\n noun: pluralLabel,\n })\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 border-b border-border px-4 py-3 sm:px-6\">\n <Row gap=\"2\" wrap>\n <ViewTabs />\n <FilterControl collection={collection} />\n <Separator orientation=\"vertical\" className=\"h-5 self-center\" />\n <Text\n as=\"span\"\n size=\"xs\"\n tone=\"muted\"\n className=\"whitespace-nowrap tabular-nums\"\n data-testid=\"asset-grid-count\"\n >\n {countLabel}\n </Text>\n <Spacer />\n {toolbarExtra}\n </Row>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto p-4 sm:p-6\">\n {docs.length === 0 ? (\n <div className=\"grid h-full place-items-center p-8\">\n <Text tone=\"muted\">{translate('cms.list.emptyNone', { noun: pluralLabel.toLowerCase() })}</Text>\n </div>\n ) : visibleDocs.length === 0 ? (\n <div className=\"grid h-full place-items-center p-8\">\n <Text tone=\"muted\" data-testid=\"empty-filtered\">\n {translate('cms.list.emptyFiltered')}\n </Text>\n </div>\n ) : (\n <Grid minItemWidth=\"220px\" gap=\"3\" data-testid=\"asset-grid\">\n {visibleDocs.map((doc) => (\n <AssetGridCard\n key={doc.id}\n doc={doc}\n record={recordsById.get(doc.id)}\n projectId={projectId}\n />\n ))}\n </Grid>\n )}\n </div>\n </div>\n )\n}\n\n/**\n * One thumbnail card: image preview (focal-point aware, EC-248) + filename/title\n * + key record metadata (dimensions, content type, size). Links to the asset's\n * detail surface (`asset-detail.tsx`) — the same place list mode's row/\"…\" menu\n * opens — so edit/delete/references live in exactly one surface, not duplicated\n * per view mode. Pure presentational: the record is resolved once by the parent\n * grid, not looked up per card.\n */\nexport function AssetGridCard({\n doc,\n record,\n projectId,\n}: {\n doc: CmsDocument\n record: AssetRecord | undefined\n projectId: string\n}) {\n const source = useCollectionView()\n const title = record?.title ?? record?.filename ?? doc.id\n const dims = record?.width && record?.height ? `${record.width}×${record.height}` : null\n const kind = record?.contentType?.split('/')[1]?.toUpperCase()\n const splat = source.detailPath?.(doc.id)\n\n const card = (\n <Panel className=\"overflow-hidden\" data-testid=\"asset-grid-card\">\n <AssetThumb record={record} alt={title} className=\"aspect-video w-full rounded-none border-0\" />\n <Stack gap=\"1\" className=\"p-3\">\n <Text size=\"sm\" weight=\"medium\" className=\"truncate\" title={record?.filename ?? title}>\n {title}\n </Text>\n {record === undefined ? (\n <Row gap=\"1\" align=\"center\">\n <ImageIcon className=\"size-3.5 text-muted-foreground\" />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('assets.loading')}\n </Text>\n </Row>\n ) : (\n <Row gap=\"1\" wrap>\n {kind ? <Code tone=\"muted\">{kind}</Code> : null}\n {dims ? <Code tone=\"muted\">{dims}</Code> : null}\n <Code tone=\"muted\">{formatBytes(record.byteSize)}</Code>\n </Row>\n )}\n </Stack>\n </Panel>\n )\n\n if (splat === undefined) return card\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: splat }}\n className=\"block rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40\"\n >\n {card}\n </Link>\n )\n}\n"]}
@@ -30,9 +30,7 @@ export declare function formatBytes(byteSize: number): string;
30
30
  * stored value untouched, `null` clears it, a value sets it.
31
31
  */
32
32
  export interface AssetMetadataPatch {
33
- /** New alt text: a value sets (trimmed), `null` or a trimmed-empty string clears. */
34
- alt?: string | null;
35
- /** New display title; same semantics as `alt`. */
33
+ /** New display title: a value sets (trimmed), `null` or a trimmed-empty string clears. */
36
34
  title?: string | null;
37
35
  /**
38
36
  * New focal point (EC-248), the normalized 0–1 hotspot threaded to delivery's
@@ -46,16 +44,29 @@ export interface AssetMetadataPatch {
46
44
  /**
47
45
  * Schema-driven asset metadata bag (EC-301): alt, copyright, credit, … keyed by
48
46
  * the asset collection's `fields`. An object **replaces** the whole bag (the
49
- * image options dialog holds the complete edited meta); `null` clears it.
47
+ * image options dialog holds the complete edited meta, seeded through
48
+ * {@link assetMetaSeed}); `null` clears it. `meta.alt` is the asset's ONE alt
49
+ * (EC-587) — there is no separate alt key.
50
50
  */
51
51
  meta?: Record<string, unknown> | null;
52
52
  }
53
+ /**
54
+ * The meta bag an editor starts from (EC-587): the stored `meta` with a legacy `alt`
55
+ * column lifted in (core `liftLegacyAlt`), so a not-yet-migrated asset shows the alt
56
+ * every reader shows — and the save writes it to `meta`, its home.
57
+ */
58
+ export declare function assetMetaSeed(record: Pick<AssetRecord, 'alt' | 'meta'>): Record<string, unknown>;
53
59
  /**
54
60
  * The asset write for a metadata edit (EC-577): ONLY the keys the editor changed,
55
61
  * with `null` for a clear. It deliberately restates nothing else — not the
56
62
  * upload-owned columns, and never `thumb`/`lqip`: a stale copy of those would
57
63
  * overwrite a preview backfill that finished while the dialog was open, and a
58
64
  * clear expressed as an omitted key is exactly what never persisted on Convex.
65
+ *
66
+ * EC-587: a `meta` write also clears the legacy `alt` column. The bag was seeded with
67
+ * that column lifted in ({@link assetMetaSeed}), so the alt the editor saw is in the bag
68
+ * (or was deliberately cleared): the column has nothing left to say, and leaving it
69
+ * would re-surface a cleared alt through the read fallback (ASSETS D21).
59
70
  */
60
71
  export declare function assetMetadataWrite(assetId: string, patch: AssetMetadataPatch): AssetWriteInput;
61
72
  export type UpdateAssetMetadataResult = {
@@ -1,3 +1,4 @@
1
+ import { liftLegacyAlt } from '@elytracms/core/persistence';
1
2
  /**
2
3
  * Asset metadata editing (EC-152). A patch is applied through the
3
4
  * {@link AssetRepository} interface — in the studio that is the EC-141
@@ -42,6 +43,14 @@ export function formatBytes(byteSize) {
42
43
  return `${(byteSize / 1024).toFixed(1)} KB`;
43
44
  return `${(byteSize / (1024 * 1024)).toFixed(1)} MB`;
44
45
  }
46
+ /**
47
+ * The meta bag an editor starts from (EC-587): the stored `meta` with a legacy `alt`
48
+ * column lifted in (core `liftLegacyAlt`), so a not-yet-migrated asset shows the alt
49
+ * every reader shows — and the save writes it to `meta`, its home.
50
+ */
51
+ export function assetMetaSeed(record) {
52
+ return liftLegacyAlt(record)?.meta ?? { ...(record.meta ?? {}) };
53
+ }
45
54
  /** A text column's write: a trimmed value sets, `null`/empty clears. */
46
55
  function textWrite(value) {
47
56
  const trimmed = value?.trim() ?? '';
@@ -53,14 +62,18 @@ function textWrite(value) {
53
62
  * upload-owned columns, and never `thumb`/`lqip`: a stale copy of those would
54
63
  * overwrite a preview backfill that finished while the dialog was open, and a
55
64
  * clear expressed as an omitted key is exactly what never persisted on Convex.
65
+ *
66
+ * EC-587: a `meta` write also clears the legacy `alt` column. The bag was seeded with
67
+ * that column lifted in ({@link assetMetaSeed}), so the alt the editor saw is in the bag
68
+ * (or was deliberately cleared): the column has nothing left to say, and leaving it
69
+ * would re-surface a cleared alt through the read fallback (ASSETS D21).
56
70
  */
57
71
  export function assetMetadataWrite(assetId, patch) {
58
72
  return {
59
73
  id: assetId,
60
- ...(patch.alt !== undefined ? { alt: textWrite(patch.alt) } : {}),
61
74
  ...(patch.title !== undefined ? { title: textWrite(patch.title) } : {}),
62
75
  ...(patch.focalPoint !== undefined ? { focalPoint: patch.focalPoint } : {}),
63
- ...(patch.meta !== undefined ? { meta: patch.meta } : {}),
76
+ ...(patch.meta !== undefined ? { meta: patch.meta, alt: null } : {}),
64
77
  };
65
78
  }
66
79
  /**