@elytracms/studio 0.0.91 → 0.0.93

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 (353) hide show
  1. package/convex/asset-alt-migration.test.ts +127 -0
  2. package/convex/asset-replace.test.ts +168 -0
  3. package/convex/asset-revalidate.test.ts +6 -6
  4. package/convex/assets.ts +41 -5
  5. package/convex/cms.ts +537 -177
  6. package/convex/contract-parity.test.ts +173 -19
  7. package/convex/delivery.ts +26 -13
  8. package/convex/document-revalidate.test.ts +17 -16
  9. package/convex/orphan-hydration.test.ts +116 -0
  10. package/convex/presence-cost.test.ts +89 -0
  11. package/convex/rail-write-parity.test.ts +17 -14
  12. package/convex/referenceMaintenance.ts +15 -15
  13. package/convex/schema.ts +70 -5
  14. package/convex/triggers.ts +21 -8
  15. package/convex/write-limits.ts +1 -1
  16. package/dist/components/ai/ai-chat-panel.js +33 -22
  17. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  18. package/dist/components/ai/proposal-card.js +2 -1
  19. package/dist/components/ai/proposal-card.js.map +1 -1
  20. package/dist/components/ai/translate-document-action.d.ts +8 -2
  21. package/dist/components/ai/translate-document-action.js.map +1 -1
  22. package/dist/components/ai/translate-document-dialog.d.ts +1 -1
  23. package/dist/components/ai/translate-document-dialog.js +14 -8
  24. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  25. package/dist/components/ai/translate-skipped.d.ts +19 -0
  26. package/dist/components/ai/translate-skipped.js +46 -0
  27. package/dist/components/ai/translate-skipped.js.map +1 -0
  28. package/dist/components/app-views/default-modules.d.ts +23 -6
  29. package/dist/components/app-views/default-modules.js +33 -14
  30. package/dist/components/app-views/default-modules.js.map +1 -1
  31. package/dist/components/assets/asset-detail.js +18 -8
  32. package/dist/components/assets/asset-detail.js.map +1 -1
  33. package/dist/components/assets/asset-metadata.d.ts +15 -4
  34. package/dist/components/assets/asset-metadata.js +15 -2
  35. package/dist/components/assets/asset-metadata.js.map +1 -1
  36. package/dist/components/assets/asset-search.js +4 -1
  37. package/dist/components/assets/asset-search.js.map +1 -1
  38. package/dist/components/assets/asset-selection.d.ts +0 -8
  39. package/dist/components/assets/asset-selection.js +4 -23
  40. package/dist/components/assets/asset-selection.js.map +1 -1
  41. package/dist/components/assets/asset-thumb.js +3 -2
  42. package/dist/components/assets/asset-thumb.js.map +1 -1
  43. package/dist/components/assets/assets-collection-view-source.d.ts +3 -3
  44. package/dist/components/assets/assets-collection-view-source.js +9 -5
  45. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  46. package/dist/components/assets/assets-store.d.ts +14 -0
  47. package/dist/components/assets/assets-store.js +23 -1
  48. package/dist/components/assets/assets-store.js.map +1 -1
  49. package/dist/components/assets/replace-file.d.ts +22 -0
  50. package/dist/components/assets/replace-file.js +59 -0
  51. package/dist/components/assets/replace-file.js.map +1 -0
  52. package/dist/components/builder/blocks-model.d.ts +14 -26
  53. package/dist/components/builder/blocks-model.js +35 -41
  54. package/dist/components/builder/blocks-model.js.map +1 -1
  55. package/dist/components/builder/blocks-panel.d.ts +7 -3
  56. package/dist/components/builder/blocks-panel.js +6 -5
  57. package/dist/components/builder/blocks-panel.js.map +1 -1
  58. package/dist/components/builder/builder-store.d.ts +126 -30
  59. package/dist/components/builder/builder-store.js +169 -67
  60. package/dist/components/builder/builder-store.js.map +1 -1
  61. package/dist/components/builder/canvas-body.d.ts +30 -0
  62. package/dist/components/builder/canvas-body.js +38 -0
  63. package/dist/components/builder/canvas-body.js.map +1 -0
  64. package/dist/components/builder/canvas-chrome-css.js +7 -0
  65. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  66. package/dist/components/builder/canvas-dnd.d.ts +7 -7
  67. package/dist/components/builder/canvas-dnd.js +5 -5
  68. package/dist/components/builder/canvas-dnd.js.map +1 -1
  69. package/dist/components/builder/canvas-render-context.d.ts +30 -22
  70. package/dist/components/builder/canvas-render-context.js +44 -44
  71. package/dist/components/builder/canvas-render-context.js.map +1 -1
  72. package/dist/components/builder/canvas-runs.d.ts +45 -0
  73. package/dist/components/builder/canvas-runs.js +51 -0
  74. package/dist/components/builder/canvas-runs.js.map +1 -0
  75. package/dist/components/builder/canvas.d.ts +5 -4
  76. package/dist/components/builder/canvas.js +34 -6
  77. package/dist/components/builder/canvas.js.map +1 -1
  78. package/dist/components/builder/clipboard-actions.d.ts +15 -0
  79. package/dist/components/builder/clipboard-actions.js +50 -13
  80. package/dist/components/builder/clipboard-actions.js.map +1 -1
  81. package/dist/components/builder/composition-field-editor.d.ts +10 -16
  82. package/dist/components/builder/composition-field-editor.js +30 -40
  83. package/dist/components/builder/composition-field-editor.js.map +1 -1
  84. package/dist/components/builder/composition-patches.d.ts +7 -0
  85. package/dist/components/builder/composition-patches.js +256 -0
  86. package/dist/components/builder/composition-patches.js.map +1 -0
  87. package/dist/components/builder/delete-key.d.ts +5 -0
  88. package/dist/components/builder/delete-key.js +4 -0
  89. package/dist/components/builder/delete-key.js.map +1 -1
  90. package/dist/components/builder/insert-context.d.ts +17 -25
  91. package/dist/components/builder/insert-context.js +45 -92
  92. package/dist/components/builder/insert-context.js.map +1 -1
  93. package/dist/components/builder/page-tree.d.ts +3 -3
  94. package/dist/components/builder/page-tree.js +3 -3
  95. package/dist/components/builder/page-tree.js.map +1 -1
  96. package/dist/components/builder/placement-refusal.d.ts +18 -0
  97. package/dist/components/builder/placement-refusal.js +46 -0
  98. package/dist/components/builder/placement-refusal.js.map +1 -0
  99. package/dist/components/builder/property-inspector.js +4 -0
  100. package/dist/components/builder/property-inspector.js.map +1 -1
  101. package/dist/components/builder/reuse-context.js +65 -26
  102. package/dist/components/builder/reuse-context.js.map +1 -1
  103. package/dist/components/builder/reuse.d.ts +79 -8
  104. package/dist/components/builder/reuse.js +71 -23
  105. package/dist/components/builder/reuse.js.map +1 -1
  106. package/dist/components/builder/run-inspector.d.ts +11 -0
  107. package/dist/components/builder/run-inspector.js +33 -0
  108. package/dist/components/builder/run-inspector.js.map +1 -0
  109. package/dist/components/cms/asset-field.d.ts +38 -1
  110. package/dist/components/cms/asset-field.js +182 -58
  111. package/dist/components/cms/asset-field.js.map +1 -1
  112. package/dist/components/cms/asset-picker-core.d.ts +16 -0
  113. package/dist/components/cms/asset-picker-core.js +20 -0
  114. package/dist/components/cms/asset-picker-core.js.map +1 -1
  115. package/dist/components/cms/asset-picker.js +12 -18
  116. package/dist/components/cms/asset-picker.js.map +1 -1
  117. package/dist/components/cms/bulk-actions.d.ts +4 -20
  118. package/dist/components/cms/bulk-actions.js +3 -24
  119. package/dist/components/cms/bulk-actions.js.map +1 -1
  120. package/dist/components/cms/cell-edit.d.ts +5 -5
  121. package/dist/components/cms/cell-edit.js.map +1 -1
  122. package/dist/components/cms/cell.js.map +1 -1
  123. package/dist/components/cms/cms-store.d.ts +87 -45
  124. package/dist/components/cms/cms-store.js +347 -203
  125. package/dist/components/cms/cms-store.js.map +1 -1
  126. package/dist/components/cms/collection-table.js +7 -2
  127. package/dist/components/cms/collection-table.js.map +1 -1
  128. package/dist/components/cms/commit-policy.d.ts +24 -0
  129. package/dist/components/cms/commit-policy.js +37 -0
  130. package/dist/components/cms/commit-policy.js.map +1 -0
  131. package/dist/components/cms/detail-editor.js +83 -137
  132. package/dist/components/cms/detail-editor.js.map +1 -1
  133. package/dist/components/cms/document-diff-view.d.ts +3 -1
  134. package/dist/components/cms/document-diff-view.js +12 -7
  135. package/dist/components/cms/document-diff-view.js.map +1 -1
  136. package/dist/components/cms/draft-commit.d.ts +70 -0
  137. package/dist/components/cms/draft-commit.js +82 -0
  138. package/dist/components/cms/draft-commit.js.map +1 -0
  139. package/dist/components/cms/duplicate-document.d.ts +11 -13
  140. package/dist/components/cms/duplicate-document.js +26 -34
  141. package/dist/components/cms/duplicate-document.js.map +1 -1
  142. package/dist/components/cms/field-anchors.d.ts +7 -4
  143. package/dist/components/cms/field-anchors.js +9 -7
  144. package/dist/components/cms/field-anchors.js.map +1 -1
  145. package/dist/components/cms/field-collaboration.d.ts +47 -0
  146. package/dist/components/cms/field-collaboration.js +87 -0
  147. package/dist/components/cms/field-collaboration.js.map +1 -0
  148. package/dist/components/cms/field-control-lib.d.ts +22 -1
  149. package/dist/components/cms/field-control-lib.js +33 -4
  150. package/dist/components/cms/field-control-lib.js.map +1 -1
  151. package/dist/components/cms/field-control.js +17 -14
  152. package/dist/components/cms/field-control.js.map +1 -1
  153. package/dist/components/cms/gate-validation.d.ts +21 -30
  154. package/dist/components/cms/gate-validation.js +11 -22
  155. package/dist/components/cms/gate-validation.js.map +1 -1
  156. package/dist/components/cms/object-array-field.js +4 -4
  157. package/dist/components/cms/object-array-field.js.map +1 -1
  158. package/dist/components/cms/prop-fields.d.ts +7 -2
  159. package/dist/components/cms/prop-fields.js +13 -6
  160. package/dist/components/cms/prop-fields.js.map +1 -1
  161. package/dist/components/cms/reference-confirm.js +10 -4
  162. package/dist/components/cms/reference-confirm.js.map +1 -1
  163. package/dist/components/cms/rich-text/embed-picker.d.ts +1 -1
  164. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  165. package/dist/components/cms/rich-text/insert.d.ts +20 -9
  166. package/dist/components/cms/rich-text/insert.js +25 -17
  167. package/dist/components/cms/rich-text/insert.js.map +1 -1
  168. package/dist/components/cms/use-document-draft.d.ts +96 -0
  169. package/dist/components/cms/use-document-draft.js +283 -0
  170. package/dist/components/cms/use-document-draft.js.map +1 -0
  171. package/dist/components/cms/use-document-versions.js +2 -2
  172. package/dist/components/cms/use-document-versions.js.map +1 -1
  173. package/dist/components/cms/use-live-url.d.ts +6 -12
  174. package/dist/components/cms/use-live-url.js +16 -15
  175. package/dist/components/cms/use-live-url.js.map +1 -1
  176. package/dist/components/cms/use-publish-controller.d.ts +13 -18
  177. package/dist/components/cms/use-publish-controller.js +24 -41
  178. package/dist/components/cms/use-publish-controller.js.map +1 -1
  179. package/dist/components/cms/use-published-perspective.d.ts +2 -3
  180. package/dist/components/cms/use-published-perspective.js +4 -3
  181. package/dist/components/cms/use-published-perspective.js.map +1 -1
  182. package/dist/components/cms/versions-pane.js +8 -1
  183. package/dist/components/cms/versions-pane.js.map +1 -1
  184. package/dist/components/collection-view/cms-collection-view-source.js +24 -7
  185. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  186. package/dist/components/components/transfer-dialog.js +28 -23
  187. package/dist/components/components/transfer-dialog.js.map +1 -1
  188. package/dist/components/components/transfer.d.ts +31 -21
  189. package/dist/components/components/transfer.js +49 -35
  190. package/dist/components/components/transfer.js.map +1 -1
  191. package/dist/components/project-list.js.map +1 -1
  192. package/dist/components/project-session.d.ts +6 -3
  193. package/dist/components/project-session.js +14 -8
  194. package/dist/components/project-session.js.map +1 -1
  195. package/dist/components/project-switcher.js.map +1 -1
  196. package/dist/components/rail-view-source.js +2 -1
  197. package/dist/components/rail-view-source.js.map +1 -1
  198. package/dist/components/section-nav.js +1 -1
  199. package/dist/components/section-nav.js.map +1 -1
  200. package/dist/lib/ai/apply-content.js +56 -116
  201. package/dist/lib/ai/apply-content.js.map +1 -1
  202. package/dist/lib/ai/chat-context.d.ts +8 -0
  203. package/dist/lib/ai/chat-context.js +37 -6
  204. package/dist/lib/ai/chat-context.js.map +1 -1
  205. package/dist/lib/ai/chat-state.d.ts +14 -1
  206. package/dist/lib/ai/chat-state.js.map +1 -1
  207. package/dist/lib/ai/constraints.d.ts +20 -5
  208. package/dist/lib/ai/constraints.js +125 -33
  209. package/dist/lib/ai/constraints.js.map +1 -1
  210. package/dist/lib/ai/fake-provider.d.ts +16 -3
  211. package/dist/lib/ai/fake-provider.js +22 -6
  212. package/dist/lib/ai/fake-provider.js.map +1 -1
  213. package/dist/lib/ai/index.d.ts +2 -0
  214. package/dist/lib/ai/index.js +2 -0
  215. package/dist/lib/ai/index.js.map +1 -1
  216. package/dist/lib/ai/leash.d.ts +34 -0
  217. package/dist/lib/ai/leash.js +28 -0
  218. package/dist/lib/ai/leash.js.map +1 -0
  219. package/dist/lib/ai/provenance.d.ts +1 -1
  220. package/dist/lib/ai/provenance.js.map +1 -1
  221. package/dist/lib/ai/rich-text-input.d.ts +8 -8
  222. package/dist/lib/ai/rich-text-input.js +2 -1
  223. package/dist/lib/ai/rich-text-input.js.map +1 -1
  224. package/dist/lib/ai/rich-text-translate.d.ts +27 -4
  225. package/dist/lib/ai/rich-text-translate.js +28 -10
  226. package/dist/lib/ai/rich-text-translate.js.map +1 -1
  227. package/dist/lib/ai/shape.d.ts +13 -13
  228. package/dist/lib/ai/shape.js +69 -93
  229. package/dist/lib/ai/shape.js.map +1 -1
  230. package/dist/lib/ai/tools.d.ts +36 -41
  231. package/dist/lib/ai/tools.js +85 -87
  232. package/dist/lib/ai/tools.js.map +1 -1
  233. package/dist/lib/ai/translate-document.d.ts +39 -5
  234. package/dist/lib/ai/translate-document.js +160 -51
  235. package/dist/lib/ai/translate-document.js.map +1 -1
  236. package/dist/lib/ai/types.d.ts +34 -23
  237. package/dist/lib/ai/types.js.map +1 -1
  238. package/dist/lib/ai/update-fields.d.ts +21 -12
  239. package/dist/lib/ai/update-fields.js +118 -177
  240. package/dist/lib/ai/update-fields.js.map +1 -1
  241. package/dist/lib/ai/writer-form.d.ts +116 -0
  242. package/dist/lib/ai/writer-form.js +429 -0
  243. package/dist/lib/ai/writer-form.js.map +1 -0
  244. package/dist/lib/ai-current-document.d.ts +11 -11
  245. package/dist/lib/ai-current-document.js +4 -4
  246. package/dist/lib/ai-current-document.js.map +1 -1
  247. package/dist/lib/assets/upload.d.ts +31 -2
  248. package/dist/lib/assets/upload.js +61 -6
  249. package/dist/lib/assets/upload.js.map +1 -1
  250. package/dist/lib/clipboard/os-clipboard.d.ts +10 -2
  251. package/dist/lib/clipboard/os-clipboard.js +8 -4
  252. package/dist/lib/clipboard/os-clipboard.js.map +1 -1
  253. package/dist/lib/clipboard/paste-target.d.ts +18 -9
  254. package/dist/lib/clipboard/paste-target.js +8 -7
  255. package/dist/lib/clipboard/paste-target.js.map +1 -1
  256. package/dist/lib/cms/field-patch.d.ts +15 -0
  257. package/dist/lib/cms/field-patch.js +25 -0
  258. package/dist/lib/cms/field-patch.js.map +1 -0
  259. package/dist/lib/cms/published-documents.d.ts +1 -1
  260. package/dist/lib/cms/published-documents.js +1 -1
  261. package/dist/lib/cms/published-documents.js.map +1 -1
  262. package/dist/lib/cms-views/view-model.d.ts +3 -3
  263. package/dist/lib/collection-view/__fixtures__/document-writes.d.ts +6 -0
  264. package/dist/lib/collection-view/__fixtures__/document-writes.js +23 -0
  265. package/dist/lib/collection-view/__fixtures__/document-writes.js.map +1 -0
  266. package/dist/lib/collection-view/collection-view-source.d.ts +69 -6
  267. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  268. package/dist/lib/data/collection-content.d.ts +4 -13
  269. package/dist/lib/data/collection-content.js +11 -5
  270. package/dist/lib/data/collection-content.js.map +1 -1
  271. package/dist/lib/dnd/drop-rules.d.ts +25 -31
  272. package/dist/lib/dnd/drop-rules.js +53 -59
  273. package/dist/lib/dnd/drop-rules.js.map +1 -1
  274. package/dist/lib/engine-version.d.ts +1 -1
  275. package/dist/lib/engine-version.js +1 -1
  276. package/dist/lib/engine-version.js.map +1 -1
  277. package/dist/lib/i18n/messages.de.js +72 -15
  278. package/dist/lib/i18n/messages.de.js.map +1 -1
  279. package/dist/lib/i18n/messages.en.d.ts +61 -12
  280. package/dist/lib/i18n/messages.en.js +76 -16
  281. package/dist/lib/i18n/messages.en.js.map +1 -1
  282. package/dist/lib/operations/__fixtures__/write-document.d.ts +30 -0
  283. package/dist/lib/operations/__fixtures__/write-document.js +16 -0
  284. package/dist/lib/operations/__fixtures__/write-document.js.map +1 -0
  285. package/dist/lib/operations/cms-persistence.d.ts +124 -31
  286. package/dist/lib/operations/cms-persistence.js +447 -68
  287. package/dist/lib/operations/cms-persistence.js.map +1 -1
  288. package/dist/lib/operations/config-manifest-lookup.d.ts +35 -0
  289. package/dist/lib/operations/config-manifest-lookup.js +56 -0
  290. package/dist/lib/operations/config-manifest-lookup.js.map +1 -0
  291. package/dist/lib/operations/project-runtime-config.d.ts +39 -0
  292. package/dist/lib/operations/project-runtime-config.js +54 -0
  293. package/dist/lib/operations/project-runtime-config.js.map +1 -0
  294. package/dist/lib/operations/publish-with-revision.d.ts +64 -0
  295. package/dist/lib/operations/publish-with-revision.js +66 -0
  296. package/dist/lib/operations/publish-with-revision.js.map +1 -0
  297. package/dist/lib/operations/runtime-validator.d.ts +32 -0
  298. package/dist/lib/operations/runtime-validator.js +18 -0
  299. package/dist/lib/operations/runtime-validator.js.map +1 -0
  300. package/dist/lib/operations/session-hydration.d.ts +31 -14
  301. package/dist/lib/operations/session-hydration.js +12 -16
  302. package/dist/lib/operations/session-hydration.js.map +1 -1
  303. package/dist/lib/operations/studio-operations.d.ts +59 -2
  304. package/dist/lib/operations/studio-operations.js +76 -14
  305. package/dist/lib/operations/studio-operations.js.map +1 -1
  306. package/dist/lib/persistence/convex-adapter.d.ts +13 -5
  307. package/dist/lib/persistence/convex-adapter.js +189 -74
  308. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  309. package/dist/lib/persistence/convex-client.d.ts +65 -5
  310. package/dist/lib/persistence/convex-client.js +106 -3
  311. package/dist/lib/persistence/convex-client.js.map +1 -1
  312. package/dist/lib/persistence/index.js +17 -18
  313. package/dist/lib/persistence/index.js.map +1 -1
  314. package/dist/lib/persona/labels.js +4 -1
  315. package/dist/lib/persona/labels.js.map +1 -1
  316. package/dist/lib/query/operations-query.d.ts +1 -1
  317. package/dist/lib/query/operations-query.js +1 -1
  318. package/dist/lib/query/operations-query.js.map +1 -1
  319. package/dist/lib/references/impact.d.ts +21 -15
  320. package/dist/lib/references/impact.js +13 -26
  321. package/dist/lib/references/impact.js.map +1 -1
  322. package/dist/lib/references/references-store.d.ts +9 -0
  323. package/dist/lib/references/references-store.js +24 -4
  324. package/dist/lib/references/references-store.js.map +1 -1
  325. package/dist/lib/routes/route-issues.d.ts +6 -6
  326. package/dist/lib/routes/route-issues.js +26 -80
  327. package/dist/lib/routes/route-issues.js.map +1 -1
  328. package/dist/lib/workspace/config-issues.js +2 -1
  329. package/dist/lib/workspace/config-issues.js.map +1 -1
  330. package/dist/lib/workspace/index.d.ts +31 -1
  331. package/dist/lib/workspace/index.js +64 -17
  332. package/dist/lib/workspace/index.js.map +1 -1
  333. package/dist/lib/workspace/switcher-source.d.ts +3 -3
  334. package/dist/lib/workspace/switcher-source.js.map +1 -1
  335. package/dist/lib/workspace/workspace-source.d.ts +2 -2
  336. package/dist/lib/workspace/workspace-source.js +2 -1
  337. package/dist/lib/workspace/workspace-source.js.map +1 -1
  338. package/package.json +3 -3
  339. package/dist/components/cms/cms-composition-validators.d.ts +0 -16
  340. package/dist/components/cms/cms-composition-validators.js +0 -133
  341. package/dist/components/cms/cms-composition-validators.js.map +0 -1
  342. package/dist/components/cms/publish-clean.d.ts +0 -28
  343. package/dist/components/cms/publish-clean.js +0 -38
  344. package/dist/components/cms/publish-clean.js.map +0 -1
  345. package/dist/components/cms/use-save-mode.d.ts +0 -35
  346. package/dist/components/cms/use-save-mode.js +0 -54
  347. package/dist/components/cms/use-save-mode.js.map +0 -1
  348. package/dist/lib/assets/asset-placements.d.ts +0 -28
  349. package/dist/lib/assets/asset-placements.js +0 -83
  350. package/dist/lib/assets/asset-placements.js.map +0 -1
  351. package/dist/lib/cms/document-gate.d.ts +0 -47
  352. package/dist/lib/cms/document-gate.js +0 -61
  353. package/dist/lib/cms/document-gate.js.map +0 -1
