@elytracms/studio 0.0.93 → 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 (481) hide show
  1. package/convex/analytics-cost.test.ts +817 -0
  2. package/convex/auth.ts +5 -2
  3. package/convex/authority.ts +35 -11
  4. package/convex/contract-parity.test.ts +7 -0
  5. package/convex/delivery.ts +12 -9
  6. package/convex/guard.ts +9 -7
  7. package/convex/http-routes.test.ts +18 -8
  8. package/convex/http.ts +23 -17
  9. package/convex/ingest.test.ts +326 -0
  10. package/convex/ingest.ts +281 -0
  11. package/convex/invites.ts +13 -5
  12. package/convex/members.ts +10 -4
  13. package/convex/record-functions.ts +325 -0
  14. package/convex/records.test.ts +156 -0
  15. package/convex/redirect-migration.test.ts +205 -0
  16. package/convex/redirect-revalidate.test.ts +5 -1
  17. package/convex/redirects.ts +120 -79
  18. package/convex/role-policy.test.ts +315 -0
  19. package/convex/roles.ts +83 -0
  20. package/convex/schema.ts +30 -0
  21. package/convex/triggers.ts +21 -1
  22. package/dist/addon.d.ts +71 -0
  23. package/dist/addon.js +67 -0
  24. package/dist/addon.js.map +1 -0
  25. package/dist/components/account/account-settings.js +2 -2
  26. package/dist/components/account/account-settings.js.map +1 -1
  27. package/dist/components/account/credential-panel.js +2 -2
  28. package/dist/components/account/credential-panel.js.map +1 -1
  29. package/dist/components/account/self-account-schema.d.ts +6 -15
  30. package/dist/components/account/self-account-schema.js +52 -34
  31. package/dist/components/account/self-account-schema.js.map +1 -1
  32. package/dist/components/account/self-account-view-source.d.ts +1 -1
  33. package/dist/components/account/self-account-view-source.js +6 -4
  34. package/dist/components/account/self-account-view-source.js.map +1 -1
  35. package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
  36. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  37. package/dist/components/ai/ai-chat-panel.js +6 -6
  38. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  39. package/dist/components/ai/ai-cms-panel.js +2 -2
  40. package/dist/components/ai/ai-cms-panel.js.map +1 -1
  41. package/dist/components/ai/ai-panel-toggle.js +4 -4
  42. package/dist/components/ai/ai-panel-toggle.js.map +1 -1
  43. package/dist/components/ai/chat-ui.js +2 -2
  44. package/dist/components/ai/chat-ui.js.map +1 -1
  45. package/dist/components/ai/proposal-card.d.ts +2 -2
  46. package/dist/components/ai/proposal-card.js +2 -2
  47. package/dist/components/ai/proposal-card.js.map +1 -1
  48. package/dist/components/ai/proposal-issues.js +2 -2
  49. package/dist/components/ai/proposal-issues.js.map +1 -1
  50. package/dist/components/ai/translate-document-action.js +2 -2
  51. package/dist/components/ai/translate-document-action.js.map +1 -1
  52. package/dist/components/ai/translate-document-dialog.js +6 -6
  53. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  54. package/dist/components/ai/translate-skipped.d.ts +1 -1
  55. package/dist/components/ai/translate-skipped.js +2 -2
  56. package/dist/components/ai/translate-skipped.js.map +1 -1
  57. package/dist/components/app-views/default-modules.d.ts +50 -2
  58. package/dist/components/app-views/default-modules.js +65 -20
  59. package/dist/components/app-views/default-modules.js.map +1 -1
  60. package/dist/components/app-views/module.d.ts +53 -3
  61. package/dist/components/app-views/module.js +14 -7
  62. package/dist/components/app-views/module.js.map +1 -1
  63. package/dist/components/app-views/sidebar.js +1 -1
  64. package/dist/components/app-views/sidebar.js.map +1 -1
  65. package/dist/components/app-views/studio-config.d.ts +11 -2
  66. package/dist/components/app-views/studio-config.js.map +1 -1
  67. package/dist/components/assets/asset-detail.js +2 -2
  68. package/dist/components/assets/asset-detail.js.map +1 -1
  69. package/dist/components/assets/asset-grid.js +2 -2
  70. package/dist/components/assets/asset-grid.js.map +1 -1
  71. package/dist/components/assets/asset-thumb.js +3 -3
  72. package/dist/components/assets/asset-thumb.js.map +1 -1
  73. package/dist/components/assets/assets-collection-view-source.d.ts +2 -2
  74. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  75. package/dist/components/assets/assets-module.js +1 -1
  76. package/dist/components/assets/assets-module.js.map +1 -1
  77. package/dist/components/assets/assets-store.d.ts +2 -2
  78. package/dist/components/assets/assets-store.js +6 -6
  79. package/dist/components/assets/assets-store.js.map +1 -1
  80. package/dist/components/assets/assets-view-preference.js +1 -1
  81. package/dist/components/assets/assets-view-preference.js.map +1 -1
  82. package/dist/components/assets/replace-file.js +1 -1
  83. package/dist/components/assets/replace-file.js.map +1 -1
  84. package/dist/components/auth-context.d.ts +1 -1
  85. package/dist/components/auth-context.js +6 -6
  86. package/dist/components/auth-context.js.map +1 -1
  87. package/dist/components/builder/blocks-model.d.ts +1 -1
  88. package/dist/components/builder/blocks-model.js +3 -3
  89. package/dist/components/builder/blocks-model.js.map +1 -1
  90. package/dist/components/builder/blocks-panel.d.ts +1 -1
  91. package/dist/components/builder/blocks-panel.js +4 -4
  92. package/dist/components/builder/blocks-panel.js.map +1 -1
  93. package/dist/components/builder/builder-store.js +2 -2
  94. package/dist/components/builder/builder-store.js.map +1 -1
  95. package/dist/components/builder/canvas-breakpoints.js +1 -1
  96. package/dist/components/builder/canvas-breakpoints.js.map +1 -1
  97. package/dist/components/builder/canvas-dnd.d.ts +2 -2
  98. package/dist/components/builder/canvas-dnd.js +1 -1
  99. package/dist/components/builder/canvas-dnd.js.map +1 -1
  100. package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
  101. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  102. package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
  103. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
  104. package/dist/components/builder/canvas-render-context.d.ts +1 -1
  105. package/dist/components/builder/canvas-render-context.js +4 -4
  106. package/dist/components/builder/canvas-render-context.js.map +1 -1
  107. package/dist/components/builder/canvas-theme-notice.js +2 -2
  108. package/dist/components/builder/canvas-theme-notice.js.map +1 -1
  109. package/dist/components/builder/canvas.js +3 -3
  110. package/dist/components/builder/canvas.js.map +1 -1
  111. package/dist/components/builder/clipboard-actions.d.ts +2 -2
  112. package/dist/components/builder/clipboard-actions.js +4 -4
  113. package/dist/components/builder/clipboard-actions.js.map +1 -1
  114. package/dist/components/builder/composition-field-editor-lazy.js +1 -1
  115. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
  116. package/dist/components/builder/composition-field-editor.d.ts +1 -1
  117. package/dist/components/builder/composition-field-editor.js +3 -3
  118. package/dist/components/builder/composition-field-editor.js.map +1 -1
  119. package/dist/components/builder/drag-session.d.ts +1 -1
  120. package/dist/components/builder/drag-session.js.map +1 -1
  121. package/dist/components/builder/fixture-thumbnail.js +1 -1
  122. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  123. package/dist/components/builder/insert-context.d.ts +1 -1
  124. package/dist/components/builder/insert-context.js +3 -3
  125. package/dist/components/builder/insert-context.js.map +1 -1
  126. package/dist/components/builder/layout-inspector.js +1 -1
  127. package/dist/components/builder/layout-inspector.js.map +1 -1
  128. package/dist/components/builder/node-title.js +1 -1
  129. package/dist/components/builder/node-title.js.map +1 -1
  130. package/dist/components/builder/page-tree.d.ts +1 -1
  131. package/dist/components/builder/page-tree.js +5 -5
  132. package/dist/components/builder/page-tree.js.map +1 -1
  133. package/dist/components/builder/placement-refusal.js +3 -3
  134. package/dist/components/builder/placement-refusal.js.map +1 -1
  135. package/dist/components/builder/property-inspector.js +4 -4
  136. package/dist/components/builder/property-inspector.js.map +1 -1
  137. package/dist/components/builder/reuse-context.js +4 -4
  138. package/dist/components/builder/reuse-context.js.map +1 -1
  139. package/dist/components/builder/reuse.js +1 -1
  140. package/dist/components/builder/reuse.js.map +1 -1
  141. package/dist/components/builder/rich-text-toolbar.js +1 -1
  142. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  143. package/dist/components/builder/run-inspector.js +1 -1
  144. package/dist/components/builder/run-inspector.js.map +1 -1
  145. package/dist/components/builder/selection-overlay.js +3 -3
  146. package/dist/components/builder/selection-overlay.js.map +1 -1
  147. package/dist/components/builder/spacing-handles.d.ts +2 -2
  148. package/dist/components/builder/spacing-handles.js +3 -3
  149. package/dist/components/builder/spacing-handles.js.map +1 -1
  150. package/dist/components/cms/asset-field.js +2 -2
  151. package/dist/components/cms/asset-field.js.map +1 -1
  152. package/dist/components/cms/asset-picker.js +2 -2
  153. package/dist/components/cms/asset-picker.js.map +1 -1
  154. package/dist/components/cms/bulk-actions.d.ts +1 -1
  155. package/dist/components/cms/bulk-actions.js +2 -2
  156. package/dist/components/cms/bulk-actions.js.map +1 -1
  157. package/dist/components/cms/bulk-bar.d.ts +2 -2
  158. package/dist/components/cms/bulk-bar.js +2 -2
  159. package/dist/components/cms/bulk-bar.js.map +1 -1
  160. package/dist/components/cms/cell-edit.d.ts +1 -1
  161. package/dist/components/cms/cell-edit.js +1 -1
  162. package/dist/components/cms/cell-edit.js.map +1 -1
  163. package/dist/components/cms/cell.d.ts +1 -1
  164. package/dist/components/cms/cell.js +3 -3
  165. package/dist/components/cms/cell.js.map +1 -1
  166. package/dist/components/cms/cms-index.js +2 -2
  167. package/dist/components/cms/cms-index.js.map +1 -1
  168. package/dist/components/cms/cms-labels.js +2 -2
  169. package/dist/components/cms/cms-labels.js.map +1 -1
  170. package/dist/components/cms/cms-store.d.ts +2 -2
  171. package/dist/components/cms/cms-store.js +10 -10
  172. package/dist/components/cms/cms-store.js.map +1 -1
  173. package/dist/components/cms/collection-table.js +7 -7
  174. package/dist/components/cms/collection-table.js.map +1 -1
  175. package/dist/components/cms/detail-editor.d.ts +5 -2
  176. package/dist/components/cms/detail-editor.js +20 -14
  177. package/dist/components/cms/detail-editor.js.map +1 -1
  178. package/dist/components/cms/details-pane.js +1 -1
  179. package/dist/components/cms/details-pane.js.map +1 -1
  180. package/dist/components/cms/doc-state.js +1 -1
  181. package/dist/components/cms/doc-state.js.map +1 -1
  182. package/dist/components/cms/document-diff-view.js +5 -5
  183. package/dist/components/cms/document-diff-view.js.map +1 -1
  184. package/dist/components/cms/document-editor-context.js +1 -1
  185. package/dist/components/cms/document-editor-context.js.map +1 -1
  186. package/dist/components/cms/document-staging.js +1 -1
  187. package/dist/components/cms/document-staging.js.map +1 -1
  188. package/dist/components/cms/document-url-path.js +2 -2
  189. package/dist/components/cms/document-url-path.js.map +1 -1
  190. package/dist/components/cms/duplicate-document.d.ts +1 -1
  191. package/dist/components/cms/duplicate-document.js.map +1 -1
  192. package/dist/components/cms/editable-cell.js +3 -3
  193. package/dist/components/cms/editable-cell.js.map +1 -1
  194. package/dist/components/cms/field-collaboration.d.ts +1 -1
  195. package/dist/components/cms/field-collaboration.js +1 -1
  196. package/dist/components/cms/field-collaboration.js.map +1 -1
  197. package/dist/components/cms/field-connection.d.ts +1 -1
  198. package/dist/components/cms/field-connection.js +2 -2
  199. package/dist/components/cms/field-connection.js.map +1 -1
  200. package/dist/components/cms/field-control-lib.js +1 -1
  201. package/dist/components/cms/field-control-lib.js.map +1 -1
  202. package/dist/components/cms/field-control.js +4 -4
  203. package/dist/components/cms/field-control.js.map +1 -1
  204. package/dist/components/cms/field-renderer.d.ts +1 -1
  205. package/dist/components/cms/field-renderer.js +2 -2
  206. package/dist/components/cms/field-renderer.js.map +1 -1
  207. package/dist/components/cms/filter-builder.d.ts +1 -1
  208. package/dist/components/cms/filter-builder.js +3 -3
  209. package/dist/components/cms/filter-builder.js.map +1 -1
  210. package/dist/components/cms/gate-validation.js +1 -1
  211. package/dist/components/cms/gate-validation.js.map +1 -1
  212. package/dist/components/cms/import-export.js +2 -2
  213. package/dist/components/cms/import-export.js.map +1 -1
  214. package/dist/components/cms/issue-copy.js +1 -1
  215. package/dist/components/cms/issue-copy.js.map +1 -1
  216. package/dist/components/cms/issues-pane.js +3 -3
  217. package/dist/components/cms/issues-pane.js.map +1 -1
  218. package/dist/components/cms/object-array-field.js +2 -2
  219. package/dist/components/cms/object-array-field.js.map +1 -1
  220. package/dist/components/cms/prop-fields.js +2 -2
  221. package/dist/components/cms/prop-fields.js.map +1 -1
  222. package/dist/components/cms/reference-confirm.d.ts +1 -1
  223. package/dist/components/cms/reference-confirm.js +2 -2
  224. package/dist/components/cms/reference-confirm.js.map +1 -1
  225. package/dist/components/cms/relation-picker.js +2 -2
  226. package/dist/components/cms/relation-picker.js.map +1 -1
  227. package/dist/components/cms/rich-text/block-style.js +1 -1
  228. package/dist/components/cms/rich-text/block-style.js.map +1 -1
  229. package/dist/components/cms/rich-text/component-embed.js +4 -4
  230. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  231. package/dist/components/cms/rich-text/editor.js +3 -3
  232. package/dist/components/cms/rich-text/editor.js.map +1 -1
  233. package/dist/components/cms/rich-text/embed-picker.js +3 -3
  234. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  235. package/dist/components/cms/rich-text/embed-props.js +3 -3
  236. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  237. package/dist/components/cms/rich-text/extensions.js +1 -1
  238. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  239. package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
  240. package/dist/components/cms/rich-text/font-size-control.js +2 -2
  241. package/dist/components/cms/rich-text/font-size-control.js.map +1 -1
  242. package/dist/components/cms/rich-text/lazy.js +1 -1
  243. package/dist/components/cms/rich-text/lazy.js.map +1 -1
  244. package/dist/components/cms/rich-text/link-control.js +2 -2
  245. package/dist/components/cms/rich-text/link-control.js.map +1 -1
  246. package/dist/components/cms/rich-text/slash-command.js +3 -3
  247. package/dist/components/cms/rich-text/slash-command.js.map +1 -1
  248. package/dist/components/cms/rich-text/toolbar.js +1 -1
  249. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  250. package/dist/components/cms/rich-text/unknown-node.js +2 -2
  251. package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
  252. package/dist/components/cms/rich-text-preview.js +2 -2
  253. package/dist/components/cms/rich-text-preview.js.map +1 -1
  254. package/dist/components/cms/row-actions.d.ts +1 -1
  255. package/dist/components/cms/row-actions.js.map +1 -1
  256. package/dist/components/cms/scalar-value-editor.d.ts +1 -1
  257. package/dist/components/cms/scalar-value-editor.js +2 -2
  258. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  259. package/dist/components/cms/use-discard-controller.d.ts +1 -1
  260. package/dist/components/cms/use-discard-controller.js +1 -1
  261. package/dist/components/cms/use-discard-controller.js.map +1 -1
  262. package/dist/components/cms/use-document-draft.d.ts +7 -1
  263. package/dist/components/cms/use-document-draft.js +16 -2
  264. package/dist/components/cms/use-document-draft.js.map +1 -1
  265. package/dist/components/cms/use-document-versions.js +1 -1
  266. package/dist/components/cms/use-document-versions.js.map +1 -1
  267. package/dist/components/cms/use-field-focus.d.ts +1 -1
  268. package/dist/components/cms/use-field-focus.js +2 -2
  269. package/dist/components/cms/use-field-focus.js.map +1 -1
  270. package/dist/components/cms/use-live-url.d.ts +1 -1
  271. package/dist/components/cms/use-live-url.js +3 -3
  272. package/dist/components/cms/use-live-url.js.map +1 -1
  273. package/dist/components/cms/use-publish-controller.d.ts +1 -1
  274. package/dist/components/cms/use-publish-controller.js +1 -1
  275. package/dist/components/cms/use-publish-controller.js.map +1 -1
  276. package/dist/components/cms/use-published-perspective.d.ts +1 -1
  277. package/dist/components/cms/use-published-perspective.js.map +1 -1
  278. package/dist/components/cms/use-restore-version.d.ts +1 -1
  279. package/dist/components/cms/use-restore-version.js +1 -1
  280. package/dist/components/cms/use-restore-version.js.map +1 -1
  281. package/dist/components/cms/version-compare-lazy.js +1 -1
  282. package/dist/components/cms/version-compare-lazy.js.map +1 -1
  283. package/dist/components/cms/version-compare.js +1 -1
  284. package/dist/components/cms/version-compare.js.map +1 -1
  285. package/dist/components/cms/versions-pane.js +3 -3
  286. package/dist/components/cms/versions-pane.js.map +1 -1
  287. package/dist/components/cms/view-bar.js +3 -3
  288. package/dist/components/cms/view-bar.js.map +1 -1
  289. package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
  290. package/dist/components/collection-view/cms-collection-view-source.js +2 -2
  291. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  292. package/dist/components/collection-view/collection-view-context.d.ts +1 -1
  293. package/dist/components/collection-view/collection-view-context.js +1 -1
  294. package/dist/components/collection-view/collection-view-context.js.map +1 -1
  295. package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
  296. package/dist/components/collection-view/collection-view-surface.js +18 -0
  297. package/dist/components/collection-view/collection-view-surface.js.map +1 -0
  298. package/dist/components/components/component-detail.js +10 -10
  299. package/dist/components/components/component-detail.js.map +1 -1
  300. package/dist/components/components/component-preview.d.ts +2 -2
  301. package/dist/components/components/component-preview.js +5 -5
  302. package/dist/components/components/component-preview.js.map +1 -1
  303. package/dist/components/components/component-status.d.ts +1 -1
  304. package/dist/components/components/component-status.js +1 -1
  305. package/dist/components/components/component-status.js.map +1 -1
  306. package/dist/components/components/components-index.js +2 -2
  307. package/dist/components/components/components-index.js.map +1 -1
  308. package/dist/components/components/field-inspector.js +1 -1
  309. package/dist/components/components/field-inspector.js.map +1 -1
  310. package/dist/components/components/registry-browser.js +3 -3
  311. package/dist/components/components/registry-browser.js.map +1 -1
  312. package/dist/components/components/taxonomy.d.ts +1 -1
  313. package/dist/components/components/taxonomy.js.map +1 -1
  314. package/dist/components/components/transfer-dialog.js +3 -3
  315. package/dist/components/components/transfer-dialog.js.map +1 -1
  316. package/dist/components/current-user-menu.js +1 -1
  317. package/dist/components/current-user-menu.js.map +1 -1
  318. package/dist/components/delivery-health-banner.js +2 -2
  319. package/dist/components/delivery-health-banner.js.map +1 -1
  320. package/dist/components/deployment-setup-gate.js +3 -3
  321. package/dist/components/deployment-setup-gate.js.map +1 -1
  322. package/dist/components/dev-login-badge.js +2 -2
  323. package/dist/components/dev-login-badge.js.map +1 -1
  324. package/dist/components/guards.js +1 -1
  325. package/dist/components/guards.js.map +1 -1
  326. package/dist/components/inspector/document-panes.d.ts +54 -0
  327. package/dist/components/inspector/document-panes.js +41 -0
  328. package/dist/components/inspector/document-panes.js.map +1 -0
  329. package/dist/components/inspector/inspector-sidebar.js +1 -1
  330. package/dist/components/inspector/inspector-sidebar.js.map +1 -1
  331. package/dist/components/nav-issues-registry.d.ts +53 -0
  332. package/dist/components/nav-issues-registry.js +69 -0
  333. package/dist/components/nav-issues-registry.js.map +1 -0
  334. package/dist/components/nav-issues.js +4 -4
  335. package/dist/components/nav-issues.js.map +1 -1
  336. package/dist/components/operation-error.d.ts +1 -1
  337. package/dist/components/operation-error.js +4 -4
  338. package/dist/components/operation-error.js.map +1 -1
  339. package/dist/components/playground-badge.js +2 -2
  340. package/dist/components/playground-badge.js.map +1 -1
  341. package/dist/components/project-list.js +3 -3
  342. package/dist/components/project-list.js.map +1 -1
  343. package/dist/components/project-session.d.ts +2 -2
  344. package/dist/components/project-session.js +5 -5
  345. package/dist/components/project-session.js.map +1 -1
  346. package/dist/components/project-switcher.js +2 -2
  347. package/dist/components/project-switcher.js.map +1 -1
  348. package/dist/components/rail-store-status.js +1 -1
  349. package/dist/components/rail-store-status.js.map +1 -1
  350. package/dist/components/rail-view-source.d.ts +6 -5
  351. package/dist/components/rail-view-source.js +4 -3
  352. package/dist/components/rail-view-source.js.map +1 -1
  353. package/dist/components/records/record-lens.d.ts +51 -0
  354. package/dist/components/records/record-lens.js +156 -0
  355. package/dist/components/records/record-lens.js.map +1 -0
  356. package/dist/components/records/record-rail.d.ts +86 -0
  357. package/dist/components/records/record-rail.js +241 -0
  358. package/dist/components/records/record-rail.js.map +1 -0
  359. package/dist/components/redirects/redirects-schema.js +1 -1
  360. package/dist/components/redirects/redirects-schema.js.map +1 -1
  361. package/dist/components/references/references-panel.js +2 -2
  362. package/dist/components/references/references-panel.js.map +1 -1
  363. package/dist/components/section-nav.js +27 -11
  364. package/dist/components/section-nav.js.map +1 -1
  365. package/dist/components/section-state.js +2 -2
  366. package/dist/components/section-state.js.map +1 -1
  367. package/dist/components/section-surface.js +2 -2
  368. package/dist/components/section-surface.js.map +1 -1
  369. package/dist/components/session-expired-overlay.js +3 -3
  370. package/dist/components/session-expired-overlay.js.map +1 -1
  371. package/dist/components/settings-form.js +1 -1
  372. package/dist/components/settings-form.js.map +1 -1
  373. package/dist/components/sign-in-needed-badge.js +1 -1
  374. package/dist/components/sign-in-needed-badge.js.map +1 -1
  375. package/dist/components/sign-in-view.d.ts +2 -2
  376. package/dist/components/sign-in-view.js +5 -5
  377. package/dist/components/sign-in-view.js.map +1 -1
  378. package/dist/components/studio-context.js +3 -3
  379. package/dist/components/studio-context.js.map +1 -1
  380. package/dist/components/studio-project-screen.js +30 -3
  381. package/dist/components/studio-project-screen.js.map +1 -1
  382. package/dist/components/studio-shell.js +6 -5
  383. package/dist/components/studio-shell.js.map +1 -1
  384. package/dist/components/theme-context.d.ts +1 -1
  385. package/dist/components/theme-context.js +2 -2
  386. package/dist/components/theme-context.js.map +1 -1
  387. package/dist/components/use-hydration-phase.d.ts +1 -1
  388. package/dist/components/use-hydration-phase.js +1 -1
  389. package/dist/components/use-hydration-phase.js.map +1 -1
  390. package/dist/components/users/invite-user-bar.d.ts +27 -0
  391. package/dist/components/users/invite-user-bar.js +25 -9
  392. package/dist/components/users/invite-user-bar.js.map +1 -1
  393. package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
  394. package/dist/components/users/role-policy-drift-issues.js +71 -0
  395. package/dist/components/users/role-policy-drift-issues.js.map +1 -0
  396. package/dist/components/users/users-collection-view-source.d.ts +1 -1
  397. package/dist/components/users/users-collection-view-source.js +4 -2
  398. package/dist/components/users/users-collection-view-source.js.map +1 -1
  399. package/dist/components/users/users-module.js +1 -1
  400. package/dist/components/users/users-module.js.map +1 -1
  401. package/dist/components/users/users-schema.d.ts +10 -1
  402. package/dist/components/users/users-schema.js +45 -25
  403. package/dist/components/users/users-schema.js.map +1 -1
  404. package/dist/components/users/users-store.js +3 -3
  405. package/dist/components/users/users-store.js.map +1 -1
  406. package/dist/components/workspace-projects.d.ts +1 -1
  407. package/dist/components/workspace-projects.js +1 -1
  408. package/dist/components/workspace-projects.js.map +1 -1
  409. package/dist/lib/ai/index.d.ts +1 -1
  410. package/dist/lib/ai/index.js +1 -1
  411. package/dist/lib/ai/index.js.map +1 -1
  412. package/dist/lib/ai/node-handler.d.ts +1 -1
  413. package/dist/lib/ai/node-handler.js.map +1 -1
  414. package/dist/lib/ai/translate-document.js +1 -1
  415. package/dist/lib/ai/translate-document.js.map +1 -1
  416. package/dist/lib/clipboard/paste-issues.js +1 -1
  417. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  418. package/dist/lib/clipboard/paste-target.js +2 -2
  419. package/dist/lib/clipboard/paste-target.js.map +1 -1
  420. package/dist/lib/cms/published-documents.js +1 -1
  421. package/dist/lib/cms/published-documents.js.map +1 -1
  422. package/dist/lib/cms-views/view-model.d.ts +12 -12
  423. package/dist/lib/cms-views/view-model.js +2 -2
  424. package/dist/lib/cms-views/view-model.js.map +1 -1
  425. package/dist/lib/collection-view/collection-view-source.d.ts +7 -2
  426. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  427. package/dist/lib/data/content-session-refresh.d.ts +1 -1
  428. package/dist/lib/data/content-session-refresh.js.map +1 -1
  429. package/dist/lib/data/playground-unload-guard.d.ts +1 -1
  430. package/dist/lib/data/playground-unload-guard.js +1 -1
  431. package/dist/lib/data/playground-unload-guard.js.map +1 -1
  432. package/dist/lib/engine-version.d.ts +1 -1
  433. package/dist/lib/engine-version.js +1 -1
  434. package/dist/lib/engine-version.js.map +1 -1
  435. package/dist/lib/i18n/i18n.js +1 -1
  436. package/dist/lib/i18n/i18n.js.map +1 -1
  437. package/dist/lib/i18n/messages.de.js +4 -0
  438. package/dist/lib/i18n/messages.de.js.map +1 -1
  439. package/dist/lib/i18n/messages.en.d.ts +4 -0
  440. package/dist/lib/i18n/messages.en.js +8 -0
  441. package/dist/lib/i18n/messages.en.js.map +1 -1
  442. package/dist/lib/operations/project-runtime-config.d.ts +9 -0
  443. package/dist/lib/operations/project-runtime-config.js +13 -0
  444. package/dist/lib/operations/project-runtime-config.js.map +1 -1
  445. package/dist/lib/operations/studio-operations.d.ts +9 -1
  446. package/dist/lib/operations/studio-operations.js +6 -1
  447. package/dist/lib/operations/studio-operations.js.map +1 -1
  448. package/dist/lib/persistence/convex-adapter.d.ts +16 -3
  449. package/dist/lib/persistence/convex-adapter.js +78 -47
  450. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  451. package/dist/lib/persistence/index.js +14 -1
  452. package/dist/lib/persistence/index.js.map +1 -1
  453. package/dist/lib/persona/labels.js +1 -1
  454. package/dist/lib/persona/labels.js.map +1 -1
  455. package/dist/lib/query/operations-query.d.ts +1 -1
  456. package/dist/lib/query/operations-query.js +1 -1
  457. package/dist/lib/query/operations-query.js.map +1 -1
  458. package/dist/lib/references/reference-model.js +1 -1
  459. package/dist/lib/references/reference-model.js.map +1 -1
  460. package/dist/lib/references/references-store.js +1 -1
  461. package/dist/lib/references/references-store.js.map +1 -1
  462. package/dist/lib/workspace/index.d.ts +6 -0
  463. package/dist/lib/workspace/index.js +8 -0
  464. package/dist/lib/workspace/index.js.map +1 -1
  465. package/dist/mount.d.ts +28 -0
  466. package/dist/mount.js +27 -0
  467. package/dist/mount.js.map +1 -0
  468. package/dist/router/root.js +10 -10
  469. package/dist/router/root.js.map +1 -1
  470. package/dist/ui/badge.d.ts +1 -1
  471. package/dist/ui/button.d.ts +2 -2
  472. package/package.json +15 -11
  473. package/dist/components/redirects/redirects-collection-view-source.d.ts +0 -53
  474. package/dist/components/redirects/redirects-collection-view-source.js +0 -146
  475. package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
  476. package/dist/components/redirects/redirects-module.d.ts +0 -21
  477. package/dist/components/redirects/redirects-module.js +0 -40
  478. package/dist/components/redirects/redirects-module.js.map +0 -1
  479. package/dist/components/redirects/redirects-store.d.ts +0 -68
  480. package/dist/components/redirects/redirects-store.js +0 -132
  481. package/dist/components/redirects/redirects-store.js.map +0 -1