@@ -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 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 * 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\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 /** 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 ...(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],\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 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 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 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 '@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"]}
@@ -0,0 +1,22 @@
1
+ import type { AssetRecord } from '@elytracms/core/persistence';
2
+ /**
3
+ * What a file REPLACE accepts (EC-568): a file of the same kind as the current one — an
4
+ * image stays an image (every field that holds it accepted an image), a video a video.
5
+ * Any other kind is unrestricted. Pure, for the test.
6
+ */
7
+ export declare function replaceAccept(record: Pick<AssetRecord, 'contentType'>): readonly string[] | undefined;
8
+ /**
9
+ * "Replace file" (EC-568 A): new bytes inside the SAME asset — every placement that
10
+ * points at it shows the new file. Runs THE upload path with `replace` (the store's
11
+ * `replaceFile`), so the derived data is regenerated and the file's embedded metadata
12
+ * overrides `meta` per key. Used by the asset library's detail surface and the image
13
+ * options dialog. `beforeReplace` lets a caller flush an edit buffer first, so a stale
14
+ * buffer can never overwrite what the new file brought.
15
+ */
16
+ export declare function ReplaceFileButton({ record, disabled, disabledHint, beforeReplace, onReplaced, }: {
17
+ record: AssetRecord;
18
+ disabled?: boolean;
19
+ disabledHint?: string;
20
+ beforeReplace?: () => Promise<void>;
21
+ onReplaced?: (asset: AssetRecord) => void;
22
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { RefreshCw } from 'lucide-react';
3
+ import { useRef, useState } from 'react';
4
+ import { assetAcceptAttr } from '@elytracms/core/cms-core';
5
+ import { Button, Text } from '@elytracms/studio/ui';
6
+ import { translate } from '@elytracms/studio/lib/i18n';
7
+ import { splitByAccept } from '../cms/asset-picker-core.js';
8
+ import { useOptionalAssets } from './assets-store.js';
9
+ /**
10
+ * What a file REPLACE accepts (EC-568): a file of the same kind as the current one — an
11
+ * image stays an image (every field that holds it accepted an image), a video a video.
12
+ * Any other kind is unrestricted. Pure, for the test.
13
+ */
14
+ export function replaceAccept(record) {
15
+ const kind = record.contentType.split('/')[0];
16
+ return kind === 'image' || kind === 'video' || kind === 'audio' ? [kind] : undefined;
17
+ }
18
+ /**
19
+ * "Replace file" (EC-568 A): new bytes inside the SAME asset — every placement that
20
+ * points at it shows the new file. Runs THE upload path with `replace` (the store's
21
+ * `replaceFile`), so the derived data is regenerated and the file's embedded metadata
22
+ * overrides `meta` per key. Used by the asset library's detail surface and the image
23
+ * options dialog. `beforeReplace` lets a caller flush an edit buffer first, so a stale
24
+ * buffer can never overwrite what the new file brought.
25
+ */
26
+ export function ReplaceFileButton({ record, disabled, disabledHint, beforeReplace, onReplaced, }) {
27
+ const assets = useOptionalAssets();
28
+ const input = useRef(null);
29
+ const [busy, setBusy] = useState(false);
30
+ const [error, setError] = useState(null);
31
+ const accept = replaceAccept(record);
32
+ if (!assets)
33
+ return null;
34
+ async function replace(files) {
35
+ if (!assets || files.length === 0)
36
+ return;
37
+ const { accepted, rejected } = splitByAccept(files.slice(0, 1), accept);
38
+ const next = accepted[0];
39
+ if (!next) {
40
+ setError(translate('cms.asset.replaceWrongKind', { name: rejected[0] ?? '', types: (accept ?? []).join(', ') }));
41
+ return;
42
+ }
43
+ setBusy(true);
44
+ setError(null);
45
+ await beforeReplace?.();
46
+ const result = await assets.replaceFile(record.id, next);
47
+ setBusy(false);
48
+ if (result.ok)
49
+ onReplaced?.(result.asset);
50
+ else
51
+ setError(result.message);
52
+ }
53
+ return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", disabled: disabled || busy, title: disabled ? disabledHint : undefined, onClick: () => input.current?.click(), "data-testid": "asset-replace-file", children: [_jsx(RefreshCw, {}), " ", busy ? translate('cms.asset.replacing') : translate('cms.asset.replaceFile')] }), _jsx("input", { ref: input, type: "file", className: "hidden", ...(accept ? { accept: assetAcceptAttr(accept) } : {}), onChange: (e) => {
54
+ const files = Array.from(e.target.files ?? []);
55
+ e.target.value = '';
56
+ void replace(files);
57
+ } }), error ? (_jsx(Text, { size: "xs", className: "text-destructive", role: "alert", children: error })) : null] }));
58
+ }
59
+ //# sourceMappingURL=replace-file.js.map
@@ -0,0 +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,4 +1,4 @@
1
- import type { ComponentNode } from '@elytracms/core/project-graph';
1
+ import type { AllowList, ComponentNode } from '@elytracms/core/project-graph';
2
2
  import type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry';
3
3
  import type { StudioRegistry } from '@elytracms/studio/lib/registry';
4
4
  /**
@@ -90,22 +90,12 @@ export interface BlockGroup {
90
90
  export interface BlockFilter {
91
91
  /** Case-insensitive match over id, title, description, category, and fixture names. */
92
92
  query?: string;
93
- /** When set, only these component ids may be inserted (slot.allow). */
94
- slotAllow?: string[];
93
+ /**
94
+ * The insertion position's effective vocabulary (EC-584 — `allowAt`: the field's at
95
+ * the composition root, the slot's below). Omitted → any component.
96
+ */
97
+ allow?: AllowList;
95
98
  }
96
- /**
97
- * Intersect two component-id allow-lists. `undefined` means "no constraint from
98
- * this source", so the result is the other list (copied); two present lists yield
99
- * their set intersection, preserving the first list's order. This layers a
100
- * composition field's top-level `allow` vocabulary over a slot's positional
101
- * `allow` at the canvas root (EC-188): a component must satisfy BOTH leashes to be
102
- * offered. A present list is a closed set (EC-544): the intersection of two
103
- * disjoint lists is `[]`, which every reader treats as "allow nothing" — there is
104
- * no coalescing hatch anywhere, and a *declared* `[]` is rejected at the
105
- * declaration site (config resolve / the `empty-allow` registry issue), so an
106
- * empty result here is always computed, never authored.
107
- */
108
- export declare function intersectAllow(slotAllow: readonly string[] | undefined, fieldAllow: readonly string[] | undefined): string[] | undefined;
109
99
  /**
110
100
  * Build the configured subtree a fixture describes: the fixture's props (EC-591/2f:
111
101
  * never the manifest defaults — an unset prop stays absent), plus one bare child
@@ -143,16 +133,14 @@ export declare function tileMonogram(title: string): string;
143
133
  */