@@ -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"]}
@@ -3,10 +3,10 @@ import { useMemo, useRef, useState } from 'react';
3
3
  import { Image } from 'lucide-react';
4
4
  import { assetAlt } from '@elytracms/core/persistence';
5
5
  import { Text } from '@elytracms/studio/ui';
6
- import { translate } from '@elytracms/studio/lib/i18n';
7
- import { cn } from '@elytracms/studio/lib/utils';
6
+ import { translate } from '../../lib/i18n/index.js';
7
+ import { cn } from '../../lib/utils.js';
8
8
  import { useOptionalAssets } from './assets-store.js';
9
- import { isDisplayableAssetUrl } from '@elytracms/studio/lib/assets';
9
+ import { isDisplayableAssetUrl } from '../../lib/assets/index.js';
10
10
  import { CENTER_FOCAL_POINT, focalPointFromPoint, focalPointObjectPosition, } from './focal-point.js';
11
11
  /** Resolve an asset record by id from the ambient library, if one is mounted. */
12
12
  export function useAssetRecord(assetId) {
@@ -1 +1 @@
1
- {"version":3,"file":"asset-thumb.js","sourceRoot":"","sources":["../../../src/components/assets/asset-thumb.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useRef, useState } from 'react'\nimport { Image } from 'lucide-react'\nimport { assetAlt } from '@elytracms/core/persistence'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useOptionalAssets } from './assets-store'\nimport { isDisplayableAssetUrl } from '@elytracms/studio/lib/assets'\nimport {\n CENTER_FOCAL_POINT,\n focalPointFromPoint,\n focalPointObjectPosition,\n} from './focal-point'\nimport type { FocalPoint } from './focal-point'\n\n/** Resolve an asset record by id from the ambient library, if one is mounted. */\nexport function useAssetRecord(assetId: string | null | undefined): AssetRecord | undefined {\n const store = useOptionalAssets()\n return useMemo(() => {\n if (!assetId || !store || store.status !== 'ready') return undefined\n return store.assets.find((a) => a.id === assetId)\n }, [assetId, store])\n}\n\n/** True when the record is an image with a browser-displayable URL (EC-212, EC-459). */\nfunction isResolvableImage(record: AssetRecord | undefined): record is AssetRecord & { url: string } {\n return (\n record !== undefined &&\n record.contentType.startsWith('image/') &&\n isDisplayableAssetUrl(record.url)\n )\n}\n\n/**\n * The best studio thumbnail src for an image record (EC-400): prefer the stored\n * downscaled `thumb` variant when it is browser-displayable, else fall back to\n * the full original `url` (EC-459: `http(s)`, `blob:`, and `data:` all qualify —\n * an opaque backend locator like `memory://` does not). A missing/broken thumb\n * therefore degrades to the original — never a broken tile (scope §4). Non-image\n * / unresolvable records yield `undefined` (the icon placeholder). Focal-point\n * editing + the canvas keep the original elsewhere (delivery fidelity) — this\n * preference is thumbnail-only.\n */\nfunction thumbnailSrc(record: AssetRecord | undefined): string | undefined {\n if (!record || !record.contentType.startsWith('image/')) return undefined\n const thumbUrl = record.thumb?.url\n if (isDisplayableAssetUrl(thumbUrl)) return thumbUrl\n if (isDisplayableAssetUrl(record.url)) return record.url\n return undefined\n}\n\n/**\n * A read-only image thumbnail honouring the asset's focal point (EC-248): the\n * preview crops with the same `object-position` delivery uses (EC-230), so the\n * editor sees exactly what a focal-point change does. Prefers the EC-400\n * downscaled `thumb` variant (light egress) and falls back to the original.\n * Non-image / unresolvable assets fall back to the labelled icon, matching the\n * table cell's preview.\n */\nexport function AssetThumb({\n record,\n className,\n alt,\n}: {\n record: AssetRecord | undefined\n className?: string\n alt?: string\n}) {\n const src = thumbnailSrc(record)\n if (record && src) {\n return (\n <img\n src={src}\n alt={alt ?? assetAlt(record) ?? record.title ?? record.filename}\n loading=\"lazy\"\n style={{ objectPosition: focalPointObjectPosition(record.focalPoint) }}\n className={cn('rounded border border-border bg-muted object-cover', className)}\n />\n )\n }\n return (\n <div\n className={cn(\n 'flex items-center justify-center rounded border border-border bg-muted text-muted-foreground',\n className,\n )}\n >\n <Image className=\"size-4\" aria-hidden />\n </div>\n )\n}\n\n/**\n * The focal-point picker (EC-248/EC-301): click or **drag** anywhere on the image\n * to place the normalized 0–1 hotspot delivery threads to `object-position`\n * (EC-230). The marker follows the pointer live while dragging and only commits on\n * release (`onPreview` updates the marker, `onChange` commits). `onChange(null)`\n * resets to centre. Shows a placeholder until the image loads; renders a muted note\n * when the asset has no resolvable image.\n */\nexport function FocalPointPicker({\n record,\n point,\n onChange,\n onPreview,\n disabled,\n}: {\n record: AssetRecord | undefined\n point: FocalPoint | undefined\n onChange: (point: FocalPoint | null) => void\n /** Called continuously while dragging to move the marker without committing. */\n onPreview?: (point: FocalPoint) => void\n disabled?: boolean\n}) {\n const marker = point ?? CENTER_FOCAL_POINT\n const dragging = useRef(false)\n const [loaded, setLoaded] = useState(false)\n\n if (!isResolvableImage(record)) {\n return (\n <Text size=\"xs\" tone=\"subtle\">\n {translate('assets.focalPoint.noPreview')}\n </Text>\n )\n }\n\n const pointFrom = (e: React.PointerEvent<HTMLElement>): FocalPoint =>\n focalPointFromPoint(e.currentTarget.getBoundingClientRect(), e.clientX, e.clientY)\n\n function onPointerDown(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled) return\n dragging.current = true\n e.currentTarget.setPointerCapture(e.pointerId)\n ;(onPreview ?? onChange)(pointFrom(e))\n }\n function onPointerMove(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled || !dragging.current) return\n ;(onPreview ?? onChange)(pointFrom(e))\n }\n function onPointerUp(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled || !dragging.current) return\n dragging.current = false\n e.currentTarget.releasePointerCapture(e.pointerId)\n onChange(pointFrom(e)) // commit on release only\n }\n\n return (\n <button\n type=\"button\"\n aria-label={translate('assets.focalPoint.setLabel')}\n disabled={disabled}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n className={cn(\n 'relative block w-full touch-none select-none overflow-hidden rounded border border-border bg-muted',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',\n disabled ? 'cursor-default opacity-60' : 'cursor-crosshair',\n )}\n >\n {/* Width-constrained, natural aspect (no object-fit), so the button wraps\n the image tightly and the bounding rect equals the image box — the\n normalized pointer coordinates are then exact, with no letterbox offset. */}\n <img\n src={record.url}\n alt={assetAlt(record) ?? record.filename}\n draggable={false}\n onLoad={() => setLoaded(true)}\n className={cn('block h-auto w-full select-none transition-opacity', loaded ? 'opacity-100' : 'opacity-0')}\n />\n {!loaded ? (\n <span className=\"absolute inset-0 grid aspect-video place-items-center bg-muted\">\n <Image className=\"size-6 animate-pulse text-muted-foreground\" aria-hidden />\n </span>\n ) : (\n <span\n aria-hidden\n className=\"pointer-events-none absolute size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1.5px_rgba(0,0,0,0.55)] ring-1 ring-black/40\"\n style={{ left: `${marker.x * 100}%`, top: `${marker.y * 100}%` }}\n />\n )}\n </button>\n )\n}\n\n/** Common crop aspect ratios shown as focal-aware previews (EC-301). */\nconst FOCAL_CROP_RATIOS = [\n { label: '9:16', className: 'aspect-[9/16]' },\n { label: '3:4', className: 'aspect-[3/4]' },\n { label: '1:1', className: 'aspect-square' },\n { label: '4:3', className: 'aspect-[4/3]' },\n { label: '16:9', className: 'aspect-video' },\n] as const\n\n/**\n * A row of common-ratio crop previews (EC-301), each cropping the image with the\n * same `object-position` delivery uses — so the editor sees how the focal point\n * holds the subject across portrait/square/landscape crops. Updates live as the\n * point is dragged.\n */\nexport function FocalCropPreviews({\n record,\n point,\n}: {\n record: AssetRecord | undefined\n point: FocalPoint | undefined\n}) {\n if (!isResolvableImage(record)) return null\n const objectPosition = focalPointObjectPosition(point)\n return (\n <div className=\"flex items-end gap-2\">\n {FOCAL_CROP_RATIOS.map((ratio) => (\n <div key={ratio.label} className=\"flex min-w-0 flex-col items-center gap-1\">\n <div className={cn('h-14 overflow-hidden rounded border border-border bg-muted', ratio.className)}>\n <img\n src={record.url}\n alt=\"\"\n draggable={false}\n style={{ objectPosition }}\n className=\"h-full w-full object-cover\"\n />\n </div>\n <Text size=\"xs\" tone=\"subtle\">\n {ratio.label}\n </Text>\n </div>\n ))}\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"asset-thumb.js","sourceRoot":"","sources":["../../../src/components/assets/asset-thumb.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useRef, useState } from 'react'\nimport { Image } from 'lucide-react'\nimport { assetAlt } from '@elytracms/core/persistence'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Text } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\nimport { cn } from '../../lib/utils'\nimport { useOptionalAssets } from './assets-store'\nimport { isDisplayableAssetUrl } from '../../lib/assets'\nimport {\n CENTER_FOCAL_POINT,\n focalPointFromPoint,\n focalPointObjectPosition,\n} from './focal-point'\nimport type { FocalPoint } from './focal-point'\n\n/** Resolve an asset record by id from the ambient library, if one is mounted. */\nexport function useAssetRecord(assetId: string | null | undefined): AssetRecord | undefined {\n const store = useOptionalAssets()\n return useMemo(() => {\n if (!assetId || !store || store.status !== 'ready') return undefined\n return store.assets.find((a) => a.id === assetId)\n }, [assetId, store])\n}\n\n/** True when the record is an image with a browser-displayable URL (EC-212, EC-459). */\nfunction isResolvableImage(record: AssetRecord | undefined): record is AssetRecord & { url: string } {\n return (\n record !== undefined &&\n record.contentType.startsWith('image/') &&\n isDisplayableAssetUrl(record.url)\n )\n}\n\n/**\n * The best studio thumbnail src for an image record (EC-400): prefer the stored\n * downscaled `thumb` variant when it is browser-displayable, else fall back to\n * the full original `url` (EC-459: `http(s)`, `blob:`, and `data:` all qualify —\n * an opaque backend locator like `memory://` does not). A missing/broken thumb\n * therefore degrades to the original — never a broken tile (scope §4). Non-image\n * / unresolvable records yield `undefined` (the icon placeholder). Focal-point\n * editing + the canvas keep the original elsewhere (delivery fidelity) — this\n * preference is thumbnail-only.\n */\nfunction thumbnailSrc(record: AssetRecord | undefined): string | undefined {\n if (!record || !record.contentType.startsWith('image/')) return undefined\n const thumbUrl = record.thumb?.url\n if (isDisplayableAssetUrl(thumbUrl)) return thumbUrl\n if (isDisplayableAssetUrl(record.url)) return record.url\n return undefined\n}\n\n/**\n * A read-only image thumbnail honouring the asset's focal point (EC-248): the\n * preview crops with the same `object-position` delivery uses (EC-230), so the\n * editor sees exactly what a focal-point change does. Prefers the EC-400\n * downscaled `thumb` variant (light egress) and falls back to the original.\n * Non-image / unresolvable assets fall back to the labelled icon, matching the\n * table cell's preview.\n */\nexport function AssetThumb({\n record,\n className,\n alt,\n}: {\n record: AssetRecord | undefined\n className?: string\n alt?: string\n}) {\n const src = thumbnailSrc(record)\n if (record && src) {\n return (\n <img\n src={src}\n alt={alt ?? assetAlt(record) ?? record.title ?? record.filename}\n loading=\"lazy\"\n style={{ objectPosition: focalPointObjectPosition(record.focalPoint) }}\n className={cn('rounded border border-border bg-muted object-cover', className)}\n />\n )\n }\n return (\n <div\n className={cn(\n 'flex items-center justify-center rounded border border-border bg-muted text-muted-foreground',\n className,\n )}\n >\n <Image className=\"size-4\" aria-hidden />\n </div>\n )\n}\n\n/**\n * The focal-point picker (EC-248/EC-301): click or **drag** anywhere on the image\n * to place the normalized 0–1 hotspot delivery threads to `object-position`\n * (EC-230). The marker follows the pointer live while dragging and only commits on\n * release (`onPreview` updates the marker, `onChange` commits). `onChange(null)`\n * resets to centre. Shows a placeholder until the image loads; renders a muted note\n * when the asset has no resolvable image.\n */\nexport function FocalPointPicker({\n record,\n point,\n onChange,\n onPreview,\n disabled,\n}: {\n record: AssetRecord | undefined\n point: FocalPoint | undefined\n onChange: (point: FocalPoint | null) => void\n /** Called continuously while dragging to move the marker without committing. */\n onPreview?: (point: FocalPoint) => void\n disabled?: boolean\n}) {\n const marker = point ?? CENTER_FOCAL_POINT\n const dragging = useRef(false)\n const [loaded, setLoaded] = useState(false)\n\n if (!isResolvableImage(record)) {\n return (\n <Text size=\"xs\" tone=\"subtle\">\n {translate('assets.focalPoint.noPreview')}\n </Text>\n )\n }\n\n const pointFrom = (e: React.PointerEvent<HTMLElement>): FocalPoint =>\n focalPointFromPoint(e.currentTarget.getBoundingClientRect(), e.clientX, e.clientY)\n\n function onPointerDown(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled) return\n dragging.current = true\n e.currentTarget.setPointerCapture(e.pointerId)\n ;(onPreview ?? onChange)(pointFrom(e))\n }\n function onPointerMove(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled || !dragging.current) return\n ;(onPreview ?? onChange)(pointFrom(e))\n }\n function onPointerUp(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled || !dragging.current) return\n dragging.current = false\n e.currentTarget.releasePointerCapture(e.pointerId)\n onChange(pointFrom(e)) // commit on release only\n }\n\n return (\n <button\n type=\"button\"\n aria-label={translate('assets.focalPoint.setLabel')}\n disabled={disabled}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n className={cn(\n 'relative block w-full touch-none select-none overflow-hidden rounded border border-border bg-muted',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',\n disabled ? 'cursor-default opacity-60' : 'cursor-crosshair',\n )}\n >\n {/* Width-constrained, natural aspect (no object-fit), so the button wraps\n the image tightly and the bounding rect equals the image box — the\n normalized pointer coordinates are then exact, with no letterbox offset. */}\n <img\n src={record.url}\n alt={assetAlt(record) ?? record.filename}\n draggable={false}\n onLoad={() => setLoaded(true)}\n className={cn('block h-auto w-full select-none transition-opacity', loaded ? 'opacity-100' : 'opacity-0')}\n />\n {!loaded ? (\n <span className=\"absolute inset-0 grid aspect-video place-items-center bg-muted\">\n <Image className=\"size-6 animate-pulse text-muted-foreground\" aria-hidden />\n </span>\n ) : (\n <span\n aria-hidden\n className=\"pointer-events-none absolute size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1.5px_rgba(0,0,0,0.55)] ring-1 ring-black/40\"\n style={{ left: `${marker.x * 100}%`, top: `${marker.y * 100}%` }}\n />\n )}\n </button>\n )\n}\n\n/** Common crop aspect ratios shown as focal-aware previews (EC-301). */\nconst FOCAL_CROP_RATIOS = [\n { label: '9:16', className: 'aspect-[9/16]' },\n { label: '3:4', className: 'aspect-[3/4]' },\n { label: '1:1', className: 'aspect-square' },\n { label: '4:3', className: 'aspect-[4/3]' },\n { label: '16:9', className: 'aspect-video' },\n] as const\n\n/**\n * A row of common-ratio crop previews (EC-301), each cropping the image with the\n * same `object-position` delivery uses — so the editor sees how the focal point\n * holds the subject across portrait/square/landscape crops. Updates live as the\n * point is dragged.\n */\nexport function FocalCropPreviews({\n record,\n point,\n}: {\n record: AssetRecord | undefined\n point: FocalPoint | undefined\n}) {\n if (!isResolvableImage(record)) return null\n const objectPosition = focalPointObjectPosition(point)\n return (\n <div className=\"flex items-end gap-2\">\n {FOCAL_CROP_RATIOS.map((ratio) => (\n <div key={ratio.label} className=\"flex min-w-0 flex-col items-center gap-1\">\n <div className={cn('h-14 overflow-hidden rounded border border-border bg-muted', ratio.className)}>\n <img\n src={record.url}\n alt=\"\"\n draggable={false}\n style={{ objectPosition }}\n className=\"h-full w-full object-cover\"\n />\n </div>\n <Text size=\"xs\" tone=\"subtle\">\n {ratio.label}\n </Text>\n </div>\n ))}\n </div>\n )\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  import type { CmsDocument, CollectionDef, DocumentRef } from '@elytracms/core/cms-core';
2
2
  import type { AssetRecord, ReferenceTarget } from '@elytracms/core/persistence';
3
3
  import type { OperationError } from '@elytracms/core/operations';
4
- import type { DeleteAssetResult } from '@elytracms/studio/lib/assets';
5
- import type { CollectionViewSource, CollectionViewCapabilities } from '@elytracms/studio/lib/collection-view/collection-view-source';
4
+ import type { DeleteAssetResult } from '../../lib/assets/index.js';
5
+ import type { CollectionViewSource, CollectionViewCapabilities } from '../../lib/collection-view/collection-view-source.js';
6
6
  /**
7
7
  * The capability profile of the assets rail (EC-319, EC-433). Hoisted to a
8
8
  * module-level constant (no prop/state dependency) so it's both the value the
@@ -1 +1 @@
1
- {"version":3,"file":"assets-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/assets/assets-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type { CmsDocument, CollectionDef, DocumentRef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport { createCollectionRegistry, validateDocument } from '@elytracms/core/cms-core'\nimport { assetAlt } from '@elytracms/core/persistence'\nimport type { AssetRecord, ReferenceTarget } from '@elytracms/core/persistence'\nimport type { OperationError } from '@elytracms/core/operations'\nimport { studioPath } from '@elytracms/studio/router'\nimport type { DeleteAssetResult } from '@elytracms/studio/lib/assets'\nimport type { CollectionViewSource, CollectionViewCapabilities } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useAssets } from './assets-store'\nimport { SYSTEM_ASSET_FIELDS, pruneMeta } from './asset-metadata'\nimport { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source'\nimport { parseLocaleConfig } from '@elytracms/core/cms-core'\n\n/** The assets rail is not localized in v1 (EC-301: asset metadata is non-localized). */\nconst ASSETS_LOCALE: Locale = 'en'\nconst ASSETS_LOCALE_CONFIG: LocaleConfig = parseLocaleConfig({\n default: ASSETS_LOCALE,\n locales: [ASSETS_LOCALE],\n})\n\n/**\n * The capability profile of the assets rail (EC-319, EC-433). Hoisted to a\n * module-level constant (no prop/state dependency) so it's both the value the\n * hook returns AND independently unit-testable.\n *\n * `create` is off — a new asset comes from an upload, never a blank draft.\n * `remove` + `bulk` are ON since EC-433: the list's row/bulk delete used to be\n * off deliberately because the generic `ReferenceConfirmDialog` enumerated\n * inbound references as `{ type: 'document' }` targets — the wrong index shape\n * for an asset — which would have shown \"not referenced\" for an asset that IS\n * referenced. That pre-flight now queries the shape THIS source declares via\n * {@link assetReferenceTarget} (`{ type: 'asset' }`, the same lookup the\n * detail surface runs), and the confirmed delete dispatches the SAME checked\n * store delete the detail surface uses (`deleteAsset`: original + EC-400 thumb\n * blobs, then the record — EC-420 lqips ride on the record). Nothing else\n * applies to assets: no publish/versions/locales/inline-edit/live-URL/\n * hierarchy/import-export.\n */\nexport const ASSET_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: false,\n duplicate: false, // EC-442: blob duplication is a different feature, out of scope\n remove: true,\n bulk: true,\n liveUrl: false,\n hierarchy: false,\n importExport: false,\n adminOnly: false,\n}\n\n/** Read one of the record's fixed, upload-managed columns by field name. */\nfunction systemFieldValue(record: AssetRecord, name: string): unknown {\n switch (name) {\n case 'filename':\n return record.filename\n case 'url':\n return record.url\n case 'contentType':\n return record.contentType\n case 'byteSize':\n return record.byteSize\n case 'width':\n return record.width\n case 'height':\n return record.height\n case 'storageKey':\n return record.storageKey\n case 'title':\n return record.title\n default:\n return undefined\n }\n}\n\n/**\n * Project a stored asset record into the lens's `values` shape (EC-319) — the\n * value-keyed projection the redirects/users rails use (EC-577, RESIDUE-3d): EVERY\n * key of the record's open `meta` bag (EC-301) rides in `values`, declared by the\n * schema or not, so a save hands the bag back whole (D22). Declared fields then\n * take their canonical reading on top — a column never loses to a same-named meta\n * key: a system column (`filename`, `url`, dimensions, …) reads straight off the\n * record, and `alt` reads through THE reader (`assetAlt`, EC-587 — `meta.alt`, with a\n * not-yet-migrated legacy column lifted in), exactly what delivery and the image\n * field's options dialog show.\n */\nexport function assetToDocument(collection: CollectionDef, record: AssetRecord): CmsDocument {\n const values: Record<string, unknown> = { ...(record.meta ?? {}) }\n for (const field of collection.fields) {\n if (field.name === 'alt') {\n const alt = assetAlt(record)\n if (alt !== null) values.alt = alt\n else delete values.alt\n } else if (SYSTEM_ASSET_FIELDS.has(field.name)) {\n values[field.name] = systemFieldValue(record, field.name)\n }\n }\n return { collection: collection.id, id: record.id, values }\n}\n\n/**\n * The inverse of {@link assetToDocument}: the meta bag a save writes back — every\n * value outside the system set (EC-301/EC-311), declared or not, through the ONE\n * value-keyed projection the users/redirects rails share (EC-577, RESIDUE-3d).\n * `meta` is written whole (it replaces the stored bag), so a key the lens never\n * declared must be in it: the schema-driven projection this replaced dropped them\n * on every rail save (D22), while the options dialog and asset detail kept them.\n * System columns are upload-managed and never written here.\n */\nexport function documentToAssetMetaPatch(doc: CmsDocument): Record<string, unknown> {\n return pruneMeta(metaBagProjection(doc.values ?? {}, SYSTEM_ASSET_FIELDS))\n}\n\n/** `create` when `capabilities.create === false` — never reached from the UI\n * (list/detail never call it); an explicit throw rather than a silent no-op. */\nfunction createUnsupported(): DocumentRef {\n throw new Error('assets are created by upload, not the assets rail')\n}\n\n/** Assets' `remove()` returns an `outcome`-discriminated {@link DeleteAssetResult}\n * (not the `{ ok }` shape users/redirects use, EC-327). `'error'` surfaces as-is;\n * `'blocked'` (the unconfirmed usage gate fired — nothing was removed) surfaces\n * as an explicit failure too (EC-433): a delete that left the asset in place\n * must NEVER look like success. It is unreachable from this rail's UI — every\n * delete path confirms through the reference dialog and dispatches with\n * `confirmInUse` — but stays honest for any direct caller. `'not-found'` maps\n * to success (idempotent delete: the desired end state — gone — holds). */\nexport function normalizeAssetRemoveResult(result: DeleteAssetResult): OperationError | null {\n if (result.outcome === 'error') return { code: 'unknown', message: result.message }\n if (result.outcome === 'blocked') {\n return {\n code: 'unknown',\n message: `Asset is referenced in ${result.usageCount} ${result.usageCount === 1 ? 'place' : 'places'} and was not deleted.`,\n }\n }\n return null\n}\n\n/**\n * How an asset row is addressed in the EC-155 reference index (EC-433):\n * `{ type: 'asset' }`, never the document shape — so the delete confirmation's\n * inbound pre-flight counts the SAME references `loadInboundAssetReferenceViews`\n * (the detail surface's check) does.\n */\nexport function assetReferenceTarget(ref: DocumentRef): ReferenceTarget {\n return { type: 'asset', id: ref.id }\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **assets rail** (EC-319) — the\n * portable list surface over the existing asset library store instead of a\n * bespoke listing. `schema` is the DEPLOYMENT-resolved `kind:'asset'` collection\n * (EC-311: a project's own declared fields, or a mount's `nav.assets({ fields })`\n * override merged over it — see `resolveAssetsSchema` in `app-views/default-modules.tsx`),\n * never a hardcoded engine default — so a project's `copyright`/`credit`/… fields\n * (and any EC-311 override) drive the list's columns/filters exactly as they\n * drive the image field's options dialog.\n *\n * Must be mounted under an `<AssetsProvider>` (for {@link useAssets}) — already\n * ambient at the project level (EC-195), so callers rarely mount their own.\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the asset-store\n * reads/writes, the capability profile, and the projections above.\n */\nexport function useAssetsCollectionViewSource(\n projectId: string,\n schema: CollectionDef,\n): CollectionViewSource {\n const store = useAssets()\n const registry = useMemo(() => createCollectionRegistry([schema]), [schema])\n\n const documents = useCallback(\n () => store.assets.map((record) => assetToDocument(schema, record)),\n [store.assets, schema],\n )\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = store.assets.find((a) => a.id === ref.id)\n return record ? assetToDocument(schema, record) : undefined\n },\n [store.assets, schema],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => {\n const record = store.assets.find((a) => a.id === ref.id)\n return record?.title ?? record?.filename ?? ref.id\n },\n [store.assets],\n )\n const validate = useCallback(\n (doc: CmsDocument) => validateDocument(registry, doc, { localeConfig: ASSETS_LOCALE_CONFIG }),\n [registry],\n )\n const save = useCallback(\n (doc: CmsDocument) => store.updateMetadata(doc.id, { meta: documentToAssetMetaPatch(doc) }),\n [store],\n )\n // The unconfirmed delete (the store's usage gate still applies — a referenced\n // asset comes back `blocked`, surfaced via `lastError`). The UI's delete paths\n // never land here: row/bulk deletes confirm through the reference dialog and\n // dispatch `bulkRemove` below.\n const remove = useCallback((ref: DocumentRef) => store.remove(ref.id), [store])\n // The CONFIRMED delete the bulk/row path dispatches (EC-433): the reference\n // dialog has already enumerated inbound usages (asset-shaped, see\n // `assetReferenceTarget`), so this passes `confirmInUse` exactly like the\n // detail surface's confirmed delete — same blob + thumb + cache handling.\n const bulkRemove = useCallback(\n (ref: DocumentRef) => store.remove(ref.id, { confirmInUse: true }),\n [store],\n )\n\n return useRailViewSource({\n projectId,\n schema,\n capabilities: ASSET_COLLECTION_CAPABILITIES,\n localeConfig: ASSETS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections`/`isFieldReadOnly` override: assets carry\n // no relation/asset fields of their own (v1) — nothing cross-collection to\n // resolve — and read-only follows the factory's default `form.readOnly` check.\n detailPath: (id: string) => studioPath.asset(id),\n listPath: studioPath.assets(),\n // EC-433: the delete confirmation must enumerate ASSET-shaped inbound\n // references — the document-shaped default would report \"not referenced\"\n // for a referenced asset.\n referenceTarget: assetReferenceTarget,\n validate,\n create: createUnsupported,\n save,\n remove,\n bulkRemove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeAssetRemoveResult,\n })\n}\n"]}
1
+ {"version":3,"file":"assets-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/assets/assets-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type { CmsDocument, CollectionDef, DocumentRef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport { createCollectionRegistry, validateDocument } from '@elytracms/core/cms-core'\nimport { assetAlt } from '@elytracms/core/persistence'\nimport type { AssetRecord, ReferenceTarget } from '@elytracms/core/persistence'\nimport type { OperationError } from '@elytracms/core/operations'\nimport { studioPath } from '@elytracms/studio/router'\nimport type { DeleteAssetResult } from '../../lib/assets'\nimport type { CollectionViewSource, CollectionViewCapabilities } from '../../lib/collection-view/collection-view-source'\nimport { useAssets } from './assets-store'\nimport { SYSTEM_ASSET_FIELDS, pruneMeta } from './asset-metadata'\nimport { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source'\nimport { parseLocaleConfig } from '@elytracms/core/cms-core'\n\n/** The assets rail is not localized in v1 (EC-301: asset metadata is non-localized). */\nconst ASSETS_LOCALE: Locale = 'en'\nconst ASSETS_LOCALE_CONFIG: LocaleConfig = parseLocaleConfig({\n default: ASSETS_LOCALE,\n locales: [ASSETS_LOCALE],\n})\n\n/**\n * The capability profile of the assets rail (EC-319, EC-433). Hoisted to a\n * module-level constant (no prop/state dependency) so it's both the value the\n * hook returns AND independently unit-testable.\n *\n * `create` is off — a new asset comes from an upload, never a blank draft.\n * `remove` + `bulk` are ON since EC-433: the list's row/bulk delete used to be\n * off deliberately because the generic `ReferenceConfirmDialog` enumerated\n * inbound references as `{ type: 'document' }` targets — the wrong index shape\n * for an asset — which would have shown \"not referenced\" for an asset that IS\n * referenced. That pre-flight now queries the shape THIS source declares via\n * {@link assetReferenceTarget} (`{ type: 'asset' }`, the same lookup the\n * detail surface runs), and the confirmed delete dispatches the SAME checked\n * store delete the detail surface uses (`deleteAsset`: original + EC-400 thumb\n * blobs, then the record — EC-420 lqips ride on the record). Nothing else\n * applies to assets: no publish/versions/locales/inline-edit/live-URL/\n * hierarchy/import-export.\n */\nexport const ASSET_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: false,\n duplicate: false, // EC-442: blob duplication is a different feature, out of scope\n remove: true,\n bulk: true,\n liveUrl: false,\n hierarchy: false,\n importExport: false,\n adminOnly: false,\n}\n\n/** Read one of the record's fixed, upload-managed columns by field name. */\nfunction systemFieldValue(record: AssetRecord, name: string): unknown {\n switch (name) {\n case 'filename':\n return record.filename\n case 'url':\n return record.url\n case 'contentType':\n return record.contentType\n case 'byteSize':\n return record.byteSize\n case 'width':\n return record.width\n case 'height':\n return record.height\n case 'storageKey':\n return record.storageKey\n case 'title':\n return record.title\n default:\n return undefined\n }\n}\n\n/**\n * Project a stored asset record into the lens's `values` shape (EC-319) — the\n * value-keyed projection the redirects/users rails use (EC-577, RESIDUE-3d): EVERY\n * key of the record's open `meta` bag (EC-301) rides in `values`, declared by the\n * schema or not, so a save hands the bag back whole (D22). Declared fields then\n * take their canonical reading on top — a column never loses to a same-named meta\n * key: a system column (`filename`, `url`, dimensions, …) reads straight off the\n * record, and `alt` reads through THE reader (`assetAlt`, EC-587 — `meta.alt`, with a\n * not-yet-migrated legacy column lifted in), exactly what delivery and the image\n * field's options dialog show.\n */\nexport function assetToDocument(collection: CollectionDef, record: AssetRecord): CmsDocument {\n const values: Record<string, unknown> = { ...(record.meta ?? {}) }\n for (const field of collection.fields) {\n if (field.name === 'alt') {\n const alt = assetAlt(record)\n if (alt !== null) values.alt = alt\n else delete values.alt\n } else if (SYSTEM_ASSET_FIELDS.has(field.name)) {\n values[field.name] = systemFieldValue(record, field.name)\n }\n }\n return { collection: collection.id, id: record.id, values }\n}\n\n/**\n * The inverse of {@link assetToDocument}: the meta bag a save writes back — every\n * value outside the system set (EC-301/EC-311), declared or not, through the ONE\n * value-keyed projection the users/redirects rails share (EC-577, RESIDUE-3d).\n * `meta` is written whole (it replaces the stored bag), so a key the lens never\n * declared must be in it: the schema-driven projection this replaced dropped them\n * on every rail save (D22), while the options dialog and asset detail kept them.\n * System columns are upload-managed and never written here.\n */\nexport function documentToAssetMetaPatch(doc: CmsDocument): Record<string, unknown> {\n return pruneMeta(metaBagProjection(doc.values ?? {}, SYSTEM_ASSET_FIELDS))\n}\n\n/** `create` when `capabilities.create === false` — never reached from the UI\n * (list/detail never call it); an explicit throw rather than a silent no-op. */\nfunction createUnsupported(): DocumentRef {\n throw new Error('assets are created by upload, not the assets rail')\n}\n\n/** Assets' `remove()` returns an `outcome`-discriminated {@link DeleteAssetResult}\n * (not the `{ ok }` shape users/redirects use, EC-327). `'error'` surfaces as-is;\n * `'blocked'` (the unconfirmed usage gate fired — nothing was removed) surfaces\n * as an explicit failure too (EC-433): a delete that left the asset in place\n * must NEVER look like success. It is unreachable from this rail's UI — every\n * delete path confirms through the reference dialog and dispatches with\n * `confirmInUse` — but stays honest for any direct caller. `'not-found'` maps\n * to success (idempotent delete: the desired end state — gone — holds). */\nexport function normalizeAssetRemoveResult(result: DeleteAssetResult): OperationError | null {\n if (result.outcome === 'error') return { code: 'unknown', message: result.message }\n if (result.outcome === 'blocked') {\n return {\n code: 'unknown',\n message: `Asset is referenced in ${result.usageCount} ${result.usageCount === 1 ? 'place' : 'places'} and was not deleted.`,\n }\n }\n return null\n}\n\n/**\n * How an asset row is addressed in the EC-155 reference index (EC-433):\n * `{ type: 'asset' }`, never the document shape — so the delete confirmation's\n * inbound pre-flight counts the SAME references `loadInboundAssetReferenceViews`\n * (the detail surface's check) does.\n */\nexport function assetReferenceTarget(ref: DocumentRef): ReferenceTarget {\n return { type: 'asset', id: ref.id }\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **assets rail** (EC-319) — the\n * portable list surface over the existing asset library store instead of a\n * bespoke listing. `schema` is the DEPLOYMENT-resolved `kind:'asset'` collection\n * (EC-311: a project's own declared fields, or a mount's `nav.assets({ fields })`\n * override merged over it — see `resolveAssetsSchema` in `app-views/default-modules.tsx`),\n * never a hardcoded engine default — so a project's `copyright`/`credit`/… fields\n * (and any EC-311 override) drive the list's columns/filters exactly as they\n * drive the image field's options dialog.\n *\n * Must be mounted under an `<AssetsProvider>` (for {@link useAssets}) — already\n * ambient at the project level (EC-195), so callers rarely mount their own.\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the asset-store\n * reads/writes, the capability profile, and the projections above.\n */\nexport function useAssetsCollectionViewSource(\n projectId: string,\n schema: CollectionDef,\n): CollectionViewSource {\n const store = useAssets()\n const registry = useMemo(() => createCollectionRegistry([schema]), [schema])\n\n const documents = useCallback(\n () => store.assets.map((record) => assetToDocument(schema, record)),\n [store.assets, schema],\n )\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = store.assets.find((a) => a.id === ref.id)\n return record ? assetToDocument(schema, record) : undefined\n },\n [store.assets, schema],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => {\n const record = store.assets.find((a) => a.id === ref.id)\n return record?.title ?? record?.filename ?? ref.id\n },\n [store.assets],\n )\n const validate = useCallback(\n (doc: CmsDocument) => validateDocument(registry, doc, { localeConfig: ASSETS_LOCALE_CONFIG }),\n [registry],\n )\n const save = useCallback(\n (doc: CmsDocument) => store.updateMetadata(doc.id, { meta: documentToAssetMetaPatch(doc) }),\n [store],\n )\n // The unconfirmed delete (the store's usage gate still applies — a referenced\n // asset comes back `blocked`, surfaced via `lastError`). The UI's delete paths\n // never land here: row/bulk deletes confirm through the reference dialog and\n // dispatch `bulkRemove` below.\n const remove = useCallback((ref: DocumentRef) => store.remove(ref.id), [store])\n // The CONFIRMED delete the bulk/row path dispatches (EC-433): the reference\n // dialog has already enumerated inbound usages (asset-shaped, see\n // `assetReferenceTarget`), so this passes `confirmInUse` exactly like the\n // detail surface's confirmed delete — same blob + thumb + cache handling.\n const bulkRemove = useCallback(\n (ref: DocumentRef) => store.remove(ref.id, { confirmInUse: true }),\n [store],\n )\n\n return useRailViewSource({\n projectId,\n schema,\n capabilities: ASSET_COLLECTION_CAPABILITIES,\n localeConfig: ASSETS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections`/`isFieldReadOnly` override: assets carry\n // no relation/asset fields of their own (v1) — nothing cross-collection to\n // resolve — and read-only follows the factory's default `form.readOnly` check.\n detailPath: (id: string) => studioPath.asset(id),\n listPath: studioPath.assets(),\n // EC-433: the delete confirmation must enumerate ASSET-shaped inbound\n // references — the document-shaped default would report \"not referenced\"\n // for a referenced asset.\n referenceTarget: assetReferenceTarget,\n validate,\n create: createUnsupported,\n save,\n remove,\n bulkRemove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeAssetRemoveResult,\n })\n}\n"]}
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useRef, useState } from 'react';
3
3
  import { ImageDown, LayoutGrid, List, Loader2, RotateCcw, Upload, X } from 'lucide-react';
4
4
  import { Badge, Button, Panel, PanelBody, PanelHeader, Row, SegmentedControl, Stack, Text, } from '@elytracms/studio/ui';
5
- import { translate } from '@elytracms/studio/lib/i18n';
5
+ import { translate } from '../../lib/i18n/index.js';
6
6
  import { useAssets } from './assets-store.js';
7
7
  import { useAssetsCollectionViewSource } from './assets-collection-view-source.js';
8
8
  import { CollectionViewProvider } from '../collection-view/collection-view-context.js';
@@ -1 +1 @@
1
- {"version":3,"file":"assets-module.js","sourceRoot":"","sources":["../../../src/components/assets/assets-module.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useRef, useState } from 'react'\nimport type { ChangeEvent, DragEvent } from 'react'\nimport { ImageDown, LayoutGrid, List, Loader2, RotateCcw, Upload, X } from 'lucide-react'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport {\n Badge,\n Button,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n SegmentedControl,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport type { BackfillProgress, BackfillSignal, UploadState } from '@elytracms/studio/lib/assets'\nimport { useAssets } from './assets-store'\nimport type { UploadEntry } from './assets-store'\nimport { useAssetsCollectionViewSource } from './assets-collection-view-source'\nimport { CollectionViewProvider } from '../collection-view/collection-view-context'\nimport { RailStoreStatus } from '../rail-store-status'\nimport { LoadedCollectionTable } from '../cms/collection-table'\nimport { AssetGridView } from './asset-grid'\nimport { AssetDetail } from './asset-detail'\nimport {\n type AssetsViewMode,\n readAssetsViewMode,\n storeAssetsViewMode,\n} from './assets-view-preference'\n\n/**\n * The studio Assets module (EC-319). The reuse proof (alongside users/redirects)\n * that the portable list surface serves a rail with an entirely different\n * backend (the asset library store, not `cmsDocuments`): `assetId === null` is\n * the universal list — list mode is `LoadedCollectionTable`, grid mode is\n * `AssetGridView`, a **transient personal** toggle (EC-317-style, localStorage) —\n * over the SAME {@link useAssetsCollectionViewSource}; `assetId` set opens the\n * bespoke asset detail surface (`asset-detail.tsx`).\n *\n * Must be mounted where an `<AssetsProvider>` is ambient — already true at the\n * project level (EC-195), so this never mounts its own.\n */\nexport function AssetsModule({\n projectId,\n assetId = null,\n schema,\n}: {\n projectId: string\n assetId?: string | null\n schema: CollectionDef\n}) {\n const store = useAssets()\n const source = useAssetsCollectionViewSource(projectId, schema)\n\n if (store.status !== 'ready') {\n return (\n <RailStoreStatus\n store={store}\n errorLabel={translate('assets.error.loadFailed')}\n loadingLabel={translate('assets.loading')}\n errorTestId=\"assets-error\"\n />\n )\n }\n\n return (\n <CollectionViewProvider source={source}>\n {assetId !== null ? (\n <AssetDetail key={assetId} projectId={projectId} assetId={assetId} />\n ) : (\n <AssetsListSurface projectId={projectId} />\n )}\n </CollectionViewProvider>\n )\n}\n\n/** The list surface: upload + the grid/list toggle in a shared toolbar, over\n * either the universal table or the thumbnail grid — same source, same rows. */\nfunction AssetsListSurface({ projectId }: { projectId: string }) {\n const store = useAssets()\n const [mode, setMode] = useState<AssetsViewMode>(() => readAssetsViewMode())\n const [dragging, setDragging] = useState(false)\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n\n function setModeAndPersist(next: AssetsViewMode) {\n setMode(next)\n storeAssetsViewMode(next)\n }\n\n function onPick(event: ChangeEvent<HTMLInputElement>) {\n if (event.target.files) store.upload([...event.target.files])\n event.target.value = ''\n }\n\n function onDrop(event: DragEvent) {\n event.preventDefault()\n setDragging(false)\n if (event.dataTransfer.files.length > 0) store.upload([...event.dataTransfer.files])\n }\n\n const toolbarExtra = (\n <Row gap=\"2\" align=\"center\">\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={onPick}\n data-testid=\"asset-file-input\"\n />\n <GeneratePreviewsButton />\n <Button size=\"sm\" variant=\"outline\" onClick={() => fileInputRef.current?.click()} data-testid=\"asset-upload\">\n <Upload /> {translate('common.upload')}\n </Button>\n <AssetsViewToggle mode={mode} onChange={setModeAndPersist} />\n </Row>\n )\n\n return (\n <div\n className=\"relative flex h-full min-h-0 flex-col\"\n onDragOver={(event) => {\n event.preventDefault()\n setDragging(true)\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n >\n {store.uploads.length > 0 ? <UploadQueue /> : null}\n\n {mode === 'grid' ? (\n <AssetGridView projectId={projectId} toolbarExtra={toolbarExtra} />\n ) : (\n <LoadedCollectionTable projectId={projectId} toolbarExtra={toolbarExtra} />\n )}\n\n {dragging ? (\n <div\n className=\"pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-xl border-2 border-dashed border-primary bg-background/85\"\n data-testid=\"asset-drop-overlay\"\n >\n <Row gap=\"2\" align=\"center\">\n <Upload className=\"size-5 text-primary\" />\n <Text className=\"font-medium\">{translate('cms.asset.dropToUpload')}</Text>\n </Row>\n </div>\n ) : null}\n </div>\n )\n}\n\n/** The grid/list toggle (EC-319, EC-317-style transient view pref). */\nfunction AssetsViewToggle({\n mode,\n onChange,\n}: {\n mode: AssetsViewMode\n onChange: (mode: AssetsViewMode) => void\n}) {\n return (\n <SegmentedControl\n ariaLabel={translate('assets.viewToggle.groupLabel')}\n items={[\n {\n key: 'list',\n label: <List className=\"size-3.5\" />,\n active: mode === 'list',\n onClick: () => onChange('list'),\n ariaLabel: translate('assets.viewToggle.list'),\n testId: 'assets-view-list',\n },\n {\n key: 'grid',\n label: <LayoutGrid className=\"size-3.5\" />,\n active: mode === 'grid',\n onClick: () => onChange('grid'),\n ariaLabel: translate('assets.viewToggle.grid'),\n testId: 'assets-view-grid',\n },\n ]}\n />\n )\n}\n\n/**\n * The EC-400 \"Generate previews\" maintenance action (AD-1): backfills downscaled\n * thumbnails onto existing images so their grid/picker cells stop streaming the\n * full original. Idempotent (skips assets that already have a thumb or don't need\n * one), sequential with a live count, and cancellable. The work runs through the\n * assets store → operations facade → shared cache; this component only drives it.\n */\nfunction GeneratePreviewsButton() {\n const store = useAssets()\n const [progress, setProgress] = useState<BackfillProgress | null>(null)\n const [running, setRunning] = useState(false)\n const [summary, setSummary] = useState<string | null>(null)\n const signalRef = useRef<BackfillSignal | null>(null)\n\n async function onClick() {\n if (running) return\n setSummary(null)\n const signal: BackfillSignal = { cancelled: false }\n signalRef.current = signal\n setRunning(true)\n setProgress({ total: 0, done: 0, generated: 0, skipped: 0, failed: 0 })\n try {\n const result = await store.generatePreviews({ onProgress: setProgress, signal })\n setSummary(\n result.cancelled\n ? translate('assets.generatePreviews.cancelled', { count: result.generated })\n : result.generated > 0\n ? translate('assets.generatePreviews.done', { count: result.generated })\n : translate('assets.generatePreviews.none'),\n )\n } finally {\n setRunning(false)\n setProgress(null)\n signalRef.current = null\n }\n }\n\n if (running) {\n return (\n <Row gap=\"2\" align=\"center\" data-testid=\"generate-previews-running\">\n <Text size=\"xs\" tone=\"muted\" className=\"whitespace-nowrap\">\n {translate('assets.generatePreviews.running', {\n done: progress?.done ?? 0,\n total: progress?.total ?? 0,\n })}\n </Text>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n if (signalRef.current) signalRef.current.cancelled = true\n }}\n data-testid=\"generate-previews-cancel\"\n >\n {translate('common.cancel')}\n </Button>\n </Row>\n )\n }\n\n return (\n <Row gap=\"2\" align=\"center\">\n {summary ? (\n <Text size=\"xs\" tone=\"subtle\" className=\"whitespace-nowrap\" data-testid=\"generate-previews-summary\">\n {summary}\n </Text>\n ) : null}\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={onClick}\n title={translate('assets.generatePreviews.hint')}\n data-testid=\"generate-previews\"\n >\n {running ? <Loader2 className=\"animate-spin\" /> : <ImageDown />}{' '}\n {translate('assets.generatePreviews.button')}\n </Button>\n </Row>\n )\n}\n\n/** Upload phase → the `assets.upload.phase.*` catalog key suffix (idle → queued). */\nconst PHASE_KEY: Record<UploadState['phase'], string> = {\n idle: 'queued',\n preparing: 'preparing',\n uploading: 'uploading',\n confirming: 'confirming',\n done: 'done',\n failed: 'failed',\n}\n\n/** The in-flight upload panel: progress while transferring, explicit failure + retry. */\nfunction UploadQueue() {\n const store = useAssets()\n return (\n <Panel className=\"m-4 mb-0 sm:mx-6\" data-testid=\"upload-queue\">\n <PanelHeader>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('assets.uploads.heading')}\n </Text>\n </PanelHeader>\n <PanelBody className=\"divide-y divide-border p-0\">\n {store.uploads.map((entry) => (\n <UploadRow\n key={entry.id}\n entry={entry}\n onRetry={() => store.retryUpload(entry.id)}\n onDismiss={() => store.dismissUpload(entry.id)}\n />\n ))}\n </PanelBody>\n </Panel>\n )\n}\n\nfunction UploadRow({\n entry,\n onRetry,\n onDismiss,\n}: {\n entry: UploadEntry\n onRetry: () => void\n onDismiss: () => void\n}) {\n const { state } = entry\n const failed = state.phase === 'failed'\n return (\n <Stack gap=\"1\" className=\"px-3 py-2\" data-testid=\"upload-row\">\n <Row gap=\"2\">\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 flex-1 truncate\">\n {entry.filename}\n </Text>\n <Badge variant={failed ? 'error' : state.phase === 'done' ? 'success' : 'info'}>\n {translate('assets.upload.phase.' + PHASE_KEY[state.phase])}\n {state.attempt > 1\n ? ' · ' + translate('assets.upload.attempt', { count: state.attempt })\n : ''}\n </Badge>\n {failed ? (\n <>\n {state.failure?.retryable !== false ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} data-testid=\"upload-retry\">\n <RotateCcw /> Retry\n </Button>\n ) : null}\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onDismiss}\n aria-label={translate('assets.upload.dismissLabel')}\n >\n <X />\n </Button>\n </>\n ) : null}\n </Row>\n {failed ? (\n <Text size=\"xs\" tone=\"muted\" data-testid=\"upload-error\">\n {state.failure?.stage}: {state.failure?.message}\n </Text>\n ) : (\n <ProgressBar value={state.progress} />\n )}\n </Stack>\n )\n}\n\nfunction ProgressBar({ value }: { value: number }) {\n return (\n <span className=\"block h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <span\n className=\"block h-full rounded-full bg-primary transition-[width]\"\n style={{ width: `${Math.round(value * 100)}%` }}\n />\n </span>\n )\n}\n"]}
1
+ {"version":3,"file":"assets-module.js","sourceRoot":"","sources":["../../../src/components/assets/assets-module.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useRef, useState } from 'react'\nimport type { ChangeEvent, DragEvent } from 'react'\nimport { ImageDown, LayoutGrid, List, Loader2, RotateCcw, Upload, X } from 'lucide-react'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport {\n Badge,\n Button,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n SegmentedControl,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\nimport type { BackfillProgress, BackfillSignal, UploadState } from '../../lib/assets'\nimport { useAssets } from './assets-store'\nimport type { UploadEntry } from './assets-store'\nimport { useAssetsCollectionViewSource } from './assets-collection-view-source'\nimport { CollectionViewProvider } from '../collection-view/collection-view-context'\nimport { RailStoreStatus } from '../rail-store-status'\nimport { LoadedCollectionTable } from '../cms/collection-table'\nimport { AssetGridView } from './asset-grid'\nimport { AssetDetail } from './asset-detail'\nimport {\n type AssetsViewMode,\n readAssetsViewMode,\n storeAssetsViewMode,\n} from './assets-view-preference'\n\n/**\n * The studio Assets module (EC-319). The reuse proof (alongside users/redirects)\n * that the portable list surface serves a rail with an entirely different\n * backend (the asset library store, not `cmsDocuments`): `assetId === null` is\n * the universal list — list mode is `LoadedCollectionTable`, grid mode is\n * `AssetGridView`, a **transient personal** toggle (EC-317-style, localStorage) —\n * over the SAME {@link useAssetsCollectionViewSource}; `assetId` set opens the\n * bespoke asset detail surface (`asset-detail.tsx`).\n *\n * Must be mounted where an `<AssetsProvider>` is ambient — already true at the\n * project level (EC-195), so this never mounts its own.\n */\nexport function AssetsModule({\n projectId,\n assetId = null,\n schema,\n}: {\n projectId: string\n assetId?: string | null\n schema: CollectionDef\n}) {\n const store = useAssets()\n const source = useAssetsCollectionViewSource(projectId, schema)\n\n if (store.status !== 'ready') {\n return (\n <RailStoreStatus\n store={store}\n errorLabel={translate('assets.error.loadFailed')}\n loadingLabel={translate('assets.loading')}\n errorTestId=\"assets-error\"\n />\n )\n }\n\n return (\n <CollectionViewProvider source={source}>\n {assetId !== null ? (\n <AssetDetail key={assetId} projectId={projectId} assetId={assetId} />\n ) : (\n <AssetsListSurface projectId={projectId} />\n )}\n </CollectionViewProvider>\n )\n}\n\n/** The list surface: upload + the grid/list toggle in a shared toolbar, over\n * either the universal table or the thumbnail grid — same source, same rows. */\nfunction AssetsListSurface({ projectId }: { projectId: string }) {\n const store = useAssets()\n const [mode, setMode] = useState<AssetsViewMode>(() => readAssetsViewMode())\n const [dragging, setDragging] = useState(false)\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n\n function setModeAndPersist(next: AssetsViewMode) {\n setMode(next)\n storeAssetsViewMode(next)\n }\n\n function onPick(event: ChangeEvent<HTMLInputElement>) {\n if (event.target.files) store.upload([...event.target.files])\n event.target.value = ''\n }\n\n function onDrop(event: DragEvent) {\n event.preventDefault()\n setDragging(false)\n if (event.dataTransfer.files.length > 0) store.upload([...event.dataTransfer.files])\n }\n\n const toolbarExtra = (\n <Row gap=\"2\" align=\"center\">\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={onPick}\n data-testid=\"asset-file-input\"\n />\n <GeneratePreviewsButton />\n <Button size=\"sm\" variant=\"outline\" onClick={() => fileInputRef.current?.click()} data-testid=\"asset-upload\">\n <Upload /> {translate('common.upload')}\n </Button>\n <AssetsViewToggle mode={mode} onChange={setModeAndPersist} />\n </Row>\n )\n\n return (\n <div\n className=\"relative flex h-full min-h-0 flex-col\"\n onDragOver={(event) => {\n event.preventDefault()\n setDragging(true)\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n >\n {store.uploads.length > 0 ? <UploadQueue /> : null}\n\n {mode === 'grid' ? (\n <AssetGridView projectId={projectId} toolbarExtra={toolbarExtra} />\n ) : (\n <LoadedCollectionTable projectId={projectId} toolbarExtra={toolbarExtra} />\n )}\n\n {dragging ? (\n <div\n className=\"pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-xl border-2 border-dashed border-primary bg-background/85\"\n data-testid=\"asset-drop-overlay\"\n >\n <Row gap=\"2\" align=\"center\">\n <Upload className=\"size-5 text-primary\" />\n <Text className=\"font-medium\">{translate('cms.asset.dropToUpload')}</Text>\n </Row>\n </div>\n ) : null}\n </div>\n )\n}\n\n/** The grid/list toggle (EC-319, EC-317-style transient view pref). */\nfunction AssetsViewToggle({\n mode,\n onChange,\n}: {\n mode: AssetsViewMode\n onChange: (mode: AssetsViewMode) => void\n}) {\n return (\n <SegmentedControl\n ariaLabel={translate('assets.viewToggle.groupLabel')}\n items={[\n {\n key: 'list',\n label: <List className=\"size-3.5\" />,\n active: mode === 'list',\n onClick: () => onChange('list'),\n ariaLabel: translate('assets.viewToggle.list'),\n testId: 'assets-view-list',\n },\n {\n key: 'grid',\n label: <LayoutGrid className=\"size-3.5\" />,\n active: mode === 'grid',\n onClick: () => onChange('grid'),\n ariaLabel: translate('assets.viewToggle.grid'),\n testId: 'assets-view-grid',\n },\n ]}\n />\n )\n}\n\n/**\n * The EC-400 \"Generate previews\" maintenance action (AD-1): backfills downscaled\n * thumbnails onto existing images so their grid/picker cells stop streaming the\n * full original. Idempotent (skips assets that already have a thumb or don't need\n * one), sequential with a live count, and cancellable. The work runs through the\n * assets store → operations facade → shared cache; this component only drives it.\n */\nfunction GeneratePreviewsButton() {\n const store = useAssets()\n const [progress, setProgress] = useState<BackfillProgress | null>(null)\n const [running, setRunning] = useState(false)\n const [summary, setSummary] = useState<string | null>(null)\n const signalRef = useRef<BackfillSignal | null>(null)\n\n async function onClick() {\n if (running) return\n setSummary(null)\n const signal: BackfillSignal = { cancelled: false }\n signalRef.current = signal\n setRunning(true)\n setProgress({ total: 0, done: 0, generated: 0, skipped: 0, failed: 0 })\n try {\n const result = await store.generatePreviews({ onProgress: setProgress, signal })\n setSummary(\n result.cancelled\n ? translate('assets.generatePreviews.cancelled', { count: result.generated })\n : result.generated > 0\n ? translate('assets.generatePreviews.done', { count: result.generated })\n : translate('assets.generatePreviews.none'),\n )\n } finally {\n setRunning(false)\n setProgress(null)\n signalRef.current = null\n }\n }\n\n if (running) {\n return (\n <Row gap=\"2\" align=\"center\" data-testid=\"generate-previews-running\">\n <Text size=\"xs\" tone=\"muted\" className=\"whitespace-nowrap\">\n {translate('assets.generatePreviews.running', {\n done: progress?.done ?? 0,\n total: progress?.total ?? 0,\n })}\n </Text>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n if (signalRef.current) signalRef.current.cancelled = true\n }}\n data-testid=\"generate-previews-cancel\"\n >\n {translate('common.cancel')}\n </Button>\n </Row>\n )\n }\n\n return (\n <Row gap=\"2\" align=\"center\">\n {summary ? (\n <Text size=\"xs\" tone=\"subtle\" className=\"whitespace-nowrap\" data-testid=\"generate-previews-summary\">\n {summary}\n </Text>\n ) : null}\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={onClick}\n title={translate('assets.generatePreviews.hint')}\n data-testid=\"generate-previews\"\n >\n {running ? <Loader2 className=\"animate-spin\" /> : <ImageDown />}{' '}\n {translate('assets.generatePreviews.button')}\n </Button>\n </Row>\n )\n}\n\n/** Upload phase → the `assets.upload.phase.*` catalog key suffix (idle → queued). */\nconst PHASE_KEY: Record<UploadState['phase'], string> = {\n idle: 'queued',\n preparing: 'preparing',\n uploading: 'uploading',\n confirming: 'confirming',\n done: 'done',\n failed: 'failed',\n}\n\n/** The in-flight upload panel: progress while transferring, explicit failure + retry. */\nfunction UploadQueue() {\n const store = useAssets()\n return (\n <Panel className=\"m-4 mb-0 sm:mx-6\" data-testid=\"upload-queue\">\n <PanelHeader>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n {translate('assets.uploads.heading')}\n </Text>\n </PanelHeader>\n <PanelBody className=\"divide-y divide-border p-0\">\n {store.uploads.map((entry) => (\n <UploadRow\n key={entry.id}\n entry={entry}\n onRetry={() => store.retryUpload(entry.id)}\n onDismiss={() => store.dismissUpload(entry.id)}\n />\n ))}\n </PanelBody>\n </Panel>\n )\n}\n\nfunction UploadRow({\n entry,\n onRetry,\n onDismiss,\n}: {\n entry: UploadEntry\n onRetry: () => void\n onDismiss: () => void\n}) {\n const { state } = entry\n const failed = state.phase === 'failed'\n return (\n <Stack gap=\"1\" className=\"px-3 py-2\" data-testid=\"upload-row\">\n <Row gap=\"2\">\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 flex-1 truncate\">\n {entry.filename}\n </Text>\n <Badge variant={failed ? 'error' : state.phase === 'done' ? 'success' : 'info'}>\n {translate('assets.upload.phase.' + PHASE_KEY[state.phase])}\n {state.attempt > 1\n ? ' · ' + translate('assets.upload.attempt', { count: state.attempt })\n : ''}\n </Badge>\n {failed ? (\n <>\n {state.failure?.retryable !== false ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} data-testid=\"upload-retry\">\n <RotateCcw /> Retry\n </Button>\n ) : null}\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onDismiss}\n aria-label={translate('assets.upload.dismissLabel')}\n >\n <X />\n </Button>\n </>\n ) : null}\n </Row>\n {failed ? (\n <Text size=\"xs\" tone=\"muted\" data-testid=\"upload-error\">\n {state.failure?.stage}: {state.failure?.message}\n </Text>\n ) : (\n <ProgressBar value={state.progress} />\n )}\n </Stack>\n )\n}\n\nfunction ProgressBar({ value }: { value: number }) {\n return (\n <span className=\"block h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <span\n className=\"block h-full rounded-full bg-primary transition-[width]\"\n style={{ width: `${Math.round(value * 100)}%` }}\n />\n </span>\n )\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { AssetRecord } from '@elytracms/core/persistence';
3
- import type { BackfillProgress, BackfillSignal, DeleteAssetResult, UploadState, UploadableFile } from '@elytracms/studio/lib/assets';
4
- import type { ReferenceEntryView } from '@elytracms/studio/lib/references';
3
+ import type { BackfillProgress, BackfillSignal, DeleteAssetResult, UploadState, UploadableFile } from '../../lib/assets/index.js';
4
+ import type { ReferenceEntryView } from '../../lib/references/index.js';
5
5
  import type { AssetMetadataPatch, UpdateAssetMetadataResult } from './asset-metadata.js';
6
6
  /**
7
7
  * Asset library store (EC-151, EC-141). Asset *metadata* mutations go through
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useContext, useMemo, useRef, useState } from 'react';
3
- import { deleteAsset, runVariantBackfill, runUpload, xhrUploadTransport } from '@elytracms/studio/lib/assets';
4
- import { loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references';
5
- import { getStudioOperations, operationsAssetRepository } from '@elytracms/studio/lib/operations';
6
- import { projectCollections } from '@elytracms/studio/lib/operations/project-runtime-config';
7
- import { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query';
8
- import { createRequiredContext } from '@elytracms/studio/lib/required-context';
3
+ import { deleteAsset, runVariantBackfill, runUpload, xhrUploadTransport } from '../../lib/assets/index.js';
4
+ import { loadInboundAssetReferenceViews } from '../../lib/references/index.js';
5
+ import { getStudioOperations, operationsAssetRepository } from '../../lib/operations/index.js';
6
+ import { projectCollections } from '../../lib/operations/project-runtime-config.js';
7
+ import { listStoreState, useOperationCache, useOperationQuery } from '../../lib/query/index.js';
8
+ import { createRequiredContext } from '../../lib/required-context.js';
9
9
  import { updateAssetMetadata } from './asset-metadata.js';
10
10
  const { Context: AssetsContext, useRequired: useAssets } = createRequiredContext('Assets', { article: 'an' });
11
11
  const EMPTY_ASSETS = [];
@@ -1 +1 @@
1
- {"version":3,"file":"assets-store.js","sourceRoot":"","sources":["../../../src/components/assets/assets-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useContext, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { AssetRecord, AssetRepository } from '@elytracms/core/persistence'\nimport { deleteAsset, runVariantBackfill, runUpload, xhrUploadTransport } from '@elytracms/studio/lib/assets'\nimport type {\n BackfillProgress,\n BackfillSignal,\n DeleteAssetResult,\n UploadState,\n UploadableFile,\n} from '@elytracms/studio/lib/assets'\nimport { loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { getStudioOperations, operationsAssetRepository } from '@elytracms/studio/lib/operations'\nimport { projectCollections } from '@elytracms/studio/lib/operations/project-runtime-config'\nimport type { OperationsClient, StudioOperations } from '@elytracms/studio/lib/operations'\nimport { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\nimport { updateAssetMetadata } from './asset-metadata'\nimport type { AssetMetadataPatch, UpdateAssetMetadataResult } from './asset-metadata'\n\n/**\n * Asset library store (EC-151, EC-141). Asset *metadata* mutations go through\n * the shared operations client (`assets.*` commands — validate → authorize →\n * mutate → event); blob *bytes* ride on `adapter.blobs`, which is transport,\n * not domain. The UI works identically against the in-memory seed and a live\n * Convex deployment.\n */\n\nexport interface UploadEntry {\n id: string\n filename: string\n state: UploadState\n}\n\nexport interface AssetsStore {\n /** The active project (e.g. for reference deep links, EC-202). */\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n assets: readonly AssetRecord[]\n uploads: readonly UploadEntry[]\n /**\n * Queue files for upload. Returns the upload-queue id of each file, in order, so a\n * caller can follow exactly ITS uploads (EC-588 A7) — see {@link landedAssetId}.\n */\n upload(files: Iterable<UploadableFile>): readonly string[]\n /**\n * The asset id an upload landed as, once its record is confirmed (EC-588 A7);\n * `undefined` while it runs, after it failed, or for an id this store never issued.\n * A completed upload leaves the queue, so this is where its outcome stays readable.\n */\n landedAssetId(uploadId: string): string | undefined\n retryUpload(uploadId: string): void\n dismissUpload(uploadId: string): void\n /**\n * Index-accurate inbound references of an asset (EC-202): the \"used by\"\n * pre-flight of the block-or-confirm delete dialog, read from the EC-155\n * reference index (not the retired string scan).\n */\n usagesOf(assetId: string): Promise<ReferenceEntryView[]>\n remove(assetId: string, options?: { confirmInUse?: boolean }): Promise<DeleteAssetResult>\n /** Edit alt/title (EC-152); persists through the operations asset facade. */\n updateMetadata(assetId: string, patch: AssetMetadataPatch): Promise<UpdateAssetMetadataResult>\n /**\n * EC-568 — replace the file inside an asset: the same id, the new bytes, everything\n * derived regenerated through THE upload path, the file's embedded metadata over\n * `meta` per key (else kept), the focal point kept.\n */\n replaceFile(assetId: string, file: UploadableFile): Promise<ReplaceFileResult>\n /**\n * Backfill downscaled previews onto existing images (EC-400 AD-1). Sequential,\n * idempotent, cancellable; each patch flows through the operations facade and\n * the shared query cache (AD-5/AD-17). Reports progress via `handlers.onProgress`\n * and stops when `handlers.signal.cancelled` flips.\n */\n generatePreviews(handlers?: {\n onProgress?: (progress: BackfillProgress) => void\n signal?: BackfillSignal\n }): Promise<BackfillProgress>\n refresh(): Promise<void>\n}\n\n/** The outcome of {@link AssetsStore.replaceFile}. */\nexport type ReplaceFileResult = { ok: true; asset: AssetRecord } | { ok: false; message: string }\n\nconst { Context: AssetsContext, useRequired: useAssets } = createRequiredContext<AssetsStore>(\n 'Assets',\n { article: 'an' },\n)\n\nconst EMPTY_ASSETS: readonly AssetRecord[] = []\n\n/** Cache key for a project's asset library (shared by the query and its patches). */\nfunction assetsQueryKey(projectId: string): readonly [string, string] {\n return ['assets', projectId]\n}\n\n/** Load a project's asset library (pure over the operations client). A list\n * failure throws so the query surfaces it as an error status. */\nasync function loadAssets(operations: OperationsClient, projectId: string): Promise<AssetRecord[]> {\n const result = await operations.assets.list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n}\n\nexport function AssetsProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const uploadFilesRef = useRef(new Map<string, UploadableFile>())\n // EC-588 A7: upload id → the asset id it landed as. Written BEFORE the entry leaves\n // the queue, so a reader never sees an upload that is neither running nor landed.\n const landedRef = useRef(new Map<string, string>())\n const uploadSeq = useRef(0)\n const [uploads, setUploads] = useState<readonly UploadEntry[]>([])\n\n // EC-568: the asset collection's fields — their `source` declarations map a file's\n // embedded IPTC facts onto `meta` on every upload and replace. Config, read once.\n const assetFields = useMemo(\n () => projectCollections(projectId).find((collection) => collection.kind === 'asset')?.fields,\n [projectId],\n )\n\n /** The shared studio operations bundle + the metadata-write facade over it. */\n const studio = useCallback(async (): Promise<\n StudioOperations & { assetRepo: AssetRepository }\n > => {\n const bundle = await getStudioOperations()\n return { ...bundle, assetRepo: operationsAssetRepository(bundle.operations) }\n }, [])\n\n // The library list, read through the one operations-backed query cache\n // (EC-308/AD-17). No `invalidateOn`: this provider is the sole writer of its\n // project's assets, so its own mutations patch the cache in place (the EC-301\n // rule — a metadata or focal-point edit must never trigger a full re-list).\n const query = useOperationQuery<AssetRecord[]>({\n queryKey: assetsQueryKey(projectId),\n loader: (operations) => loadAssets(operations, projectId),\n })\n const cache = useOperationCache()\n const refetch = query.refetch\n const assets = query.data ?? EMPTY_ASSETS\n const { status, errorMessage } = listStoreState(query)\n\n /** Re-list from the backend (the public escape hatch + post-upload reconcile). */\n const refresh = useCallback(async () => {\n await refetch()\n }, [refetch])\n\n const setUploadState = useCallback((uploadId: string, state: UploadState) => {\n setUploads((prev) => prev.map((u) => (u.id === uploadId ? { ...u, state } : u)))\n }, [])\n\n const runEntry = useCallback(\n async (uploadId: string, file: UploadableFile, previous?: UploadState) => {\n const { adapter, assetRepo } = await studio()\n const result = await runUpload(\n {\n // Bytes ride on blob storage (transport); the metadata record is\n // confirmed through the `assets.upsert` operation command.\n blobs: adapter.blobs,\n assets: assetRepo,\n ...(assetFields ? { assetFields } : {}),\n ...(typeof XMLHttpRequest !== 'undefined' ? { transport: xhrUploadTransport } : {}),\n },\n {\n projectId,\n file,\n onState: (state) => setUploadState(uploadId, state),\n },\n previous,\n )\n if (result.ok) {\n // Completed uploads leave the queue; the asset appears in the library.\n landedRef.current.set(uploadId, result.asset.id)\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n await refresh()\n }\n },\n [studio, projectId, refresh, setUploadState, assetFields],\n )\n\n const upload = useCallback(\n (files: Iterable<UploadableFile>): readonly string[] => {\n const ids: string[] = []\n for (const file of files) {\n uploadSeq.current += 1\n const uploadId = `upload-${uploadSeq.current}`\n ids.push(uploadId)\n uploadFilesRef.current.set(uploadId, file)\n setUploads((prev) => [\n ...prev,\n { id: uploadId, filename: file.name, state: { phase: 'idle', progress: 0, attempt: 0 } },\n ])\n void runEntry(uploadId, file)\n }\n return ids\n },\n [runEntry],\n )\n\n const landedAssetId = useCallback((uploadId: string) => landedRef.current.get(uploadId), [])\n\n const retryUpload = useCallback(\n (uploadId: string) => {\n const file = uploadFilesRef.current.get(uploadId)\n const entry = uploads.find((u) => u.id === uploadId)\n if (!file || !entry || entry.state.phase !== 'failed') return\n void runEntry(uploadId, file, entry.state)\n },\n [runEntry, uploads],\n )\n\n const dismissUpload = useCallback((uploadId: string) => {\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n }, [])\n\n const usagesOf = useCallback(\n // Index-accurate (EC-202): inbound references from the EC-155 index via the\n // operations queries — the same machinery the References panel uses.\n async (assetId: string) => loadInboundAssetReferenceViews(projectId, assetId),\n [projectId],\n )\n\n const remove = useCallback(\n async (assetId: string, options?: { confirmInUse?: boolean }) => {\n const { adapter, assetRepo } = await studio()\n // Metadata removal goes through the `assets.remove` command (via the\n // facade); blob byte deletion stays on the adapter; the usage gate reads\n // the EC-155 reference index (`adapter.references`, EC-202).\n const result = await deleteAsset(\n { assets: assetRepo, blobs: adapter.blobs, references: adapter.references },\n projectId,\n assetId,\n options,\n )\n // Drop the one removed record from the cache rather than re-listing the whole\n // library (EC-301 perf, mirrors updateMetadata) — a delete must not trigger a full\n // `assets.list` round-trip on top of the deletion itself.\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).filter((a) => a.id !== assetId),\n )\n }\n return result\n },\n [studio, projectId, cache],\n )\n\n const updateMetadata = useCallback(\n async (assetId: string, patch: AssetMetadataPatch) => {\n // Metadata writes go through the `assets.upsert` command via the\n // operations facade (EC-141) — never straight at a repository.\n const { assetRepo } = await studio()\n const result = await updateAssetMetadata({ assets: assetRepo }, projectId, assetId, patch)\n if (result.ok) {\n // Patch the one updated record into the cache rather than re-listing the\n // whole library (EC-301). A focal-point or alt edit must not trigger a full\n // `assets.list` round-trip — that re-list (plus the marker only moving once\n // it returned) was the focal-point picker's lag and query storm.\n const updated = result.asset\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).map((a) => (a.id === assetId ? updated : a)),\n )\n }\n return result\n },\n [studio, projectId, cache],\n )\n\n const replaceFile = useCallback(\n async (assetId: string, file: UploadableFile): Promise<ReplaceFileResult> => {\n const { adapter, assetRepo } = await studio()\n // EC-568: THE upload path, in replace mode — not a second uploader.\n const result = await runUpload(\n {\n blobs: adapter.blobs,\n assets: assetRepo,\n ...(assetFields ? { assetFields } : {}),\n ...(typeof XMLHttpRequest !== 'undefined' ? { transport: xhrUploadTransport } : {}),\n },\n { projectId, file, replace: { assetId } },\n )\n if (!result.ok) return { ok: false, message: result.failure.message }\n const updated = result.asset\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).map((a) => (a.id === assetId ? updated : a)),\n )\n return { ok: true, asset: updated }\n },\n [studio, projectId, cache, assetFields],\n )\n\n const generatePreviews = useCallback(\n async (handlers?: {\n onProgress?: (progress: BackfillProgress) => void\n signal?: BackfillSignal\n }) => {\n const { adapter, assetRepo } = await studio()\n // Reads (list) + the variant patch (upsert) go through the operations facade;\n // bytes ride on `adapter.blobs`. The backfill is per-variant (EC-420 AD-4):\n // one fetch of each original fills whichever of thumb/lqip is missing. Each\n // generated record is patched into the shared cache in place (EC-301 perf,\n // AD-17) so the grid updates live without a full re-list.\n return runVariantBackfill(\n {\n assets: assetRepo,\n blobs: adapter.blobs,\n onGenerated: (updated) =>\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).map((a) => (a.id === updated.id ? updated : a)),\n ),\n ...(handlers?.onProgress ? { onProgress: handlers.onProgress } : {}),\n ...(handlers?.signal ? { signal: handlers.signal } : {}),\n },\n projectId,\n )\n },\n [studio, projectId, cache],\n )\n\n const store = useMemo<AssetsStore>(\n () => ({\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n assets,\n uploads,\n upload,\n landedAssetId,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n replaceFile,\n generatePreviews,\n refresh,\n }),\n [\n projectId,\n status,\n errorMessage,\n assets,\n uploads,\n upload,\n landedAssetId,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n replaceFile,\n generatePreviews,\n refresh,\n ],\n )\n\n return <AssetsContext.Provider value={store}>{children}</AssetsContext.Provider>\n}\n\nexport { useAssets }\n\n/**\n * The assets store when an `<AssetsProvider>` is mounted above, else `null`.\n * Lets surfaces outside the Assets section (the CMS asset picker, EC-152)\n * reuse an ambient provider or mount their own.\n */\nexport function useOptionalAssets(): AssetsStore | null {\n return useContext(AssetsContext)\n}\n"]}
1
+ {"version":3,"file":"assets-store.js","sourceRoot":"","sources":["../../../src/components/assets/assets-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useContext, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { AssetRecord, AssetRepository } from '@elytracms/core/persistence'\nimport { deleteAsset, runVariantBackfill, runUpload, xhrUploadTransport } from '../../lib/assets'\nimport type {\n BackfillProgress,\n BackfillSignal,\n DeleteAssetResult,\n UploadState,\n UploadableFile,\n} from '../../lib/assets'\nimport { loadInboundAssetReferenceViews } from '../../lib/references'\nimport type { ReferenceEntryView } from '../../lib/references'\nimport { getStudioOperations, operationsAssetRepository } from '../../lib/operations'\nimport { projectCollections } from '../../lib/operations/project-runtime-config'\nimport type { OperationsClient, StudioOperations } from '../../lib/operations'\nimport { listStoreState, useOperationCache, useOperationQuery } from '../../lib/query'\nimport { createRequiredContext } from '../../lib/required-context'\nimport { updateAssetMetadata } from './asset-metadata'\nimport type { AssetMetadataPatch, UpdateAssetMetadataResult } from './asset-metadata'\n\n/**\n * Asset library store (EC-151, EC-141). Asset *metadata* mutations go through\n * the shared operations client (`assets.*` commands — validate → authorize →\n * mutate → event); blob *bytes* ride on `adapter.blobs`, which is transport,\n * not domain. The UI works identically against the in-memory seed and a live\n * Convex deployment.\n */\n\nexport interface UploadEntry {\n id: string\n filename: string\n state: UploadState\n}\n\nexport interface AssetsStore {\n /** The active project (e.g. for reference deep links, EC-202). */\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n assets: readonly AssetRecord[]\n uploads: readonly UploadEntry[]\n /**\n * Queue files for upload. Returns the upload-queue id of each file, in order, so a\n * caller can follow exactly ITS uploads (EC-588 A7) — see {@link landedAssetId}.\n */\n upload(files: Iterable<UploadableFile>): readonly string[]\n /**\n * The asset id an upload landed as, once its record is confirmed (EC-588 A7);\n * `undefined` while it runs, after it failed, or for an id this store never issued.\n * A completed upload leaves the queue, so this is where its outcome stays readable.\n */\n landedAssetId(uploadId: string): string | undefined\n retryUpload(uploadId: string): void\n dismissUpload(uploadId: string): void\n /**\n * Index-accurate inbound references of an asset (EC-202): the \"used by\"\n * pre-flight of the block-or-confirm delete dialog, read from the EC-155\n * reference index (not the retired string scan).\n */\n usagesOf(assetId: string): Promise<ReferenceEntryView[]>\n remove(assetId: string, options?: { confirmInUse?: boolean }): Promise<DeleteAssetResult>\n /** Edit alt/title (EC-152); persists through the operations asset facade. */\n updateMetadata(assetId: string, patch: AssetMetadataPatch): Promise<UpdateAssetMetadataResult>\n /**\n * EC-568 — replace the file inside an asset: the same id, the new bytes, everything\n * derived regenerated through THE upload path, the file's embedded metadata over\n * `meta` per key (else kept), the focal point kept.\n */\n replaceFile(assetId: string, file: UploadableFile): Promise<ReplaceFileResult>\n /**\n * Backfill downscaled previews onto existing images (EC-400 AD-1). Sequential,\n * idempotent, cancellable; each patch flows through the operations facade and\n * the shared query cache (AD-5/AD-17). Reports progress via `handlers.onProgress`\n * and stops when `handlers.signal.cancelled` flips.\n */\n generatePreviews(handlers?: {\n onProgress?: (progress: BackfillProgress) => void\n signal?: BackfillSignal\n }): Promise<BackfillProgress>\n refresh(): Promise<void>\n}\n\n/** The outcome of {@link AssetsStore.replaceFile}. */\nexport type ReplaceFileResult = { ok: true; asset: AssetRecord } | { ok: false; message: string }\n\nconst { Context: AssetsContext, useRequired: useAssets } = createRequiredContext<AssetsStore>(\n 'Assets',\n { article: 'an' },\n)\n\nconst EMPTY_ASSETS: readonly AssetRecord[] = []\n\n/** Cache key for a project's asset library (shared by the query and its patches). */\nfunction assetsQueryKey(projectId: string): readonly [string, string] {\n return ['assets', projectId]\n}\n\n/** Load a project's asset library (pure over the operations client). A list\n * failure throws so the query surfaces it as an error status. */\nasync function loadAssets(operations: OperationsClient, projectId: string): Promise<AssetRecord[]> {\n const result = await operations.assets.list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n}\n\nexport function AssetsProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const uploadFilesRef = useRef(new Map<string, UploadableFile>())\n // EC-588 A7: upload id → the asset id it landed as. Written BEFORE the entry leaves\n // the queue, so a reader never sees an upload that is neither running nor landed.\n const landedRef = useRef(new Map<string, string>())\n const uploadSeq = useRef(0)\n const [uploads, setUploads] = useState<readonly UploadEntry[]>([])\n\n // EC-568: the asset collection's fields — their `source` declarations map a file's\n // embedded IPTC facts onto `meta` on every upload and replace. Config, read once.\n const assetFields = useMemo(\n () => projectCollections(projectId).find((collection) => collection.kind === 'asset')?.fields,\n [projectId],\n )\n\n /** The shared studio operations bundle + the metadata-write facade over it. */\n const studio = useCallback(async (): Promise<\n StudioOperations & { assetRepo: AssetRepository }\n > => {\n const bundle = await getStudioOperations()\n return { ...bundle, assetRepo: operationsAssetRepository(bundle.operations) }\n }, [])\n\n // The library list, read through the one operations-backed query cache\n // (EC-308/AD-17). No `invalidateOn`: this provider is the sole writer of its\n // project's assets, so its own mutations patch the cache in place (the EC-301\n // rule — a metadata or focal-point edit must never trigger a full re-list).\n const query = useOperationQuery<AssetRecord[]>({\n queryKey: assetsQueryKey(projectId),\n loader: (operations) => loadAssets(operations, projectId),\n })\n const cache = useOperationCache()\n const refetch = query.refetch\n const assets = query.data ?? EMPTY_ASSETS\n const { status, errorMessage } = listStoreState(query)\n\n /** Re-list from the backend (the public escape hatch + post-upload reconcile). */\n const refresh = useCallback(async () => {\n await refetch()\n }, [refetch])\n\n const setUploadState = useCallback((uploadId: string, state: UploadState) => {\n setUploads((prev) => prev.map((u) => (u.id === uploadId ? { ...u, state } : u)))\n }, [])\n\n const runEntry = useCallback(\n async (uploadId: string, file: UploadableFile, previous?: UploadState) => {\n const { adapter, assetRepo } = await studio()\n const result = await runUpload(\n {\n // Bytes ride on blob storage (transport); the metadata record is\n // confirmed through the `assets.upsert` operation command.\n blobs: adapter.blobs,\n assets: assetRepo,\n ...(assetFields ? { assetFields } : {}),\n ...(typeof XMLHttpRequest !== 'undefined' ? { transport: xhrUploadTransport } : {}),\n },\n {\n projectId,\n file,\n onState: (state) => setUploadState(uploadId, state),\n },\n previous,\n )\n if (result.ok) {\n // Completed uploads leave the queue; the asset appears in the library.\n landedRef.current.set(uploadId, result.asset.id)\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n await refresh()\n }\n },\n [studio, projectId, refresh, setUploadState, assetFields],\n )\n\n const upload = useCallback(\n (files: Iterable<UploadableFile>): readonly string[] => {\n const ids: string[] = []\n for (const file of files) {\n uploadSeq.current += 1\n const uploadId = `upload-${uploadSeq.current}`\n ids.push(uploadId)\n uploadFilesRef.current.set(uploadId, file)\n setUploads((prev) => [\n ...prev,\n { id: uploadId, filename: file.name, state: { phase: 'idle', progress: 0, attempt: 0 } },\n ])\n void runEntry(uploadId, file)\n }\n return ids\n },\n [runEntry],\n )\n\n const landedAssetId = useCallback((uploadId: string) => landedRef.current.get(uploadId), [])\n\n const retryUpload = useCallback(\n (uploadId: string) => {\n const file = uploadFilesRef.current.get(uploadId)\n const entry = uploads.find((u) => u.id === uploadId)\n if (!file || !entry || entry.state.phase !== 'failed') return\n void runEntry(uploadId, file, entry.state)\n },\n [runEntry, uploads],\n )\n\n const dismissUpload = useCallback((uploadId: string) => {\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n }, [])\n\n const usagesOf = useCallback(\n // Index-accurate (EC-202): inbound references from the EC-155 index via the\n // operations queries — the same machinery the References panel uses.\n async (assetId: string) => loadInboundAssetReferenceViews(projectId, assetId),\n [projectId],\n )\n\n const remove = useCallback(\n async (assetId: string, options?: { confirmInUse?: boolean }) => {\n const { adapter, assetRepo } = await studio()\n // Metadata removal goes through the `assets.remove` command (via the\n // facade); blob byte deletion stays on the adapter; the usage gate reads\n // the EC-155 reference index (`adapter.references`, EC-202).\n const result = await deleteAsset(\n { assets: assetRepo, blobs: adapter.blobs, references: adapter.references },\n projectId,\n assetId,\n options,\n )\n // Drop the one removed record from the cache rather than re-listing the whole\n // library (EC-301 perf, mirrors updateMetadata) — a delete must not trigger a full\n // `assets.list` round-trip on top of the deletion itself.\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).filter((a) => a.id !== assetId),\n )\n }\n return result\n },\n [studio, projectId, cache],\n )\n\n const updateMetadata = useCallback(\n async (assetId: string, patch: AssetMetadataPatch) => {\n // Metadata writes go through the `assets.upsert` command via the\n // operations facade (EC-141) — never straight at a repository.\n const { assetRepo } = await studio()\n const result = await updateAssetMetadata({ assets: assetRepo }, projectId, assetId, patch)\n if (result.ok) {\n // Patch the one updated record into the cache rather than re-listing the\n // whole library (EC-301). A focal-point or alt edit must not trigger a full\n // `assets.list` round-trip — that re-list (plus the marker only moving once\n // it returned) was the focal-point picker's lag and query storm.\n const updated = result.asset\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).map((a) => (a.id === assetId ? updated : a)),\n )\n }\n return result\n },\n [studio, projectId, cache],\n )\n\n const replaceFile = useCallback(\n async (assetId: string, file: UploadableFile): Promise<ReplaceFileResult> => {\n const { adapter, assetRepo } = await studio()\n // EC-568: THE upload path, in replace mode — not a second uploader.\n const result = await runUpload(\n {\n blobs: adapter.blobs,\n assets: assetRepo,\n ...(assetFields ? { assetFields } : {}),\n ...(typeof XMLHttpRequest !== 'undefined' ? { transport: xhrUploadTransport } : {}),\n },\n { projectId, file, replace: { assetId } },\n )\n if (!result.ok) return { ok: false, message: result.failure.message }\n const updated = result.asset\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).map((a) => (a.id === assetId ? updated : a)),\n )\n return { ok: true, asset: updated }\n },\n [studio, projectId, cache, assetFields],\n )\n\n const generatePreviews = useCallback(\n async (handlers?: {\n onProgress?: (progress: BackfillProgress) => void\n signal?: BackfillSignal\n }) => {\n const { adapter, assetRepo } = await studio()\n // Reads (list) + the variant patch (upsert) go through the operations facade;\n // bytes ride on `adapter.blobs`. The backfill is per-variant (EC-420 AD-4):\n // one fetch of each original fills whichever of thumb/lqip is missing. Each\n // generated record is patched into the shared cache in place (EC-301 perf,\n // AD-17) so the grid updates live without a full re-list.\n return runVariantBackfill(\n {\n assets: assetRepo,\n blobs: adapter.blobs,\n onGenerated: (updated) =>\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).map((a) => (a.id === updated.id ? updated : a)),\n ),\n ...(handlers?.onProgress ? { onProgress: handlers.onProgress } : {}),\n ...(handlers?.signal ? { signal: handlers.signal } : {}),\n },\n projectId,\n )\n },\n [studio, projectId, cache],\n )\n\n const store = useMemo<AssetsStore>(\n () => ({\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n assets,\n uploads,\n upload,\n landedAssetId,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n replaceFile,\n generatePreviews,\n refresh,\n }),\n [\n projectId,\n status,\n errorMessage,\n assets,\n uploads,\n upload,\n landedAssetId,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n replaceFile,\n generatePreviews,\n refresh,\n ],\n )\n\n return <AssetsContext.Provider value={store}>{children}</AssetsContext.Provider>\n}\n\nexport { useAssets }\n\n/**\n * The assets store when an `<AssetsProvider>` is mounted above, else `null`.\n * Lets surfaces outside the Assets section (the CMS asset picker, EC-152)\n * reuse an ambient provider or mount their own.\n */\nexport function useOptionalAssets(): AssetsStore | null {\n return useContext(AssetsContext)\n}\n"]}
@@ -6,7 +6,7 @@
6
6
  * instead of a new persistence mechanism. Unavailable storage (SSR, privacy
7
7
  * mode) degrades to the default every session — never a crash.
8
8
  */
9
- import { createEnumPreference } from '@elytracms/studio/lib/local-preference';
9
+ import { createEnumPreference } from '../../lib/local-preference.js';
10
10
  export const ASSETS_VIEW_STORAGE_KEY = 'elytra-assets-view-mode';
11
11
  export const DEFAULT_ASSETS_VIEW_MODE = 'list';
12
12
  const preference = createEnumPreference(ASSETS_VIEW_STORAGE_KEY, ['list', 'grid'], DEFAULT_ASSETS_VIEW_MODE);
@@ -1 +1 @@
1
- {"version":3,"file":"assets-view-preference.js","sourceRoot":"","sources":["../../../src/components/assets/assets-view-preference.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAA;AAI7E,MAAM,CAAC,MAAM,uBAAuB,GAAG,yBAAyB,CAAA;AAChE,MAAM,CAAC,MAAM,wBAAwB,GAAmB,MAAM,CAAA;AAE9D,MAAM,UAAU,GAAG,oBAAoB,CACrC,uBAAuB,EACvB,CAAC,MAAM,EAAE,MAAM,CAAC,EAChB,wBAAwB,CACzB,CAAA;AAED,oFAAoF;AACpF,MAAM,UAAU,kBAAkB;IAChC,OAAO,UAAU,CAAC,IAAI,EAAE,CAAA;AAC1B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,mBAAmB,CAAC,IAAoB;IACtD,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;AACxB,CAAC","sourcesContent":["/**\n * Assets list/grid view-mode preference (EC-319). The same class of transient,\n * personal-local UI preference as the version-compare view mode (EC-317,\n * `lib/diff-view-preference.ts`), the theme, and rail-collapse: it never touches\n * the project graph or any document, so it rides `localStorage` best-effort\n * instead of a new persistence mechanism. Unavailable storage (SSR, privacy\n * mode) degrades to the default every session — never a crash.\n */\nimport { createEnumPreference } from '@elytracms/studio/lib/local-preference'\n\nexport type AssetsViewMode = 'list' | 'grid'\n\nexport const ASSETS_VIEW_STORAGE_KEY = 'elytra-assets-view-mode'\nexport const DEFAULT_ASSETS_VIEW_MODE: AssetsViewMode = 'list'\n\nconst preference = createEnumPreference<AssetsViewMode>(\n ASSETS_VIEW_STORAGE_KEY,\n ['list', 'grid'],\n DEFAULT_ASSETS_VIEW_MODE,\n)\n\n/** Read the persisted preference, or the default when unset/unavailable/invalid. */\nexport function readAssetsViewMode(): AssetsViewMode {\n return preference.read()\n}\n\n/** Persist the preference (best-effort; storage may be unavailable). */\nexport function storeAssetsViewMode(mode: AssetsViewMode): void {\n preference.store(mode)\n}\n"]}
1
+ {"version":3,"file":"assets-view-preference.js","sourceRoot":"","sources":["../../../src/components/assets/assets-view-preference.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAIjE,MAAM,CAAC,MAAM,uBAAuB,GAAG,yBAAyB,CAAA;AAChE,MAAM,CAAC,MAAM,wBAAwB,GAAmB,MAAM,CAAA;AAE9D,MAAM,UAAU,GAAG,oBAAoB,CACrC,uBAAuB,EACvB,CAAC,MAAM,EAAE,MAAM,CAAC,EAChB,wBAAwB,CACzB,CAAA;AAED,oFAAoF;AACpF,MAAM,UAAU,kBAAkB;IAChC,OAAO,UAAU,CAAC,IAAI,EAAE,CAAA;AAC1B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,mBAAmB,CAAC,IAAoB;IACtD,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;AACxB,CAAC","sourcesContent":["/**\n * Assets list/grid view-mode preference (EC-319). The same class of transient,\n * personal-local UI preference as the version-compare view mode (EC-317,\n * `lib/diff-view-preference.ts`), the theme, and rail-collapse: it never touches\n * the project graph or any document, so it rides `localStorage` best-effort\n * instead of a new persistence mechanism. Unavailable storage (SSR, privacy\n * mode) degrades to the default every session — never a crash.\n */\nimport { createEnumPreference } from '../../lib/local-preference'\n\nexport type AssetsViewMode = 'list' | 'grid'\n\nexport const ASSETS_VIEW_STORAGE_KEY = 'elytra-assets-view-mode'\nexport const DEFAULT_ASSETS_VIEW_MODE: AssetsViewMode = 'list'\n\nconst preference = createEnumPreference<AssetsViewMode>(\n ASSETS_VIEW_STORAGE_KEY,\n ['list', 'grid'],\n DEFAULT_ASSETS_VIEW_MODE,\n)\n\n/** Read the persisted preference, or the default when unset/unavailable/invalid. */\nexport function readAssetsViewMode(): AssetsViewMode {\n return preference.read()\n}\n\n/** Persist the preference (best-effort; storage may be unavailable). */\nexport function storeAssetsViewMode(mode: AssetsViewMode): void {\n preference.store(mode)\n}\n"]}
@@ -3,7 +3,7 @@ import { RefreshCw } from 'lucide-react';
3
3
  import { useRef, useState } from 'react';
4
4
  import { assetAcceptAttr } from '@elytracms/core/cms-core';
5
5
  import { Button, Text } from '@elytracms/studio/ui';
6
- import { translate } from '@elytracms/studio/lib/i18n';
6
+ import { translate } from '../../lib/i18n/index.js';
7
7
  import { splitByAccept } from '../cms/asset-picker-core.js';
8
8
  import { useOptionalAssets } from './assets-store.js';
9
9
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"replace-file.js","sourceRoot":"","sources":["../../../src/components/assets/replace-file.tsx"],"names":[],"mappings":"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","sourcesContent":["import { RefreshCw } from 'lucide-react'\nimport { useRef, useState } from 'react'\nimport { assetAcceptAttr } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Button, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { splitByAccept } from '../cms/asset-picker-core'\nimport { useOptionalAssets } from './assets-store'\n\n/**\n * What a file REPLACE accepts (EC-568): a file of the same kind as the current one — an\n * image stays an image (every field that holds it accepted an image), a video a video.\n * Any other kind is unrestricted. Pure, for the test.\n */\nexport function replaceAccept(record: Pick<AssetRecord, 'contentType'>): readonly string[] | undefined {\n const kind = record.contentType.split('/')[0]\n return kind === 'image' || kind === 'video' || kind === 'audio' ? [kind] : undefined\n}\n\n/**\n * \"Replace file\" (EC-568 A): new bytes inside the SAME asset — every placement that\n * points at it shows the new file. Runs THE upload path with `replace` (the store's\n * `replaceFile`), so the derived data is regenerated and the file's embedded metadata\n * overrides `meta` per key. Used by the asset library's detail surface and the image\n * options dialog. `beforeReplace` lets a caller flush an edit buffer first, so a stale\n * buffer can never overwrite what the new file brought.\n */\nexport function ReplaceFileButton({\n record,\n disabled,\n disabledHint,\n beforeReplace,\n onReplaced,\n}: {\n record: AssetRecord\n disabled?: boolean\n disabledHint?: string\n beforeReplace?: () => Promise<void>\n onReplaced?: (asset: AssetRecord) => void\n}) {\n const assets = useOptionalAssets()\n const input = useRef<HTMLInputElement>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const accept = replaceAccept(record)\n if (!assets) return null\n\n async function replace(files: File[]) {\n if (!assets || files.length === 0) return\n const { accepted, rejected } = splitByAccept(files.slice(0, 1), accept)\n const next = accepted[0]\n if (!next) {\n setError(translate('cms.asset.replaceWrongKind', { name: rejected[0] ?? '', types: (accept ?? []).join(', ') }))\n return\n }\n setBusy(true)\n setError(null)\n await beforeReplace?.()\n const result = await assets.replaceFile(record.id, next)\n setBusy(false)\n if (result.ok) onReplaced?.(result.asset)\n else setError(result.message)\n }\n\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={disabled || busy}\n title={disabled ? disabledHint : undefined}\n onClick={() => input.current?.click()}\n data-testid=\"asset-replace-file\"\n >\n <RefreshCw /> {busy ? translate('cms.asset.replacing') : translate('cms.asset.replaceFile')}\n </Button>\n <input\n ref={input}\n type=\"file\"\n className=\"hidden\"\n {...(accept ? { accept: assetAcceptAttr(accept) } : {})}\n onChange={(e) => {\n const files = Array.from(e.target.files ?? [])\n e.target.value = ''\n void replace(files)\n }}\n />\n {error ? (\n <Text size=\"xs\" className=\"text-destructive\" role=\"alert\">\n {error}\n </Text>\n ) : null}\n </>\n )\n}\n"]}
1
+ {"version":3,"file":"replace-file.js","sourceRoot":"","sources":["../../../src/components/assets/replace-file.tsx"],"names":[],"mappings":"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","sourcesContent":["import { RefreshCw } from 'lucide-react'\nimport { useRef, useState } from 'react'\nimport { assetAcceptAttr } from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Button, Text } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\nimport { splitByAccept } from '../cms/asset-picker-core'\nimport { useOptionalAssets } from './assets-store'\n\n/**\n * What a file REPLACE accepts (EC-568): a file of the same kind as the current one — an\n * image stays an image (every field that holds it accepted an image), a video a video.\n * Any other kind is unrestricted. Pure, for the test.\n */\nexport function replaceAccept(record: Pick<AssetRecord, 'contentType'>): readonly string[] | undefined {\n const kind = record.contentType.split('/')[0]\n return kind === 'image' || kind === 'video' || kind === 'audio' ? [kind] : undefined\n}\n\n/**\n * \"Replace file\" (EC-568 A): new bytes inside the SAME asset — every placement that\n * points at it shows the new file. Runs THE upload path with `replace` (the store's\n * `replaceFile`), so the derived data is regenerated and the file's embedded metadata\n * overrides `meta` per key. Used by the asset library's detail surface and the image\n * options dialog. `beforeReplace` lets a caller flush an edit buffer first, so a stale\n * buffer can never overwrite what the new file brought.\n */\nexport function ReplaceFileButton({\n record,\n disabled,\n disabledHint,\n beforeReplace,\n onReplaced,\n}: {\n record: AssetRecord\n disabled?: boolean\n disabledHint?: string\n beforeReplace?: () => Promise<void>\n onReplaced?: (asset: AssetRecord) => void\n}) {\n const assets = useOptionalAssets()\n const input = useRef<HTMLInputElement>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const accept = replaceAccept(record)\n if (!assets) return null\n\n async function replace(files: File[]) {\n if (!assets || files.length === 0) return\n const { accepted, rejected } = splitByAccept(files.slice(0, 1), accept)\n const next = accepted[0]\n if (!next) {\n setError(translate('cms.asset.replaceWrongKind', { name: rejected[0] ?? '', types: (accept ?? []).join(', ') }))\n return\n }\n setBusy(true)\n setError(null)\n await beforeReplace?.()\n const result = await assets.replaceFile(record.id, next)\n setBusy(false)\n if (result.ok) onReplaced?.(result.asset)\n else setError(result.message)\n }\n\n return (\n <>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={disabled || busy}\n title={disabled ? disabledHint : undefined}\n onClick={() => input.current?.click()}\n data-testid=\"asset-replace-file\"\n >\n <RefreshCw /> {busy ? translate('cms.asset.replacing') : translate('cms.asset.replaceFile')}\n </Button>\n <input\n ref={input}\n type=\"file\"\n className=\"hidden\"\n {...(accept ? { accept: assetAcceptAttr(accept) } : {})}\n onChange={(e) => {\n const files = Array.from(e.target.files ?? [])\n e.target.value = ''\n void replace(files)\n }}\n />\n {error ? (\n <Text size=\"xs\" className=\"text-destructive\" role=\"alert\">\n {error}\n </Text>\n ) : null}\n </>\n )\n}\n"]}