144
134
  export declare function blockGroups(studio: StudioRegistry, filter?: BlockFilter, generatedPreview?: GeneratedPreviewLookup): BlockGroup[];
145
135
  /**
146
- * Whether ANY component could be inserted under a slot allow-list (EC-405) — the
147
- * cheap existence check behind the canvas insertion affordances: a "+" only shows
148
- * at a position where the blocks panel would offer at least one tile. Mirrors the
149
- * exact filter {@link blockGroups} applies (canvas-eligible AND, when an allow-list
150
- * is present, on that list), short-circuiting on the first match instead of
151
- * building tiles; falls back to the layout-helper presets (which are also
152
- * insertable there). `undefined`/absent `slotAllow` means "allow any" → true when
153
- * the registry has any canvas-eligible component.
136
+ * Whether ANY component could be inserted at a position whose vocabulary is `allow`
137
+ * (EC-405) — the cheap existence check behind the canvas insertion affordances: a
138
+ * "+" only shows at a position where the blocks panel would offer at least one tile.
139
+ * The exact filter {@link blockGroups} applies (the placement judge), short-circuiting
140
+ * on the first match instead of building tiles; falls back to the layout-helper
141
+ * presets (which are also insertable there). Omitted `allow` → any component.
154
142
  */
155
- export declare function hasInsertableComponent(studio: StudioRegistry, slotAllow?: string[]): boolean;
143
+ export declare function hasInsertableComponent(studio: StudioRegistry, allow?: AllowList): boolean;
156
144
  /**
157
145
  * Layout helper presets (EC-067) as panel tiles — already one tile per preset.
158
146
  * A preset has a single implicit variant and never a preview image, so its
@@ -160,4 +148,4 @@ export declare function hasInsertableComponent(studio: StudioRegistry, slotAllow
160
148
  * supports ({@link layoutPresets} — built from the layout and text roles), never
161
149
  * hardcoded engine ids.
162
150
  */
163
- export declare function presetTiles(studio: StudioRegistry, slotAllow?: string[]): BlockTile[];
151
+ export declare function presetTiles(studio: StudioRegistry, allow?: AllowList): BlockTile[];
@@ -1,4 +1,5 @@
1
- import { isCanvasEligible, manifestSlots, propValueValidator } from '@elytracms/core/component-registry';
1
+ import { ANY_COMPONENT, placementRefusal, slotAllowList } from '@elytracms/core/project-graph';
2
+ import { manifestSlots, propValueValidator } from '@elytracms/core/component-registry';
2
3
  import { localizedStringVariants } from '@elytracms/core/cms-core';
3
4
  import { componentBlockNode } from '@elytracms/core/rich-text';
4
5
  import { localizedLabel } from '@elytracms/studio/lib/i18n';
@@ -11,24 +12,12 @@ import { layoutPresets, nodeFromManifest, seededNodeFromManifest } from './node-
11
12
  */
12
13
  export const DEFAULT_VARIANT = 'Default';
13
14
  /**
14
- * Intersect two component-id allow-lists. `undefined` means "no constraint from
15
- * this source", so the result is the other list (copied); two present lists yield
16
- * their set intersection, preserving the first list's order. This layers a
17
- * composition field's top-level `allow` vocabulary over a slot's positional
18
- * `allow` at the canvas root (EC-188): a component must satisfy BOTH leashes to be
19
- * offered. A present list is a closed set (EC-544): the intersection of two
20
- * disjoint lists is `[]`, which every reader treats as "allow nothing" — there is
21
- * no coalescing hatch anywhere, and a *declared* `[]` is rejected at the
22
- * declaration site (config resolve / the `empty-allow` registry issue), so an
23
- * empty result here is always computed, never authored.
15
+ * EC-584: may `manifest` be OFFERED at a position whose vocabulary is `allow`? The
16
+ * one core placement judge — the same call the builder reducer enforces on insert,
17
+ * so the panel never offers what the sink refuses (canvas eligibility included).
24
18
  */
25
- export function intersectAllow(slotAllow, fieldAllow) {
26
- if (!slotAllow)
27
- return fieldAllow ? [...fieldAllow] : undefined;
28
- if (!fieldAllow)
29
- return [...slotAllow];
30
- const inField = new Set(fieldAllow);
31
- return slotAllow.filter((id) => inField.has(id));
19
+ function offerable(studio, manifestId, allow) {
20
+ return placementRefusal({ componentId: manifestId, view: studio.registry.lookup.get(manifestId), allow }) === null;
32
21
  }
33
22
  const UNCATEGORIZED = 'other';
34
23
  /** A node built from a manifest's defaults with an explicit id. */
@@ -79,6 +68,10 @@ export function fixtureSubtree(studio, manifest, fixture, baseId) {
79
68
  if (!declared.has(slotName)) {
80
69
  issues.push(`Fixture "${fixture.name}" fills undeclared slot "${slotName}".`);
81
70
  }
71
+ // EC-584: the slot's vocabulary, read through the one placement judge — a fixture
72
+ // child the slot forbids (or a server-only one) is a visible variant issue here,
73
+ // and the reducer refuses the insert with the same verdict.
74
+ const slotAllow = slotAllowList(studio.registry.lookup.get(manifest.id)?.slots?.find((s) => s.name === slotName));
82
75
  slots[slotName] = childIds.map((childId, index) => {
83
76
  const id = `${baseId}-${slotName}-${index}`;
84
77
  const childManifest = studio.registry.getManifest(childId);
@@ -87,6 +80,13 @@ export function fixtureSubtree(studio, manifest, fixture, baseId) {
87
80
  // Keep the reference: the renderer shows a visible missing-component fallback.
88
81
  return componentBlockNode({ id, componentId: childId });
89
82
  }
83
+ const refusal = placementRefusal({ componentId: childId, view: studio.registry.lookup.get(childId), allow: slotAllow });
84
+ if (refusal === 'forbidden-component') {
85
+ issues.push(`Fixture "${fixture.name}" puts "${childId}" in slot "${slotName}", which the slot does not allow.`);
86
+ }
87
+ else if (refusal === 'server-only-component') {
88
+ issues.push(`Fixture "${fixture.name}" puts the server-only "${childId}" in slot "${slotName}" — it cannot be placed on the canvas.`);
89
+ }
90
90
  return defaultNode(childManifest, id);
91
91
  });
92
92
  }
@@ -180,15 +180,14 @@ function matches(manifest, query) {
180
180
  */
181
181
  export function blockGroups(studio, filter = {}, generatedPreview) {
182
182
  const query = filter.query?.trim().toLowerCase();
183
- const allow = filter.slotAllow;
183
+ const allow = filter.allow ?? ANY_COMPONENT;
184
184
  const groups = new Map();
185
185
  for (const manifest of studio.registry.list()) {
186
186
  // EC-191: the canvas is client-rendered, so only canvas-eligible
187
- // (client-renderable) components are offered for insertion; server-only / RSC
188
- // components are usable in dev frames, not placed on the canvas.
189
- if (!isCanvasEligible(manifest))
190
- continue;
191
- if (allow && !allow.includes(manifest.id))
187
+ // (client-renderable) components are offered; server-only / RSC components are
188
+ // usable in dev frames, not placed on the canvas. EC-584: eligibility and the
189
+ // position's vocabulary are ONE question to the placement judge.
190
+ if (!offerable(studio, manifest.id, allow))
192
191
  continue;
193
192
  if (query && !matches(manifest, query))
194
193
  continue;
@@ -206,24 +205,19 @@ export function blockGroups(studio, filter = {}, generatedPreview) {
206
205
  return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, tiles]) => ({ category, tiles }));
207
206
  }
208
207
  /**
209
- * Whether ANY component could be inserted under a slot allow-list (EC-405) — the
210
- * cheap existence check behind the canvas insertion affordances: a "+" only shows
211
- * at a position where the blocks panel would offer at least one tile. Mirrors the
212
- * exact filter {@link blockGroups} applies (canvas-eligible AND, when an allow-list
213
- * is present, on that list), short-circuiting on the first match instead of
214
- * building tiles; falls back to the layout-helper presets (which are also
215
- * insertable there). `undefined`/absent `slotAllow` means "allow any" → true when
216
- * the registry has any canvas-eligible component.
208
+ * Whether ANY component could be inserted at a position whose vocabulary is `allow`
209
+ * (EC-405) — the cheap existence check behind the canvas insertion affordances: a
210
+ * "+" only shows at a position where the blocks panel would offer at least one tile.
211
+ * The exact filter {@link blockGroups} applies (the placement judge), short-circuiting
212
+ * on the first match instead of building tiles; falls back to the layout-helper
213
+ * presets (which are also insertable there). Omitted `allow` → any component.
217
214
  */
218
- export function hasInsertableComponent(studio, slotAllow) {
215
+ export function hasInsertableComponent(studio, allow = ANY_COMPONENT) {
219
216
  for (const manifest of studio.registry.list()) {
220
- if (!isCanvasEligible(manifest))
221
- continue;
222
- if (slotAllow && !slotAllow.includes(manifest.id))
223
- continue;
224
- return true;
217
+ if (offerable(studio, manifest.id, allow))
218
+ return true;
225
219
  }
226
- return presetTiles(studio, slotAllow).length > 0;
220
+ return presetTiles(studio, allow).length > 0;
227
221
  }
228
222
  /**
229
223
  * Layout helper presets (EC-067) as panel tiles — already one tile per preset.
@@ -232,8 +226,8 @@ export function hasInsertableComponent(studio, slotAllow) {
232
226
  * supports ({@link layoutPresets} — built from the layout and text roles), never
233
227
  * hardcoded engine ids.
234
228
  */
235
- export function presetTiles(studio, slotAllow) {
236
- return layoutPresets(studio.registry).filter((preset) => !slotAllow || slotAllow.includes(preset.build().componentId)).map((preset) => ({
229
+ export function presetTiles(studio, allow = ANY_COMPONENT) {
230
+ return layoutPresets(studio.registry).filter((preset) => offerable(studio, preset.build().componentId, allow)).map((preset) => ({
237
231
  key: `preset::${preset.key}`,
238
232
  componentId: preset.build().componentId,
239
233
  title: preset.label,
@@ -1 +1 @@
1
- {"version":3,"file":"blocks-model.js","sourceRoot":"","sources":["../../../src/components/builder/blocks-model.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry'\nimport { isCanvasEligible, manifestSlots, propValueValidator } from '@elytracms/core/component-registry'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { componentBlockNode } from '@elytracms/core/rich-text'\nimport { localizedLabel } from '@elytracms/studio/lib/i18n'\nimport { componentStatus } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { nodeFromProps } from '@elytracms/studio/lib/registry/preview'\nimport { layoutPresets, nodeFromManifest, seededNodeFromManifest } from './node-factory'\n\n/**\n * Blocks panel model (EC-164, reshaped by EC-439) — the pure, React-free\n * derivation behind the visual insert surface. Every canvas-eligible registry\n * manifest yields **one tile** carrying the component identity plus a\n * `variants` list (one per declared fixture, or a single implicit `Default`\n * variant built from the seeded manifest defaults). Tiles group by manifest\n * `category` and filter by a free-text query over name / description /\n * category / **fixture names** — no hand-maintained panel config. Broken\n * fixtures degrade to variants carrying explicit `issues`, never to a missing\n * entry or a crash.\n *\n * Previews are ARTIFACTS, never runtime renders (EC-439 AD-2). Per tile and\n * per variant, `previewUrl` resolves: curated `manifest.previewImage` (EC-409,\n * always wins) → the {@link GeneratedPreviewLookup} seam (EC-440's build-time\n * fixture renders) → `undefined` (the panel shows an icon+name placeholder).\n */\n\n/**\n * The generated-preview seam (EC-439 AD-2, filled by EC-440): a pure lookup\n * from component id + fixture name to an image URL. The EC-440 CLI renders\n * every canvas-eligible component's fixtures headlessly against the consumer\n * theme and writes a previews manifest; the consumer's `studio.config.tsx`\n * adapts that manifest into this function via `defineStudio({ previews:\n * { generated } })`. A fixture-less manifest's single implicit variant is\n * looked up under the name {@link DEFAULT_VARIANT}. Empty/undefined everywhere\n * until EC-440 lands — the panel then falls back to placeholders.\n */\nexport type GeneratedPreviewLookup = (componentId: string, fixtureName: string) => string | undefined\n\n/**\n * The implicit variant name of a fixture-less manifest (and the fixture name\n * a {@link GeneratedPreviewLookup} is queried with for it).\n */\nexport const DEFAULT_VARIANT = 'Default'\n\n/**\n * One insertable variant of a component tile: a declared manifest fixture, or\n * the single implicit `Default` for a fixture-less manifest. `variants[0]` is\n * the tile's default — what tile click / Enter / drag inserts.\n */\nexport interface BlockVariant {\n /** The fixture name, or {@link DEFAULT_VARIANT} for the implicit variant. */\n name: string\n /**\n * Validation problems for this variant (manifest-level statuses plus the\n * fixture's own issues). A non-empty list marks the variant as degraded —\n * it still lists and still inserts.\n */\n issues: string[]\n /**\n * Resolved preview image URL: curated `manifest.previewImage` (wins) else\n * the generated-preview lookup's URL for this fixture. `undefined` → the\n * panel renders the icon+name placeholder.\n */\n previewUrl?: string\n /** Build the subtree this variant inserts (ids are reassigned on insert). */\n build: () => ComponentNode\n}\n\n/** One component (or layout preset) in the blocks panel — ONE tile per component (EC-439 AD-1). */\nexport interface BlockTile {\n /** Stable identity: the component id, or `preset::<key>` for layout helpers. */\n key: string\n /** Component id of the node the tile inserts. */\n componentId: string\n title: string\n description?: string\n /**\n * The DEFAULT variant's validation problems — what clicking the tile\n * inserts. Per-variant issues live on {@link BlockTile.variants}.\n */\n issues: string[]\n /** The default variant's resolved preview URL (see {@link BlockVariant.previewUrl}). */\n previewUrl?: string\n /** All insertable variants; `variants[0]` is the default the tile inserts. */\n variants: BlockVariant[]\n /**\n * Fixture names the active query matched (stamped by {@link blockGroups}\n * when a query is present) — the panel highlights the variant affordance and\n * the matching flyout rows so \"found by fixture name\" is visible.\n */\n matchedVariants?: string[]\n /** Build the DEFAULT subtree to insert: the first declared fixture's, else the seeded manifest default. */\n build: () => ComponentNode\n}\n\n/** Tiles of one manifest `category`. */\nexport interface BlockGroup {\n category: string\n tiles: BlockTile[]\n}\n\nexport interface BlockFilter {\n /** Case-insensitive match over id, title, description, category, and fixture names. */\n query?: string\n /** When set, only these component ids may be inserted (slot.allow). */\n slotAllow?: string[]\n}\n\n/**\n * Intersect two component-id allow-lists. `undefined` means \"no constraint from\n * this source\", so the result is the other list (copied); two present lists yield\n * their set intersection, preserving the first list's order. This layers a\n * composition field's top-level `allow` vocabulary over a slot's positional\n * `allow` at the canvas root (EC-188): a component must satisfy BOTH leashes to be\n * offered. A present list is a closed set (EC-544): the intersection of two\n * disjoint lists is `[]`, which every reader treats as \"allow nothing\" — there is\n * no coalescing hatch anywhere, and a *declared* `[]` is rejected at the\n * declaration site (config resolve / the `empty-allow` registry issue), so an\n * empty result here is always computed, never authored.\n */\nexport function intersectAllow(\n slotAllow: readonly string[] | undefined,\n fieldAllow: readonly string[] | undefined,\n): string[] | undefined {\n if (!slotAllow) return fieldAllow ? [...fieldAllow] : undefined\n if (!fieldAllow) return [...slotAllow]\n const inField = new Set(fieldAllow)\n return slotAllow.filter((id) => inField.has(id))\n}\n\nconst UNCATEGORIZED = 'other'\n\n/** A node built from a manifest's defaults with an explicit id. */\nfunction defaultNode(manifest: ComponentManifest, id: string): ComponentNode {\n return { ...nodeFromManifest(manifest), id }\n}\n\n/** Structured issues for a component that cannot render (EC-055 statuses). */\nfunction manifestIssues(studio: StudioRegistry, manifest: ComponentManifest): string[] {\n const status = componentStatus(studio, manifest)\n if (status === 'incompatible') {\n return [`No implementation is registered for \"${manifest.id}\".`]\n }\n if (status === 'invalid') {\n return studio.registry.issues.filter((issue) => issue.componentId === manifest.id).map((issue) => issue.message)\n }\n return []\n}\n\n/**\n * Build the configured subtree a fixture describes: the fixture's props (EC-591/2f:\n * never the manifest defaults — an unset prop stays absent), plus one bare child\n * node per `slotChildren` component id. Never throws — unknown components, undeclared\n * slots, and invalid prop values become entries in `issues` while the node is\n * still produced (the renderer shows its own visible fallbacks for them).\n */\nexport function fixtureSubtree(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n fixture: ComponentFixture,\n baseId: string,\n): { node: ComponentNode; issues: string[] } {\n const issues: string[] = []\n const fixtureProps = fixture.props ?? {}\n\n for (const [name, value] of Object.entries(fixtureProps)) {\n const spec = manifest.props[name]\n if (!spec) {\n issues.push(`Fixture \"${fixture.name}\" sets unknown prop \"${name}\".`)\n continue\n }\n // EC-190: validate the fixture value with the prop's field-def validator.\n if (!(propValueValidator(manifest, name)?.(value) ?? true)) {\n issues.push(`Fixture \"${fixture.name}\" sets an invalid value for prop \"${name}\".`)\n }\n }\n\n // EC-547: fixture-built inserts are v2 block-sequence citizens like every\n // other factory node — the root carries the `elytra/component` discriminator.\n const node = componentBlockNode(nodeFromProps(manifest, fixtureProps, baseId))\n\n if (fixture.slotChildren) {\n // EC-386: blocks-prop regions are declared slots too (`manifestSlots`), so a\n // fixture may seed a Section-style body without a false \"undeclared slot\" issue.\n const declared = new Set(manifestSlots(manifest).map((slot) => slot.name))\n const slots: NonNullable<ComponentNode['slots']> = {}\n for (const [slotName, childIds] of Object.entries(fixture.slotChildren)) {\n if (!declared.has(slotName)) {\n issues.push(`Fixture \"${fixture.name}\" fills undeclared slot \"${slotName}\".`)\n }\n slots[slotName] = childIds.map((childId, index) => {\n const id = `${baseId}-${slotName}-${index}`\n const childManifest = studio.registry.getManifest(childId)\n if (!childManifest) {\n issues.push(`Fixture \"${fixture.name}\" references unknown component \"${childId}\" in slot \"${slotName}\".`)\n // Keep the reference: the renderer shows a visible missing-component fallback.\n return componentBlockNode({ id, componentId: childId })\n }\n return defaultNode(childManifest, id)\n })\n }\n if (Object.keys(slots).length > 0) node.slots = slots\n }\n\n return { node, issues }\n}\n\n/**\n * The ONE tile for a manifest (EC-439 AD-1): component identity plus one\n * variant per declared fixture — or a single implicit `Default` variant using\n * the seeded manifest default (a bare layout container is seeded so it\n * survives EC-161 normalization instead of vanishing on commit, EC-383). The\n * tile's own `issues`/`previewUrl`/`build` mirror the default variant\n * (`variants[0]`), so tile click/Enter/drag inserts exactly what the first\n * fixture tile inserted before the reshape.\n */\nexport function tileForManifest(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n generatedPreview?: GeneratedPreviewLookup,\n): BlockTile {\n const title = localizedLabel(manifest.title, manifest.id)\n const baseIssues = manifestIssues(studio, manifest)\n // EC-409: a curated preview image previews the COMPONENT, not one fixture —\n // when declared it wins on the tile and on every variant row (AD-2 tier 1).\n const curated = manifest.previewImage\n\n const variants: BlockVariant[] =\n manifest.fixtures && manifest.fixtures.length > 0\n ? manifest.fixtures.map((fixture, index) => {\n const { issues } = fixtureSubtree(studio, manifest, fixture, `probe-${manifest.id}-${index}`)\n return {\n name: fixture.name,\n issues: [...baseIssues, ...issues],\n ...previewUrlEntry(curated ?? generatedPreview?.(manifest.id, fixture.name)),\n build: () => fixtureSubtree(studio, manifest, fixture, 'tmp').node,\n }\n })\n : [\n {\n name: DEFAULT_VARIANT,\n issues: baseIssues,\n ...previewUrlEntry(curated ?? generatedPreview?.(manifest.id, DEFAULT_VARIANT)),\n build: () => seededNodeFromManifest(manifest, studio.registry),\n },\n ]\n\n const defaultVariant = variants[0]!\n return {\n key: manifest.id,\n componentId: manifest.id,\n title,\n description: manifest.description,\n issues: defaultVariant.issues,\n ...previewUrlEntry(defaultVariant.previewUrl),\n variants,\n build: defaultVariant.build,\n }\n}\n\n/** Spreadable `{ previewUrl }` entry — omitted (not `undefined`-valued) when absent. */\nfunction previewUrlEntry(url: string | undefined): { previewUrl?: string } {\n return url === undefined ? {} : { previewUrl: url }\n}\n\n/**\n * A two-letter monogram from a tile title — the deliberate icon+name\n * placeholder glyph (EC-439 AD-2 tier 3) for tiles with neither a curated nor\n * a generated preview image: first letters of the first two words, else the\n * first two letters of a single word. Deterministic and pure.\n */\nexport function tileMonogram(title: string): string {\n const words = title.trim().split(/\\s+/).filter(Boolean)\n if (words.length === 0) return '?'\n const glyph = words.length === 1 ? words[0]!.slice(0, 2) : `${words[0]!.charAt(0)}${words[1]!.charAt(0)}`\n return glyph.toUpperCase()\n}\n\nfunction matchedFixtureNames(manifest: ComponentManifest, query: string): string[] {\n return (manifest.fixtures ?? [])\n .map((fixture) => fixture.name)\n .filter((name) => name.toLowerCase().includes(query))\n}\n\nfunction matches(manifest: ComponentManifest, query: string): boolean {\n const hay = [manifest.id, ...localizedStringVariants(manifest.title), manifest.description, manifest.category]\n .filter(Boolean)\n .join(' ')\n .toLowerCase()\n // EC-439: fixture names are searchable too — a content-seeded fixture name\n // (\"Startseite\") must find its component even when no manifest field matches.\n return hay.includes(query) || matchedFixtureNames(manifest, query).length > 0\n}\n\n/**\n * Derive the panel's category groups from the registry: filter by slot allow\n * list and query, derive ONE tile per manifest, group by `category`, and sort\n * categories alphabetically (registry order within a group). Deterministic.\n * When a query is active, each tile is stamped with the fixture names it\n * matched (see {@link BlockTile.matchedVariants}).\n */\nexport function blockGroups(\n studio: StudioRegistry,\n filter: BlockFilter = {},\n generatedPreview?: GeneratedPreviewLookup,\n): BlockGroup[] {\n const query = filter.query?.trim().toLowerCase()\n const allow = filter.slotAllow\n\n const groups = new Map<string, BlockTile[]>()\n for (const manifest of studio.registry.list()) {\n // EC-191: the canvas is client-rendered, so only canvas-eligible\n // (client-renderable) components are offered for insertion; server-only / RSC\n // components are usable in dev frames, not placed on the canvas.\n if (!isCanvasEligible(manifest)) continue\n if (allow && !allow.includes(manifest.id)) continue\n if (query && !matches(manifest, query)) continue\n const tile = tileForManifest(studio, manifest, generatedPreview)\n if (query) {\n const matched = matchedFixtureNames(manifest, query)\n if (matched.length > 0) tile.matchedVariants = matched\n }\n const category = manifest.category ?? UNCATEGORIZED\n const tiles = groups.get(category) ?? []\n tiles.push(tile)\n groups.set(category, tiles)\n }\n\n return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, tiles]) => ({ category, tiles }))\n}\n\n/**\n * Whether ANY component could be inserted under a slot allow-list (EC-405) — the\n * cheap existence check behind the canvas insertion affordances: a \"+\" only shows\n * at a position where the blocks panel would offer at least one tile. Mirrors the\n * exact filter {@link blockGroups} applies (canvas-eligible AND, when an allow-list\n * is present, on that list), short-circuiting on the first match instead of\n * building tiles; falls back to the layout-helper presets (which are also\n * insertable there). `undefined`/absent `slotAllow` means \"allow any\" → true when\n * the registry has any canvas-eligible component.\n */\nexport function hasInsertableComponent(studio: StudioRegistry, slotAllow?: string[]): boolean {\n for (const manifest of studio.registry.list()) {\n if (!isCanvasEligible(manifest)) continue\n if (slotAllow && !slotAllow.includes(manifest.id)) continue\n return true\n }\n return presetTiles(studio, slotAllow).length > 0\n}\n\n/**\n * Layout helper presets (EC-067) as panel tiles — already one tile per preset.\n * A preset has a single implicit variant and never a preview image, so its\n * tile renders the placeholder treatment. EC-591: the presets the component LIST\n * supports ({@link layoutPresets} — built from the layout and text roles), never\n * hardcoded engine ids.\n */\nexport function presetTiles(studio: StudioRegistry, slotAllow?: string[]): BlockTile[] {\n return layoutPresets(studio.registry).filter((preset) => !slotAllow || slotAllow.includes(preset.build().componentId)).map(\n (preset) => ({\n key: `preset::${preset.key}`,\n componentId: preset.build().componentId,\n title: preset.label,\n description: preset.description,\n issues: [],\n variants: [{ name: DEFAULT_VARIANT, issues: [], build: () => preset.build() }],\n build: () => preset.build(),\n }),\n )\n}\n"]}
1
+ {"version":3,"file":"blocks-model.js","sourceRoot":"","sources":["../../../src/components/builder/blocks-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { ANY_COMPONENT, placementRefusal, slotAllowList } from '@elytracms/core/project-graph'\nimport type { AllowList, ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry'\nimport { manifestSlots, propValueValidator } from '@elytracms/core/component-registry'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { componentBlockNode } from '@elytracms/core/rich-text'\nimport { localizedLabel } from '@elytracms/studio/lib/i18n'\nimport { componentStatus } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { nodeFromProps } from '@elytracms/studio/lib/registry/preview'\nimport { layoutPresets, nodeFromManifest, seededNodeFromManifest } from './node-factory'\n\n/**\n * Blocks panel model (EC-164, reshaped by EC-439) — the pure, React-free\n * derivation behind the visual insert surface. Every canvas-eligible registry\n * manifest yields **one tile** carrying the component identity plus a\n * `variants` list (one per declared fixture, or a single implicit `Default`\n * variant built from the seeded manifest defaults). Tiles group by manifest\n * `category` and filter by a free-text query over name / description /\n * category / **fixture names** — no hand-maintained panel config. Broken\n * fixtures degrade to variants carrying explicit `issues`, never to a missing\n * entry or a crash.\n *\n * Previews are ARTIFACTS, never runtime renders (EC-439 AD-2). Per tile and\n * per variant, `previewUrl` resolves: curated `manifest.previewImage` (EC-409,\n * always wins) → the {@link GeneratedPreviewLookup} seam (EC-440's build-time\n * fixture renders) → `undefined` (the panel shows an icon+name placeholder).\n */\n\n/**\n * The generated-preview seam (EC-439 AD-2, filled by EC-440): a pure lookup\n * from component id + fixture name to an image URL. The EC-440 CLI renders\n * every canvas-eligible component's fixtures headlessly against the consumer\n * theme and writes a previews manifest; the consumer's `studio.config.tsx`\n * adapts that manifest into this function via `defineStudio({ previews:\n * { generated } })`. A fixture-less manifest's single implicit variant is\n * looked up under the name {@link DEFAULT_VARIANT}. Empty/undefined everywhere\n * until EC-440 lands — the panel then falls back to placeholders.\n */\nexport type GeneratedPreviewLookup = (componentId: string, fixtureName: string) => string | undefined\n\n/**\n * The implicit variant name of a fixture-less manifest (and the fixture name\n * a {@link GeneratedPreviewLookup} is queried with for it).\n */\nexport const DEFAULT_VARIANT = 'Default'\n\n/**\n * One insertable variant of a component tile: a declared manifest fixture, or\n * the single implicit `Default` for a fixture-less manifest. `variants[0]` is\n * the tile's default — what tile click / Enter / drag inserts.\n */\nexport interface BlockVariant {\n /** The fixture name, or {@link DEFAULT_VARIANT} for the implicit variant. */\n name: string\n /**\n * Validation problems for this variant (manifest-level statuses plus the\n * fixture's own issues). A non-empty list marks the variant as degraded —\n * it still lists and still inserts.\n */\n issues: string[]\n /**\n * Resolved preview image URL: curated `manifest.previewImage` (wins) else\n * the generated-preview lookup's URL for this fixture. `undefined` → the\n * panel renders the icon+name placeholder.\n */\n previewUrl?: string\n /** Build the subtree this variant inserts (ids are reassigned on insert). */\n build: () => ComponentNode\n}\n\n/** One component (or layout preset) in the blocks panel — ONE tile per component (EC-439 AD-1). */\nexport interface BlockTile {\n /** Stable identity: the component id, or `preset::<key>` for layout helpers. */\n key: string\n /** Component id of the node the tile inserts. */\n componentId: string\n title: string\n description?: string\n /**\n * The DEFAULT variant's validation problems — what clicking the tile\n * inserts. Per-variant issues live on {@link BlockTile.variants}.\n */\n issues: string[]\n /** The default variant's resolved preview URL (see {@link BlockVariant.previewUrl}). */\n previewUrl?: string\n /** All insertable variants; `variants[0]` is the default the tile inserts. */\n variants: BlockVariant[]\n /**\n * Fixture names the active query matched (stamped by {@link blockGroups}\n * when a query is present) — the panel highlights the variant affordance and\n * the matching flyout rows so \"found by fixture name\" is visible.\n */\n matchedVariants?: string[]\n /** Build the DEFAULT subtree to insert: the first declared fixture's, else the seeded manifest default. */\n build: () => ComponentNode\n}\n\n/** Tiles of one manifest `category`. */\nexport interface BlockGroup {\n category: string\n tiles: BlockTile[]\n}\n\nexport interface BlockFilter {\n /** Case-insensitive match over id, title, description, category, and fixture names. */\n query?: string\n /**\n * The insertion position's effective vocabulary (EC-584 — `allowAt`: the field's at\n * the composition root, the slot's below). Omitted → any component.\n */\n allow?: AllowList\n}\n\n/**\n * EC-584: may `manifest` be OFFERED at a position whose vocabulary is `allow`? The\n * one core placement judge — the same call the builder reducer enforces on insert,\n * so the panel never offers what the sink refuses (canvas eligibility included).\n */\nfunction offerable(studio: StudioRegistry, manifestId: string, allow: AllowList): boolean {\n return placementRefusal({ componentId: manifestId, view: studio.registry.lookup.get(manifestId), allow }) === null\n}\n\nconst UNCATEGORIZED = 'other'\n\n/** A node built from a manifest's defaults with an explicit id. */\nfunction defaultNode(manifest: ComponentManifest, id: string): ComponentNode {\n return { ...nodeFromManifest(manifest), id }\n}\n\n/** Structured issues for a component that cannot render (EC-055 statuses). */\nfunction manifestIssues(studio: StudioRegistry, manifest: ComponentManifest): string[] {\n const status = componentStatus(studio, manifest)\n if (status === 'incompatible') {\n return [`No implementation is registered for \"${manifest.id}\".`]\n }\n if (status === 'invalid') {\n return studio.registry.issues.filter((issue) => issue.componentId === manifest.id).map((issue) => issue.message)\n }\n return []\n}\n\n/**\n * Build the configured subtree a fixture describes: the fixture's props (EC-591/2f:\n * never the manifest defaults — an unset prop stays absent), plus one bare child\n * node per `slotChildren` component id. Never throws — unknown components, undeclared\n * slots, and invalid prop values become entries in `issues` while the node is\n * still produced (the renderer shows its own visible fallbacks for them).\n */\nexport function fixtureSubtree(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n fixture: ComponentFixture,\n baseId: string,\n): { node: ComponentNode; issues: string[] } {\n const issues: string[] = []\n const fixtureProps = fixture.props ?? {}\n\n for (const [name, value] of Object.entries(fixtureProps)) {\n const spec = manifest.props[name]\n if (!spec) {\n issues.push(`Fixture \"${fixture.name}\" sets unknown prop \"${name}\".`)\n continue\n }\n // EC-190: validate the fixture value with the prop's field-def validator.\n if (!(propValueValidator(manifest, name)?.(value) ?? true)) {\n issues.push(`Fixture \"${fixture.name}\" sets an invalid value for prop \"${name}\".`)\n }\n }\n\n // EC-547: fixture-built inserts are v2 block-sequence citizens like every\n // other factory node — the root carries the `elytra/component` discriminator.\n const node = componentBlockNode(nodeFromProps(manifest, fixtureProps, baseId))\n\n if (fixture.slotChildren) {\n // EC-386: blocks-prop regions are declared slots too (`manifestSlots`), so a\n // fixture may seed a Section-style body without a false \"undeclared slot\" issue.\n const declared = new Set(manifestSlots(manifest).map((slot) => slot.name))\n const slots: NonNullable<ComponentNode['slots']> = {}\n for (const [slotName, childIds] of Object.entries(fixture.slotChildren)) {\n if (!declared.has(slotName)) {\n issues.push(`Fixture \"${fixture.name}\" fills undeclared slot \"${slotName}\".`)\n }\n // EC-584: the slot's vocabulary, read through the one placement judge — a fixture\n // child the slot forbids (or a server-only one) is a visible variant issue here,\n // and the reducer refuses the insert with the same verdict.\n const slotAllow = slotAllowList(studio.registry.lookup.get(manifest.id)?.slots?.find((s) => s.name === slotName))\n slots[slotName] = childIds.map((childId, index) => {\n const id = `${baseId}-${slotName}-${index}`\n const childManifest = studio.registry.getManifest(childId)\n if (!childManifest) {\n issues.push(`Fixture \"${fixture.name}\" references unknown component \"${childId}\" in slot \"${slotName}\".`)\n // Keep the reference: the renderer shows a visible missing-component fallback.\n return componentBlockNode({ id, componentId: childId })\n }\n const refusal = placementRefusal({ componentId: childId, view: studio.registry.lookup.get(childId), allow: slotAllow })\n if (refusal === 'forbidden-component') {\n issues.push(`Fixture \"${fixture.name}\" puts \"${childId}\" in slot \"${slotName}\", which the slot does not allow.`)\n } else if (refusal === 'server-only-component') {\n issues.push(`Fixture \"${fixture.name}\" puts the server-only \"${childId}\" in slot \"${slotName}\" — it cannot be placed on the canvas.`)\n }\n return defaultNode(childManifest, id)\n })\n }\n if (Object.keys(slots).length > 0) node.slots = slots\n }\n\n return { node, issues }\n}\n\n/**\n * The ONE tile for a manifest (EC-439 AD-1): component identity plus one\n * variant per declared fixture — or a single implicit `Default` variant using\n * the seeded manifest default (a bare layout container is seeded so it\n * survives EC-161 normalization instead of vanishing on commit, EC-383). The\n * tile's own `issues`/`previewUrl`/`build` mirror the default variant\n * (`variants[0]`), so tile click/Enter/drag inserts exactly what the first\n * fixture tile inserted before the reshape.\n */\nexport function tileForManifest(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n generatedPreview?: GeneratedPreviewLookup,\n): BlockTile {\n const title = localizedLabel(manifest.title, manifest.id)\n const baseIssues = manifestIssues(studio, manifest)\n // EC-409: a curated preview image previews the COMPONENT, not one fixture —\n // when declared it wins on the tile and on every variant row (AD-2 tier 1).\n const curated = manifest.previewImage\n\n const variants: BlockVariant[] =\n manifest.fixtures && manifest.fixtures.length > 0\n ? manifest.fixtures.map((fixture, index) => {\n const { issues } = fixtureSubtree(studio, manifest, fixture, `probe-${manifest.id}-${index}`)\n return {\n name: fixture.name,\n issues: [...baseIssues, ...issues],\n ...previewUrlEntry(curated ?? generatedPreview?.(manifest.id, fixture.name)),\n build: () => fixtureSubtree(studio, manifest, fixture, 'tmp').node,\n }\n })\n : [\n {\n name: DEFAULT_VARIANT,\n issues: baseIssues,\n ...previewUrlEntry(curated ?? generatedPreview?.(manifest.id, DEFAULT_VARIANT)),\n build: () => seededNodeFromManifest(manifest, studio.registry),\n },\n ]\n\n const defaultVariant = variants[0]!\n return {\n key: manifest.id,\n componentId: manifest.id,\n title,\n description: manifest.description,\n issues: defaultVariant.issues,\n ...previewUrlEntry(defaultVariant.previewUrl),\n variants,\n build: defaultVariant.build,\n }\n}\n\n/** Spreadable `{ previewUrl }` entry — omitted (not `undefined`-valued) when absent. */\nfunction previewUrlEntry(url: string | undefined): { previewUrl?: string } {\n return url === undefined ? {} : { previewUrl: url }\n}\n\n/**\n * A two-letter monogram from a tile title — the deliberate icon+name\n * placeholder glyph (EC-439 AD-2 tier 3) for tiles with neither a curated nor\n * a generated preview image: first letters of the first two words, else the\n * first two letters of a single word. Deterministic and pure.\n */\nexport function tileMonogram(title: string): string {\n const words = title.trim().split(/\\s+/).filter(Boolean)\n if (words.length === 0) return '?'\n const glyph = words.length === 1 ? words[0]!.slice(0, 2) : `${words[0]!.charAt(0)}${words[1]!.charAt(0)}`\n return glyph.toUpperCase()\n}\n\nfunction matchedFixtureNames(manifest: ComponentManifest, query: string): string[] {\n return (manifest.fixtures ?? [])\n .map((fixture) => fixture.name)\n .filter((name) => name.toLowerCase().includes(query))\n}\n\nfunction matches(manifest: ComponentManifest, query: string): boolean {\n const hay = [manifest.id, ...localizedStringVariants(manifest.title), manifest.description, manifest.category]\n .filter(Boolean)\n .join(' ')\n .toLowerCase()\n // EC-439: fixture names are searchable too — a content-seeded fixture name\n // (\"Startseite\") must find its component even when no manifest field matches.\n return hay.includes(query) || matchedFixtureNames(manifest, query).length > 0\n}\n\n/**\n * Derive the panel's category groups from the registry: filter by slot allow\n * list and query, derive ONE tile per manifest, group by `category`, and sort\n * categories alphabetically (registry order within a group). Deterministic.\n * When a query is active, each tile is stamped with the fixture names it\n * matched (see {@link BlockTile.matchedVariants}).\n */\nexport function blockGroups(\n studio: StudioRegistry,\n filter: BlockFilter = {},\n generatedPreview?: GeneratedPreviewLookup,\n): BlockGroup[] {\n const query = filter.query?.trim().toLowerCase()\n const allow = filter.allow ?? ANY_COMPONENT\n\n const groups = new Map<string, BlockTile[]>()\n for (const manifest of studio.registry.list()) {\n // EC-191: the canvas is client-rendered, so only canvas-eligible\n // (client-renderable) components are offered; server-only / RSC components are\n // usable in dev frames, not placed on the canvas. EC-584: eligibility and the\n // position's vocabulary are ONE question to the placement judge.\n if (!offerable(studio, manifest.id, allow)) continue\n if (query && !matches(manifest, query)) continue\n const tile = tileForManifest(studio, manifest, generatedPreview)\n if (query) {\n const matched = matchedFixtureNames(manifest, query)\n if (matched.length > 0) tile.matchedVariants = matched\n }\n const category = manifest.category ?? UNCATEGORIZED\n const tiles = groups.get(category) ?? []\n tiles.push(tile)\n groups.set(category, tiles)\n }\n\n return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, tiles]) => ({ category, tiles }))\n}\n\n/**\n * Whether ANY component could be inserted at a position whose vocabulary is `allow`\n * (EC-405) — the cheap existence check behind the canvas insertion affordances: a\n * \"+\" only shows at a position where the blocks panel would offer at least one tile.\n * The exact filter {@link blockGroups} applies (the placement judge), short-circuiting\n * on the first match instead of building tiles; falls back to the layout-helper\n * presets (which are also insertable there). Omitted `allow` → any component.\n */\nexport function hasInsertableComponent(studio: StudioRegistry, allow: AllowList = ANY_COMPONENT): boolean {\n for (const manifest of studio.registry.list()) {\n if (offerable(studio, manifest.id, allow)) return true\n }\n return presetTiles(studio, allow).length > 0\n}\n\n/**\n * Layout helper presets (EC-067) as panel tiles — already one tile per preset.\n * A preset has a single implicit variant and never a preview image, so its\n * tile renders the placeholder treatment. EC-591: the presets the component LIST\n * supports ({@link layoutPresets} — built from the layout and text roles), never\n * hardcoded engine ids.\n */\nexport function presetTiles(studio: StudioRegistry, allow: AllowList = ANY_COMPONENT): BlockTile[] {\n return layoutPresets(studio.registry).filter((preset) => offerable(studio, preset.build().componentId, allow)).map(\n (preset) => ({\n key: `preset::${preset.key}`,\n componentId: preset.build().componentId,\n title: preset.label,\n description: preset.description,\n issues: [],\n variants: [{ name: DEFAULT_VARIANT, issues: [], build: () => preset.build() }],\n build: () => preset.build(),\n }),\n )\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { InsertTarget } from '@elytracms/core/project-graph';
1
+ import type { AllowList, InsertTarget } from '@elytracms/core/project-graph';
2
2
  import type { ClipboardFragment } from '@elytracms/core/clipboard';
3
3
  import type { StudioRegistry } from '@elytracms/studio/lib/registry';
4
4
  import type { BlockTile, BlockVariant } from './blocks-model.js';
@@ -14,8 +14,12 @@ import type { BlockTile, BlockVariant } from './blocks-model.js';
14
14
  */
15
15
  export interface InsertContext {
16
16
  target: InsertTarget;
17
- /** When set, only these component ids may be inserted into the slot (slot.allow). */
18
- slotAllow?: string[];
17
+ /**
18
+ * The landing position's effective vocabulary (EC-584 — `allowAt`: the field's at
19
+ * the composition root, the slot's below); the panel offers what the placement
20
+ * judge admits there, exactly what the reducer will accept.
21
+ */
22
+ allow: AllowList;
19
23
  /** Human label of where the insert lands. */
20
24
  destinationLabel: string;
21
25
  }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { AlertTriangle, ClipboardCopy, Search } from 'lucide-react';
3
3
  import { useEffect, useMemo, useRef, useState } from 'react';
4
+ import { isOpenAllowList } from '@elytracms/core/project-graph';
4
5
  import { buildFragmentFromRegistryFixture, buildFragmentFromSubtree, } from '@elytracms/core/clipboard';
5
6
  import { getStudioRegistry } from '@elytracms/studio/lib/registry';
6
7
  import { translate } from '@elytracms/studio/lib/i18n';
@@ -55,15 +56,15 @@ export function BlocksPanel({ context, open, onOpenChange, }) {
55
56
  if (open)
56
57
  setQuery('');
57
58
  }, [open]);
58
- const slotAllow = context?.slotAllow;
59
+ const allow = context?.allow;
59
60
  const trimmed = query.trim();
60
61
  // EC-439 AD-2: the generated-preview seam (EC-440) — a config-sourced pure
61
62
  // lookup, `undefined` until a consumer wires `defineStudio({ previews })`.
62
63
  const generatedPreview = resolveDeploymentGeneratedPreviews();
63
- const groups = useMemo(() => blockGroups(studio, { query: trimmed || undefined, slotAllow }, generatedPreview), [studio, trimmed, slotAllow, generatedPreview]);
64
+ const groups = useMemo(() => blockGroups(studio, { query: trimmed || undefined, allow }, generatedPreview), [studio, trimmed, allow, generatedPreview]);
64
65
  // Layout helpers keep their dedicated group while browsing (hidden once a
65
- // query narrows the panel to registry matches), filtered by slot.allow.
66
- const presets = useMemo(() => (trimmed ? [] : presetTiles(studio, slotAllow)), [studio, trimmed, slotAllow]);
66
+ // query narrows the panel to registry matches), filtered by the position's vocabulary.
67
+ const presets = useMemo(() => (trimmed ? [] : presetTiles(studio, allow)), [studio, trimmed, allow]);
67
68
  // Drag-to-insert (EC-160): a tile is also a drag source. While the drag is
68
69
  // live the modal (content + overlay + Radix body pointer lock) gets out of
69
70
  // the way so the canvas and the page tree can receive the drop; the session
@@ -131,7 +132,7 @@ export function BlocksPanel({ context, open, onOpenChange, }) {
131
132
  endDrag('cancelled');
132
133
  }
133
134
  const empty = groups.length === 0 && presets.length === 0;
134
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-3xl", onInteractOutside: preventOutsideDismiss, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('builder.blocksPanel.title') }), _jsxs(DialogDescription, { children: ["Adds into ", _jsx(Code, { tone: "muted", children: context.destinationLabel }), "."] })] }), _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { autoFocus: true, value: query, onChange: (e) => setQuery(e.target.value), placeholder: translate('builder.blocksPanel.searchPlaceholder'), "aria-label": translate('builder.blocksPanel.searchAria'), className: "pl-8" })] }), _jsxs("div", { className: "max-h-[min(30rem,calc(100dvh-14rem))] overflow-auto pr-1", children: [presets.length > 0 ? (_jsx(TileGroup, { heading: translate('builder.blocksPanel.layoutHelpers'), tiles: presets, onInsert: insertVariant, onCopy: copyVariant, onTileDragStart: handleTileDragStart, onTileDragEnd: handleTileDragEnd })) : null, groups.map((group) => (_jsx(TileGroup, { heading: group.category, tiles: group.tiles, onInsert: insertVariant, onCopy: copyVariant, onTileDragStart: handleTileDragStart, onTileDragEnd: handleTileDragEnd }, group.category))), empty ? (_jsx(Text, { size: "sm", tone: "muted", className: "p-3", children: slotAllow
135
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-3xl", onInteractOutside: preventOutsideDismiss, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('builder.blocksPanel.title') }), _jsxs(DialogDescription, { children: ["Adds into ", _jsx(Code, { tone: "muted", children: context.destinationLabel }), "."] })] }), _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { autoFocus: true, value: query, onChange: (e) => setQuery(e.target.value), placeholder: translate('builder.blocksPanel.searchPlaceholder'), "aria-label": translate('builder.blocksPanel.searchAria'), className: "pl-8" })] }), _jsxs("div", { className: "max-h-[min(30rem,calc(100dvh-14rem))] overflow-auto pr-1", children: [presets.length > 0 ? (_jsx(TileGroup, { heading: translate('builder.blocksPanel.layoutHelpers'), tiles: presets, onInsert: insertVariant, onCopy: copyVariant, onTileDragStart: handleTileDragStart, onTileDragEnd: handleTileDragEnd })) : null, groups.map((group) => (_jsx(TileGroup, { heading: group.category, tiles: group.tiles, onInsert: insertVariant, onCopy: copyVariant, onTileDragStart: handleTileDragStart, onTileDragEnd: handleTileDragEnd }, group.category))), empty ? (_jsx(Text, { size: "sm", tone: "muted", className: "p-3", children: allow && !isOpenAllowList(allow)
135
136
  ? translate('builder.blocksPanel.noAllowed')
136
137
  : translate('builder.blocksPanel.noMatch', { query }) })) : null] })] }) }));
137
138
  }