@elytracms/studio 0.0.85 → 0.0.87

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 (248) hide show
  1. package/convex/asset-revalidate.test.ts +186 -0
  2. package/convex/assets.ts +53 -19
  3. package/convex/cms.ts +22 -0
  4. package/convex/delivery.ts +54 -116
  5. package/convex/engine-version.test.ts +31 -0
  6. package/convex/functions.ts +3 -3
  7. package/convex/rail-write-parity.test.ts +180 -0
  8. package/convex/redirects.ts +33 -11
  9. package/convex/revalidate.ts +7 -5
  10. package/convex/triggers.ts +53 -3
  11. package/dist/components/ai/ai-chat-panel.js +13 -2
  12. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  13. package/dist/components/app-views/default-modules.d.ts +20 -20
  14. package/dist/components/app-views/default-modules.js +27 -48
  15. package/dist/components/app-views/default-modules.js.map +1 -1
  16. package/dist/components/app-views/studio-config.d.ts +5 -6
  17. package/dist/components/app-views/studio-config.js.map +1 -1
  18. package/dist/components/assets/asset-metadata.d.ts +28 -19
  19. package/dist/components/assets/asset-metadata.js +24 -31
  20. package/dist/components/assets/asset-metadata.js.map +1 -1
  21. package/dist/components/assets/asset-selection.d.ts +24 -5
  22. package/dist/components/assets/asset-selection.js +39 -9
  23. package/dist/components/assets/asset-selection.js.map +1 -1
  24. package/dist/components/assets/assets-collection-view-source.d.ts +17 -14
  25. package/dist/components/assets/assets-collection-view-source.js +25 -31
  26. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  27. package/dist/components/builder/blocks-model.d.ts +5 -4
  28. package/dist/components/builder/blocks-model.js +5 -4
  29. package/dist/components/builder/blocks-model.js.map +1 -1
  30. package/dist/components/builder/blocks-panel.js +3 -3
  31. package/dist/components/builder/blocks-panel.js.map +1 -1
  32. package/dist/components/builder/builder-store.d.ts +25 -1
  33. package/dist/components/builder/builder-store.js +3 -2
  34. package/dist/components/builder/builder-store.js.map +1 -1
  35. package/dist/components/builder/canvas-chrome-css.js +0 -17
  36. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  37. package/dist/components/builder/canvas-chrome.d.ts +10 -12
  38. package/dist/components/builder/canvas-chrome.js +12 -13
  39. package/dist/components/builder/canvas-chrome.js.map +1 -1
  40. package/dist/components/builder/canvas-dnd.d.ts +8 -0
  41. package/dist/components/builder/canvas-dnd.js +5 -3
  42. package/dist/components/builder/canvas-dnd.js.map +1 -1
  43. package/dist/components/builder/canvas-render-context.d.ts +12 -24
  44. package/dist/components/builder/canvas-render-context.js +15 -44
  45. package/dist/components/builder/canvas-render-context.js.map +1 -1
  46. package/dist/components/builder/canvas.js +5 -11
  47. package/dist/components/builder/canvas.js.map +1 -1
  48. package/dist/components/builder/clipboard-actions.d.ts +15 -1
  49. package/dist/components/builder/clipboard-actions.js +35 -12
  50. package/dist/components/builder/clipboard-actions.js.map +1 -1
  51. package/dist/components/builder/composition-field-editor.d.ts +32 -26
  52. package/dist/components/builder/composition-field-editor.js +47 -54
  53. package/dist/components/builder/composition-field-editor.js.map +1 -1
  54. package/dist/components/builder/insert-context.d.ts +3 -1
  55. package/dist/components/builder/insert-context.js +19 -12
  56. package/dist/components/builder/insert-context.js.map +1 -1
  57. package/dist/components/builder/page-tree.d.ts +29 -0
  58. package/dist/components/builder/page-tree.js +56 -58
  59. package/dist/components/builder/page-tree.js.map +1 -1
  60. package/dist/components/builder/reuse.js +5 -2
  61. package/dist/components/builder/reuse.js.map +1 -1
  62. package/dist/components/cms/asset-field.js +20 -32
  63. package/dist/components/cms/asset-field.js.map +1 -1
  64. package/dist/components/cms/cell-edit.d.ts +7 -0
  65. package/dist/components/cms/cell-edit.js +9 -1
  66. package/dist/components/cms/cell-edit.js.map +1 -1
  67. package/dist/components/cms/cms-composition-validators.js +11 -2
  68. package/dist/components/cms/cms-composition-validators.js.map +1 -1
  69. package/dist/components/cms/collection-table.js +11 -0
  70. package/dist/components/cms/collection-table.js.map +1 -1
  71. package/dist/components/cms/document-url-path.d.ts +7 -4
  72. package/dist/components/cms/document-url-path.js +15 -1
  73. package/dist/components/cms/document-url-path.js.map +1 -1
  74. package/dist/components/cms/editable-cell.js +5 -2
  75. package/dist/components/cms/editable-cell.js.map +1 -1
  76. package/dist/components/cms/field-control.js +4 -0
  77. package/dist/components/cms/field-control.js.map +1 -1
  78. package/dist/components/cms/inline-field-errors.d.ts +36 -0
  79. package/dist/components/cms/inline-field-errors.js +13 -5
  80. package/dist/components/cms/inline-field-errors.js.map +1 -1
  81. package/dist/components/cms/rich-text/insert.d.ts +6 -3
  82. package/dist/components/cms/rich-text/insert.js +9 -6
  83. package/dist/components/cms/rich-text/insert.js.map +1 -1
  84. package/dist/components/cms/use-document-versions.js +1 -0
  85. package/dist/components/cms/use-document-versions.js.map +1 -1
  86. package/dist/components/cms/use-live-url.d.ts +18 -6
  87. package/dist/components/cms/use-live-url.js +10 -0
  88. package/dist/components/cms/use-live-url.js.map +1 -1
  89. package/dist/components/cms/versions-model.d.ts +0 -12
  90. package/dist/components/cms/versions-model.js +3 -15
  91. package/dist/components/cms/versions-model.js.map +1 -1
  92. package/dist/components/components/component-detail.js +1 -1
  93. package/dist/components/components/component-detail.js.map +1 -1
  94. package/dist/components/components/transfer-dialog.js +2 -4
  95. package/dist/components/components/transfer-dialog.js.map +1 -1
  96. package/dist/components/components/transfer.d.ts +19 -15
  97. package/dist/components/components/transfer.js +28 -25
  98. package/dist/components/components/transfer.js.map +1 -1
  99. package/dist/components/deployment-setup-gate.js +9 -3
  100. package/dist/components/deployment-setup-gate.js.map +1 -1
  101. package/dist/components/project-session.d.ts +9 -0
  102. package/dist/components/project-session.js +1 -1
  103. package/dist/components/project-session.js.map +1 -1
  104. package/dist/components/redirects/redirects-collection-view-source.d.ts +17 -2
  105. package/dist/components/redirects/redirects-collection-view-source.js +20 -6
  106. package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
  107. package/dist/components/section-surface.js +3 -2
  108. package/dist/components/section-surface.js.map +1 -1
  109. package/dist/components/settings-form.js +1 -2
  110. package/dist/components/settings-form.js.map +1 -1
  111. package/dist/components/studio-context.js +1 -18
  112. package/dist/components/studio-context.js.map +1 -1
  113. package/dist/components/studio-project-screen.js +3 -5
  114. package/dist/components/studio-project-screen.js.map +1 -1
  115. package/dist/lib/ai/apply-content.js +73 -29
  116. package/dist/lib/ai/apply-content.js.map +1 -1
  117. package/dist/lib/ai/constraints.d.ts +35 -0
  118. package/dist/lib/ai/constraints.js +155 -72
  119. package/dist/lib/ai/constraints.js.map +1 -1
  120. package/dist/lib/ai/index.d.ts +1 -0
  121. package/dist/lib/ai/index.js +1 -0
  122. package/dist/lib/ai/index.js.map +1 -1
  123. package/dist/lib/ai/route-fetch.d.ts +58 -0
  124. package/dist/lib/ai/route-fetch.js +98 -0
  125. package/dist/lib/ai/route-fetch.js.map +1 -0
  126. package/dist/lib/ai/route-provider.d.ts +5 -22
  127. package/dist/lib/ai/route-provider.js +23 -1
  128. package/dist/lib/ai/route-provider.js.map +1 -1
  129. package/dist/lib/ai/route.js +4 -1
  130. package/dist/lib/ai/route.js.map +1 -1
  131. package/dist/lib/ai/shape.d.ts +3 -2
  132. package/dist/lib/ai/shape.js +16 -24
  133. package/dist/lib/ai/shape.js.map +1 -1
  134. package/dist/lib/ai/types.d.ts +8 -0
  135. package/dist/lib/ai/types.js.map +1 -1
  136. package/dist/lib/ai/update-fields.js +33 -12
  137. package/dist/lib/ai/update-fields.js.map +1 -1
  138. package/dist/lib/assets/asset-placements.d.ts +35 -0
  139. package/dist/lib/assets/asset-placements.js +74 -0
  140. package/dist/lib/assets/asset-placements.js.map +1 -0
  141. package/dist/lib/assets/backfill-thumbs.d.ts +9 -8
  142. package/dist/lib/assets/backfill-thumbs.js +13 -25
  143. package/dist/lib/assets/backfill-thumbs.js.map +1 -1
  144. package/dist/lib/clipboard/paste-issues.d.ts +17 -15
  145. package/dist/lib/clipboard/paste-issues.js +8 -45
  146. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  147. package/dist/lib/clipboard/paste-target.d.ts +47 -1
  148. package/dist/lib/clipboard/paste-target.js +19 -11
  149. package/dist/lib/clipboard/paste-target.js.map +1 -1
  150. package/dist/lib/cms/document-gate.d.ts +19 -9
  151. package/dist/lib/cms/document-gate.js +25 -4
  152. package/dist/lib/cms/document-gate.js.map +1 -1
  153. package/dist/lib/cms/published-documents.js +1 -0
  154. package/dist/lib/cms/published-documents.js.map +1 -1
  155. package/dist/lib/cms-views/view-model.d.ts +3 -3
  156. package/dist/lib/data/index.d.ts +0 -3
  157. package/dist/lib/data/index.js +4 -6
  158. package/dist/lib/data/index.js.map +1 -1
  159. package/dist/lib/dnd/drop-rules.d.ts +45 -2
  160. package/dist/lib/dnd/drop-rules.js +64 -9
  161. package/dist/lib/dnd/drop-rules.js.map +1 -1
  162. package/dist/lib/engine-version.d.ts +16 -0
  163. package/dist/lib/engine-version.js +17 -0
  164. package/dist/lib/engine-version.js.map +1 -0
  165. package/dist/lib/i18n/messages.de.js +8 -56
  166. package/dist/lib/i18n/messages.de.js.map +1 -1
  167. package/dist/lib/i18n/messages.en.d.ts +8 -53
  168. package/dist/lib/i18n/messages.en.js +15 -57
  169. package/dist/lib/i18n/messages.en.js.map +1 -1
  170. package/dist/lib/locale/fallback.js +8 -2
  171. package/dist/lib/locale/fallback.js.map +1 -1
  172. package/dist/lib/locale/index.d.ts +0 -1
  173. package/dist/lib/locale/index.js +0 -1
  174. package/dist/lib/locale/index.js.map +1 -1
  175. package/dist/lib/locale/locale-store.d.ts +10 -3
  176. package/dist/lib/locale/locale-store.js +12 -5
  177. package/dist/lib/locale/locale-store.js.map +1 -1
  178. package/dist/lib/operations/session-hydration.d.ts +9 -5
  179. package/dist/lib/operations/session-hydration.js +10 -10
  180. package/dist/lib/operations/session-hydration.js.map +1 -1
  181. package/dist/lib/operations/studio-operations.js +4 -2
  182. package/dist/lib/operations/studio-operations.js.map +1 -1
  183. package/dist/lib/persistence/convex-adapter.js +63 -75
  184. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  185. package/dist/lib/persistence/deployment-readiness.d.ts +8 -1
  186. package/dist/lib/persistence/deployment-readiness.js +4 -0
  187. package/dist/lib/persistence/deployment-readiness.js.map +1 -1
  188. package/dist/lib/persona/labels.d.ts +0 -2
  189. package/dist/lib/persona/labels.js +1 -7
  190. package/dist/lib/persona/labels.js.map +1 -1
  191. package/dist/lib/registry/index.d.ts +4 -2
  192. package/dist/lib/registry/index.js +26 -21
  193. package/dist/lib/registry/index.js.map +1 -1
  194. package/dist/lib/registry/project-components.js +0 -3
  195. package/dist/lib/registry/project-components.js.map +1 -1
  196. package/dist/lib/routes/route-issues.js +4 -5
  197. package/dist/lib/routes/route-issues.js.map +1 -1
  198. package/dist/lib/theme.js +3 -2
  199. package/dist/lib/theme.js.map +1 -1
  200. package/dist/lib/webhooks/tags.js +11 -14
  201. package/dist/lib/webhooks/tags.js.map +1 -1
  202. package/dist/lib/workspace/index.d.ts +1 -1
  203. package/dist/lib/workspace/index.js +1 -1
  204. package/dist/lib/workspace/index.js.map +1 -1
  205. package/dist/previews/enumerate.d.ts +27 -9
  206. package/dist/previews/enumerate.js +27 -6
  207. package/dist/previews/enumerate.js.map +1 -1
  208. package/dist/previews/manifest-format.d.ts +16 -7
  209. package/dist/previews/manifest-format.js +0 -21
  210. package/dist/previews/manifest-format.js.map +1 -1
  211. package/dist/router/match.d.ts +0 -2
  212. package/dist/router/match.js +0 -2
  213. package/dist/router/match.js.map +1 -1
  214. package/dist/ui/badge.d.ts +1 -1
  215. package/package.json +2 -2
  216. package/src/styles/studio.css +5 -4
  217. package/src/styles/tokens.css +72 -55
  218. package/convex/content.ts +0 -291
  219. package/dist/components/collab/collab-context.d.ts +0 -27
  220. package/dist/components/collab/collab-context.js +0 -39
  221. package/dist/components/collab/collab-context.js.map +0 -1
  222. package/dist/components/collab/collab-seed.d.ts +0 -15
  223. package/dist/components/collab/collab-seed.js +0 -20
  224. package/dist/components/collab/collab-seed.js.map +0 -1
  225. package/dist/components/extensions/extensions-view.d.ts +0 -7
  226. package/dist/components/extensions/extensions-view.js +0 -127
  227. package/dist/components/extensions/extensions-view.js.map +0 -1
  228. package/dist/lib/bindings/section-bindings.d.ts +0 -24
  229. package/dist/lib/bindings/section-bindings.js +0 -9
  230. package/dist/lib/bindings/section-bindings.js.map +0 -1
  231. package/dist/lib/data/data-store.d.ts +0 -98
  232. package/dist/lib/data/data-store.js +0 -211
  233. package/dist/lib/data/data-store.js.map +0 -1
  234. package/dist/lib/data/providers.d.ts +0 -143
  235. package/dist/lib/data/providers.js +0 -255
  236. package/dist/lib/data/providers.js.map +0 -1
  237. package/dist/lib/data/sample-capture.d.ts +0 -30
  238. package/dist/lib/data/sample-capture.js +0 -70
  239. package/dist/lib/data/sample-capture.js.map +0 -1
  240. package/dist/lib/data/sources-store.d.ts +0 -42
  241. package/dist/lib/data/sources-store.js +0 -120
  242. package/dist/lib/data/sources-store.js.map +0 -1
  243. package/dist/lib/locale/binding-fallback.d.ts +0 -35
  244. package/dist/lib/locale/binding-fallback.js +0 -73
  245. package/dist/lib/locale/binding-fallback.js.map +0 -1
  246. package/dist/lib/starters/kit-session.d.ts +0 -5
  247. package/dist/lib/starters/kit-session.js +0 -17
  248. package/dist/lib/starters/kit-session.js.map +0 -1
@@ -99,10 +99,11 @@ export interface BlockFilter {
99
99
  * their set intersection, preserving the first list's order. This layers a
100
100
  * composition field's top-level `allow` vocabulary over a slot's positional
101
101
  * `allow` at the canvas root (EC-188): a component must satisfy BOTH leashes to be
102
- * offered. An empty array stays empty ("allow nothing"); callers wanting the
103
- * validator's "empty = allow any" must coalesce `[]` to `undefined` first (which
104
- * `resolveInsertContext` does via its `length > 0` guard, mirroring
105
- * `validateCompositionValue`).
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.
106
107
  */
107
108
  export declare function intersectAllow(slotAllow: readonly string[] | undefined, fieldAllow: readonly string[] | undefined): string[] | undefined;
108
109
  /**
@@ -16,10 +16,11 @@ export const DEFAULT_VARIANT = 'Default';
16
16
  * their set intersection, preserving the first list's order. This layers a
17
17
  * composition field's top-level `allow` vocabulary over a slot's positional
18
18
  * `allow` at the canvas root (EC-188): a component must satisfy BOTH leashes to be
19
- * offered. An empty array stays empty ("allow nothing"); callers wanting the
20
- * validator's "empty = allow any" must coalesce `[]` to `undefined` first (which
21
- * `resolveInsertContext` does via its `length > 0` guard, mirroring
22
- * `validateCompositionValue`).
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.
23
24
  */
24
25
  export function intersectAllow(slotAllow, fieldAllow) {
25
26
  if (!slotAllow)
@@ -1 +1 @@
1
- {"version":3,"file":"blocks-model.js","sourceRoot":"","sources":["../../../src/components/builder/blocks-model.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AACxG,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAEhE,OAAO,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AACtE,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AA+BzF;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,SAAS,CAAA;AAkExC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAC5B,SAAwC,EACxC,UAAyC;IAEzC,IAAI,CAAC,SAAS;QAAE,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAC/D,IAAI,CAAC,UAAU;QAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAA;IACtC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAA;IACnC,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;AAClD,CAAC;AAED,MAAM,aAAa,GAAG,OAAO,CAAA;AAE7B,mEAAmE;AACnE,SAAS,WAAW,CAAC,QAA2B,EAAE,EAAU;IAC1D,OAAO,EAAE,GAAG,gBAAgB,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAA;AAC9C,CAAC;AAED,8EAA8E;AAC9E,SAAS,cAAc,CAAC,MAAsB,EAAE,QAA2B;IACzE,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAChD,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;QAC9B,OAAO,CAAC,wCAAwC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAA;IAClE,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,KAAK,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAClH,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAsB,EACtB,QAA2B,EAC3B,OAAyB,EACzB,MAAc;IAEd,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAA;IAExC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,IAAI,wBAAwB,IAAI,IAAI,CAAC,CAAA;YACrE,SAAQ;QACV,CAAC;QACD,0EAA0E;QAC1E,IAAI,CAAC,CAAC,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC;YAC3D,MAAM,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,IAAI,qCAAqC,IAAI,IAAI,CAAC,CAAA;QACpF,CAAC;IACH,CAAC;IAED,0EAA0E;IAC1E,8EAA8E;IAC9E,MAAM,IAAI,GAAG,kBAAkB,CAAC,aAAa,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAA;IAE9E,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;QACzB,6EAA6E;QAC7E,iFAAiF;QACjF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QAC1E,MAAM,KAAK,GAAwC,EAAE,CAAA;QACrD,KAAK,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;YACxE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5B,MAAM,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,IAAI,4BAA4B,QAAQ,IAAI,CAAC,CAAA;YAC/E,CAAC;YACD,KAAK,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;gBAChD,MAAM,EAAE,GAAG,GAAG,MAAM,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAA;gBAC3C,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;gBAC1D,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnB,MAAM,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,IAAI,mCAAmC,OAAO,cAAc,QAAQ,IAAI,CAAC,CAAA;oBACzG,+EAA+E;oBAC/E,OAAO,kBAAkB,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAA;gBACzD,CAAC;gBACD,OAAO,WAAW,CAAC,aAAa,EAAE,EAAE,CAAC,CAAA;YACvC,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACvD,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACzB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAsB,EACtB,QAA2B,EAC3B,gBAAyC;IAEzC,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAA;IACzD,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IACnD,4EAA4E;IAC5E,4EAA4E;IAC5E,MAAM,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAA;IAErC,MAAM,QAAQ,GACZ,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QAC/C,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;YACvC,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,QAAQ,CAAC,EAAE,IAAI,KAAK,EAAE,CAAC,CAAA;YAC7F,OAAO;gBACL,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,CAAC,GAAG,UAAU,EAAE,GAAG,MAAM,CAAC;gBAClC,GAAG,eAAe,CAAC,OAAO,IAAI,gBAAgB,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC5E,KAAK,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,IAAI;aACnE,CAAA;QACH,CAAC,CAAC;QACJ,CAAC,CAAC;YACE;gBACE,IAAI,EAAE,eAAe;gBACrB,MAAM,EAAE,UAAU;gBAClB,GAAG,eAAe,CAAC,OAAO,IAAI,gBAAgB,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAC,CAAC;gBAC/E,KAAK,EAAE,GAAG,EAAE,CAAC,sBAAsB,CAAC,QAAQ,CAAC;aAC9C;SACF,CAAA;IAEP,MAAM,cAAc,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAA;IACnC,OAAO;QACL,GAAG,EAAE,QAAQ,CAAC,EAAE;QAChB,WAAW,EAAE,QAAQ,CAAC,EAAE;QACxB,KAAK;QACL,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,MAAM,EAAE,cAAc,CAAC,MAAM;QAC7B,GAAG,eAAe,CAAC,cAAc,CAAC,UAAU,CAAC;QAC7C,QAAQ;QACR,KAAK,EAAE,cAAc,CAAC,KAAK;KAC5B,CAAA;AACH,CAAC;AAED,wFAAwF;AACxF,SAAS,eAAe,CAAC,GAAuB;IAC9C,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAA;AACrD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACvD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAA;IAClC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAA;IACzG,OAAO,KAAK,CAAC,WAAW,EAAE,CAAA;AAC5B,CAAC;AAED,SAAS,mBAAmB,CAAC,QAA2B,EAAE,KAAa;IACrE,OAAO,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC;SAC7B,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;SAC9B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;AACzD,CAAC;AAED,SAAS,OAAO,CAAC,QAA2B,EAAE,KAAa;IACzD,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,uBAAuB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC;SAC3G,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC;SACT,WAAW,EAAE,CAAA;IAChB,2EAA2E;IAC3E,8EAA8E;IAC9E,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;AAC/E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CACzB,MAAsB,EACtB,SAAsB,EAAE,EACxB,gBAAyC;IAEzC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAChD,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAA;IAE9B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAA;IAC7C,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QAC9C,iEAAiE;QACjE,8EAA8E;QAC9E,iEAAiE;QACjE,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC;YAAE,SAAQ;QACzC,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAAE,SAAQ;QACnD,IAAI,KAAK,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC;YAAE,SAAQ;QAChD,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAA;QAChE,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,OAAO,GAAG,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;YACpD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;gBAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAA;QACxD,CAAC;QACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,IAAI,aAAa,CAAA;QACnD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;QACxC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChB,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;IAC7B,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;AACvH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAsB,EAAE,SAAoB;IACjF,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QAC9C,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC;YAAE,SAAQ;QACzC,IAAI,SAAS,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAAE,SAAQ;QAC3D,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,SAAoB;IAC9C,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CACxG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACX,GAAG,EAAE,WAAW,MAAM,CAAC,GAAG,EAAE;QAC5B,WAAW,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,WAAW;QACvC,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;QAC9E,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE;KAC5B,CAAC,CACH,CAAA;AACH,CAAC","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 { LAYOUT_PRESETS, 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. An empty array stays empty (\"allow nothing\"); callers wanting the\n * validator's \"empty = allow any\" must coalesce `[]` to `undefined` first (which\n * `resolveInsertContext` does via its `length > 0` guard, mirroring\n * `validateCompositionValue`).\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: manifest defaults overlaid\n * with the fixture's props, plus one default-props child node per\n * `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),\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(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.\n */\nexport function presetTiles(slotAllow?: string[]): BlockTile[] {\n return LAYOUT_PRESETS.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 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 { LAYOUT_PRESETS, 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: manifest defaults overlaid\n * with the fixture's props, plus one default-props child node per\n * `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),\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(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.\n */\nexport function presetTiles(slotAllow?: string[]): BlockTile[] {\n return LAYOUT_PRESETS.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"]}
@@ -5,7 +5,7 @@ import { buildFragmentFromRegistryFixture, buildFragmentFromSubtree, } from '@el
5
5
  import { getStudioRegistry } from '@elytracms/studio/lib/registry';
6
6
  import { translate } from '@elytracms/studio/lib/i18n';
7
7
  import { resolveDeploymentGeneratedPreviews } from '@elytracms/studio/lib/workspace';
8
- import { useCollab } from '../collab/collab-context.js';
8
+ import { useProjectSession } from '../project-session.js';
9
9
  import { useStudioClipboard } from './clipboard-actions.js';
10
10
  import { beginDrag, endDrag, onDragSessionEnd } from './drag-session.js';
11
11
  import { Code, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Input, Popover, PopoverContent, PopoverTrigger, Row, Text, } from '@elytracms/studio/ui';
@@ -48,7 +48,7 @@ export function preventOutsideDismiss(event) {
48
48
  export function BlocksPanel({ context, open, onOpenChange, }) {
49
49
  const builder = useBuilder();
50
50
  const studio = getStudioRegistry();
51
- const collab = useCollab();
51
+ const { projectId } = useProjectSession();
52
52
  const clipboard = useStudioClipboard();
53
53
  const [query, setQuery] = useState('');
54
54
  useEffect(() => {
@@ -88,7 +88,7 @@ export function BlocksPanel({ context, open, onOpenChange, }) {
88
88
  // "Copy exact configuration" (EC-175): same fragment + paste path as
89
89
  // copying a live canvas instance, per variant.
90
90
  function copyVariant(tile, variant) {
91
- const fragment = tileFragment(studio, tile, variant, collab.projectId);
91
+ const fragment = tileFragment(studio, tile, variant, projectId);
92
92
  if (fragment)
93
93
  clipboard.copyFragment(fragment, `${tile.title} — ${variant.name}`);
94
94
  }
@@ -1 +1 @@
1
- {"version":3,"file":"blocks-panel.js","sourceRoot":"","sources":["../../../src/components/builder/blocks-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, ClipboardCopy, Search } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { InsertTarget } from '@elytracms/core/project-graph'\nimport {\n buildFragmentFromRegistryFixture,\n buildFragmentFromSubtree,\n} from '@elytracms/core/clipboard'\nimport type { ClipboardFragment } from '@elytracms/core/clipboard'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { resolveDeploymentGeneratedPreviews } from '@elytracms/studio/lib/workspace'\nimport { useCollab } from '../collab/collab-context'\nimport { useStudioClipboard } from './clipboard-actions'\nimport { beginDrag, endDrag, onDragSessionEnd } from './drag-session'\nimport {\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n Input,\n Popover,\n PopoverContent,\n PopoverTrigger,\n Row,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useBuilder } from './builder-store'\nimport { blockGroups, presetTiles } from './blocks-model'\nimport type { BlockTile, BlockVariant } from './blocks-model'\nimport { PlaceholderThumbnail, PreviewImageThumbnail } from './fixture-thumbnail'\n\n/**\n * Blocks panel (EC-164, redesigned by EC-439): the visual insert surface.\n * Every canvas-eligible registry component is ONE tile; a component with 2+\n * fixtures carries a compact \"n variants\" chip opening a flyout with one\n * insert/copy row per fixture. Clicking a tile (or dragging it) inserts the\n * DEFAULT — the first declared fixture's configured subtree, else the seeded\n * manifest default — through the builder's `insert` action (one history entry).\n * Previews are artifacts (curated `previewImage` → generated artifact →\n * icon+name placeholder), never in-document runtime renders.\n */\n\nexport interface InsertContext {\n target: InsertTarget\n /** When set, only these component ids may be inserted into the slot (slot.allow). */\n slotAllow?: string[]\n /** Human label of where the insert lands. */\n destinationLabel: string\n}\n\n/**\n * \"Copy exact configuration\" (EC-175, vision 3.3): the EC-174 fragment for a\n * blocks-panel variant. Variants backed by a declared manifest fixture go\n * through `buildFragmentFromRegistryFixture` (the fixture's exact\n * configuration); implicit-default and preset variants fragment the same\n * subtree they insert. Both paths share `buildFragmentFromSubtree`, so the\n * fragment shape — and the paste path — is identical to copying a live canvas\n * instance.\n */\nexport function tileFragment(\n studio: StudioRegistry,\n tile: BlockTile,\n variant: BlockVariant,\n projectId: string,\n): ClipboardFragment | null {\n const manifest = studio.registry.getManifest(tile.componentId)\n const fixture = manifest?.fixtures?.find((candidate) => candidate.name === variant.name)\n if (manifest && fixture) {\n const built = buildFragmentFromRegistryFixture(manifest, fixture, {\n source: { projectId },\n })\n return built.ok ? built.fragment : null\n }\n return buildFragmentFromSubtree(variant.build(), { source: { projectId } })\n}\n\n/**\n * The Insert dialog never dismisses on pointer interactions outside its\n * content. Rows scrolled below the list's fold keep their layout position\n * directly above the modal overlay, so a coordinate-targeted click on a\n * clipped row (agents click what the accessibility tree lists; humans can hit\n * the boundary too) lands on the overlay — with default Radix behaviour that\n * pointerdown dismissed the dialog and silently dropped the insert. Cancel\n * stays explicit: Escape or the Close button.\n */\nexport function preventOutsideDismiss(event: { preventDefault: () => void }): void {\n event.preventDefault()\n}\n\nexport function BlocksPanel({\n context,\n open,\n onOpenChange,\n}: {\n context: InsertContext | null\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const builder = useBuilder()\n const studio = getStudioRegistry()\n const collab = useCollab()\n const clipboard = useStudioClipboard()\n const [query, setQuery] = useState('')\n\n useEffect(() => {\n if (open) setQuery('')\n }, [open])\n\n const slotAllow = context?.slotAllow\n const trimmed = query.trim()\n // EC-439 AD-2: the generated-preview seam (EC-440) — a config-sourced pure\n // lookup, `undefined` until a consumer wires `defineStudio({ previews })`.\n const generatedPreview = resolveDeploymentGeneratedPreviews()\n const groups = useMemo(\n () => blockGroups(studio, { query: trimmed || undefined, slotAllow }, generatedPreview),\n [studio, trimmed, slotAllow, generatedPreview],\n )\n // Layout helpers keep their dedicated group while browsing (hidden once a\n // query narrows the panel to registry matches), filtered by slot.allow.\n const presets = useMemo(\n () => (trimmed ? [] : presetTiles(slotAllow)),\n [trimmed, slotAllow],\n )\n\n // Drag-to-insert (EC-160): a tile is also a drag source. While the drag is\n // live the modal (content + overlay + Radix body pointer lock) gets out of\n // the way so the canvas and the page tree can receive the drop; the session\n // ending restores it — and a successful drop closes the panel. The drop\n // surfaces dispatch the single insert action; the panel only carries the\n // payload (`tile.build`, the same default-subtree model click-insert uses).\n const dragRestoreRef = useRef<(() => void) | null>(null)\n useEffect(\n () =>\n onDragSessionEnd((outcome, session) => {\n if (session.source !== 'blocks') return\n dragRestoreRef.current?.()\n dragRestoreRef.current = null\n if (outcome === 'dropped') onOpenChange(false)\n }),\n [onOpenChange],\n )\n\n if (!context) return null\n\n function insertVariant(variant: BlockVariant) {\n builder.dispatch({ type: 'insert', target: context!.target, node: variant.build() })\n onOpenChange(false)\n }\n\n // \"Copy exact configuration\" (EC-175): same fragment + paste path as\n // copying a live canvas instance, per variant.\n function copyVariant(tile: BlockTile, variant: BlockVariant) {\n const fragment = tileFragment(studio, tile, variant, collab.projectId)\n if (fragment) clipboard.copyFragment(fragment, `${tile.title} — ${variant.name}`)\n }\n\n function handleTileDragStart(tile: BlockTile, e: React.DragEvent) {\n beginDrag(\n {\n subject: { kind: 'component', componentId: tile.componentId },\n source: 'blocks',\n // AD-4: one tile per component — the drag labels the component title.\n label: tile.title,\n buildNode: tile.build,\n },\n e.dataTransfer,\n )\n // Radix's modal layer locks pointer events on the body and the overlay\n // sits above every drop surface. Lift both for the duration of the drag —\n // after a tick, so the browser captures the tile as the drag image first.\n const content = (e.currentTarget as HTMLElement).closest('[role=\"dialog\"]') as HTMLElement | null\n const overlay = (content?.previousElementSibling ?? null) as HTMLElement | null\n const previousBodyPointerEvents = document.body.style.pointerEvents\n const hide = window.setTimeout(() => {\n document.body.style.pointerEvents = 'auto'\n for (const el of [content, overlay]) {\n if (el) {\n el.style.pointerEvents = 'none'\n el.style.opacity = '0'\n }\n }\n }, 0)\n dragRestoreRef.current = () => {\n window.clearTimeout(hide)\n document.body.style.pointerEvents = previousBodyPointerEvents\n for (const el of [content, overlay]) {\n if (el) {\n el.style.pointerEvents = ''\n el.style.opacity = ''\n }\n }\n }\n }\n\n function handleTileDragEnd() {\n // Trailing dragend after a handled drop is a no-op (endDrag is idempotent).\n endDrag('cancelled')\n }\n\n const empty = groups.length === 0 && presets.length === 0\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-3xl\" onInteractOutside={preventOutsideDismiss}>\n <DialogHeader>\n <DialogTitle>{translate('builder.blocksPanel.title')}</DialogTitle>\n <DialogDescription>\n Adds into <Code tone=\"muted\">{context.destinationLabel}</Code>.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n autoFocus\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('builder.blocksPanel.searchPlaceholder')}\n aria-label={translate('builder.blocksPanel.searchAria')}\n className=\"pl-8\"\n />\n </div>\n\n {/* Viewport-bounded so the dialog never extends past the fold on short windows\n (a grid taller than the screen would put tiles outside the clickable area). */}\n <div className=\"max-h-[min(30rem,calc(100dvh-14rem))] overflow-auto pr-1\">\n {presets.length > 0 ? (\n <TileGroup\n heading={translate('builder.blocksPanel.layoutHelpers')}\n tiles={presets}\n onInsert={insertVariant}\n onCopy={copyVariant}\n onTileDragStart={handleTileDragStart}\n onTileDragEnd={handleTileDragEnd}\n />\n ) : null}\n {groups.map((group) => (\n <TileGroup\n key={group.category}\n heading={group.category}\n tiles={group.tiles}\n onInsert={insertVariant}\n onCopy={copyVariant}\n onTileDragStart={handleTileDragStart}\n onTileDragEnd={handleTileDragEnd}\n />\n ))}\n {empty ? (\n <Text size=\"sm\" tone=\"muted\" className=\"p-3\">\n {slotAllow\n ? translate('builder.blocksPanel.noAllowed')\n : translate('builder.blocksPanel.noMatch', { query })}\n </Text>\n ) : null}\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction TileGroup({\n heading,\n tiles,\n onInsert,\n onCopy,\n onTileDragStart,\n onTileDragEnd,\n}: {\n heading: string\n tiles: BlockTile[]\n onInsert: (variant: BlockVariant) => void\n onCopy: (tile: BlockTile, variant: BlockVariant) => void\n onTileDragStart: (tile: BlockTile, e: React.DragEvent) => void\n onTileDragEnd: () => void\n}) {\n return (\n <section className=\"mb-3\" aria-label={heading}>\n <Row gap=\"2\" className=\"px-1 py-1.5\">\n <Code tone=\"muted\" className=\"capitalize\">\n {heading}\n </Code>\n <Text size=\"xs\" tone=\"muted\">\n {tiles.length}\n </Text>\n </Row>\n <div className=\"grid grid-cols-3 gap-2\">\n {tiles.map((tile) => (\n <BlockTileButton\n key={tile.key}\n tile={tile}\n onInsert={onInsert}\n onCopy={onCopy}\n onDragStart={(e) => onTileDragStart(tile, e)}\n onDragEnd={onTileDragEnd}\n />\n ))}\n </div>\n </section>\n )\n}\n\n/**\n * One component tile (exported for markup tests).\n *\n * EC-463: the tile's insert affordance is a REAL `<button>`. It used to be a\n * `div[role=\"button\"]` with a hand-rolled Enter/Space handler, because a tile\n * carries two more controls (copy-exact, the variants chip) and nested\n * `<button>`s are invalid HTML — a nested interactive control is also an\n * accessibility failure, not just a validator complaint. The fix is the standard\n * \"stretched primary action\" card: the button is an inset overlay UNDER the\n * visual content, the content is `pointer-events-none`, and the two secondary\n * controls sit above it. One frame, identical layout, native activation\n * semantics — and `find role button` resolves it like every other control.\n */\nexport function BlockTileButton({\n tile,\n onInsert,\n onCopy,\n onDragStart,\n onDragEnd,\n}: {\n tile: BlockTile\n onInsert: (variant: BlockVariant) => void\n onCopy: (tile: BlockTile, variant: BlockVariant) => void\n onDragStart: (e: React.DragEvent) => void\n onDragEnd: () => void\n}) {\n const degraded = tile.issues.length > 0\n const defaultVariant = tile.variants[0]!\n return (\n <div\n draggable\n onDragStart={onDragStart}\n onDragEnd={onDragEnd}\n data-block-tile={tile.key}\n data-block-degraded={degraded || undefined}\n className={cn(\n // AD-3: this outer border is the tile's ONE frame — the preview inset\n // below is deliberately borderless.\n 'group/tile relative flex cursor-pointer flex-col gap-1.5 rounded-lg border bg-card p-2 text-left transition-colors',\n 'hover:border-ring/60 hover:bg-muted/60',\n degraded ? 'border-amber-500/50' : 'border-border',\n )}\n >\n {/* The tile's primary action, covering the whole frame. */}\n <button\n type=\"button\"\n data-block-insert={tile.key}\n onClick={() => onInsert(defaultVariant)}\n aria-label={translate('builder.blocksPanel.insertComponentAria', { title: tile.title })}\n title={tile.description}\n className=\"absolute inset-0 z-0 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-ring/60\"\n />\n {/* Copy exact configuration (EC-175/vision 3.3): the EC-174 fragment of\n the tile's DEFAULT variant; per-fixture copies live in the flyout. */}\n <button\n type=\"button\"\n aria-label={translate('builder.blocksPanel.copyExactAria', {\n title: tile.title,\n variant: defaultVariant.name,\n })}\n title={translate('builder.blocksPanel.copyExactConfiguration')}\n data-block-copy={tile.key}\n onClick={() => onCopy(tile, defaultVariant)}\n className={cn(\n 'absolute right-1.5 top-1.5 z-20 rounded-md border border-border bg-popover/90 p-1 text-muted-foreground',\n 'opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover/tile:opacity-100',\n )}\n >\n <ClipboardCopy className=\"size-3.5\" aria-hidden />\n </button>\n {/* AD-2 preview resolution: curated/generated artifact, else the\n icon+name placeholder — never an in-document live render. The visual\n body never takes the pointer: clicks belong to the insert button. */}\n <div className=\"pointer-events-none relative z-10 flex flex-col gap-1.5\">\n {tile.previewUrl ? (\n <PreviewImageThumbnail src={tile.previewUrl} alt={tile.title} />\n ) : (\n <PlaceholderThumbnail title={tile.title} />\n )}\n <Row gap=\"1\" className=\"w-full min-w-0\">\n <span className=\"truncate text-xs font-medium\">{tile.title}</span>\n {tile.variants.length > 1 ? (\n <VariantsChip tile={tile} onInsert={onInsert} onCopy={onCopy} />\n ) : null}\n </Row>\n {degraded ? (\n <Row gap=\"1\" className=\"w-full min-w-0 text-amber-700 dark:text-amber-400\">\n <AlertTriangle aria-hidden className=\"size-3 shrink-0\" />\n <span className=\"truncate text-[0.7rem]\" title={tile.issues.join('\\n')}>\n {tile.issues[0]}\n </span>\n </Row>\n ) : null}\n </div>\n </div>\n )\n}\n\n/**\n * The compact variant affordance (EC-439 AD-1): an \"n variants\" chip in the\n * tile footer opening a flyout of per-fixture rows. When the active search\n * query matched fixture names ({@link BlockTile.matchedVariants}) the chip is\n * highlighted so \"found by fixture name\" is visible at the grid.\n */\nfunction VariantsChip({\n tile,\n onInsert,\n onCopy,\n}: {\n tile: BlockTile\n onInsert: (variant: BlockVariant) => void\n onCopy: (tile: BlockTile, variant: BlockVariant) => void\n}) {\n const matched = (tile.matchedVariants?.length ?? 0) > 0\n // The tile already surfaces the DEFAULT variant's issues; the chip warns\n // when a deeper variant is degraded so it is visible before the flyout opens.\n const laterVariantDegraded = tile.variants.slice(1).some((variant) => variant.issues.length > 0)\n return (\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n data-block-variants={tile.key}\n data-block-variants-match={matched || undefined}\n aria-label={translate('builder.blocksPanel.variantsAria', {\n count: tile.variants.length,\n title: tile.title,\n })}\n title={laterVariantDegraded ? translate('builder.blocksPanel.variantIssuesHint') : undefined}\n className={cn(\n // EC-463: the tile body is `pointer-events-none` so the stretched\n // insert button underneath owns plain clicks — the chip opts back in.\n 'pointer-events-auto relative z-20',\n 'ml-auto flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[0.65rem] leading-none transition-colors',\n matched\n ? 'bg-primary/15 text-primary'\n : 'bg-muted text-muted-foreground hover:bg-muted/70 hover:text-foreground',\n )}\n >\n {laterVariantDegraded ? (\n <AlertTriangle aria-hidden className=\"size-2.5 text-amber-600 dark:text-amber-400\" />\n ) : null}\n {translate('builder.blocksPanel.variantCount', { count: tile.variants.length })}\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-64 p-1.5\" onClick={(event) => event.stopPropagation()}>\n <Text size=\"xs\" tone=\"muted\" className=\"px-1.5 pb-1 pt-0.5\">\n {translate('builder.blocksPanel.variantsHeading')}\n </Text>\n <VariantRows tile={tile} onInsert={onInsert} onCopy={onCopy} />\n </PopoverContent>\n </Popover>\n )\n}\n\n/**\n * The flyout's variant rows (exported for markup tests): one insert row +\n * copy-exact button per fixture (EC-175 semantics per variant). Rows matched\n * by the active fixture-name search are highlighted.\n */\nexport function VariantRows({\n tile,\n onInsert,\n onCopy,\n}: {\n tile: BlockTile\n onInsert: (variant: BlockVariant) => void\n onCopy: (tile: BlockTile, variant: BlockVariant) => void\n}) {\n return (\n <div className=\"flex flex-col gap-0.5\">\n {tile.variants.map((variant) => {\n const matched = tile.matchedVariants?.includes(variant.name) ?? false\n const variantDegraded = variant.issues.length > 0\n return (\n <Row key={variant.name} gap=\"1\" className=\"w-full min-w-0\">\n <button\n type=\"button\"\n data-block-variant={`${tile.componentId}::${variant.name}`}\n data-block-variant-match={matched || undefined}\n data-block-variant-degraded={variantDegraded || undefined}\n aria-label={translate('builder.blocksPanel.insertTileAria', {\n title: tile.title,\n variant: variant.name,\n })}\n title={variantDegraded ? variant.issues.join('\\n') : undefined}\n onClick={() => onInsert(variant)}\n className={cn(\n 'flex min-w-0 flex-1 items-center gap-2 rounded-md px-1.5 py-1 text-left text-xs transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none',\n matched ? 'text-primary' : 'text-foreground',\n )}\n >\n {variant.previewUrl ? (\n <img\n src={variant.previewUrl}\n alt=\"\"\n draggable={false}\n className=\"pointer-events-none h-7 w-11 shrink-0 select-none rounded bg-muted/40 object-cover\"\n />\n ) : null}\n <span className=\"truncate\">{variant.name}</span>\n {variantDegraded ? (\n <AlertTriangle\n aria-hidden\n className=\"ml-auto size-3 shrink-0 text-amber-600 dark:text-amber-400\"\n />\n ) : null}\n </button>\n <button\n type=\"button\"\n data-block-variant-copy={`${tile.componentId}::${variant.name}`}\n aria-label={translate('builder.blocksPanel.copyExactAria', {\n title: tile.title,\n variant: variant.name,\n })}\n title={translate('builder.blocksPanel.copyExactConfiguration')}\n onClick={() => onCopy(tile, variant)}\n className=\"shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:bg-muted focus-visible:outline-none\"\n >\n <ClipboardCopy className=\"size-3.5\" aria-hidden />\n </button>\n </Row>\n )\n })}\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"blocks-panel.js","sourceRoot":"","sources":["../../../src/components/builder/blocks-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, ClipboardCopy, Search } from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type { InsertTarget } from '@elytracms/core/project-graph'\nimport {\n buildFragmentFromRegistryFixture,\n buildFragmentFromSubtree,\n} from '@elytracms/core/clipboard'\nimport type { ClipboardFragment } from '@elytracms/core/clipboard'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { resolveDeploymentGeneratedPreviews } from '@elytracms/studio/lib/workspace'\nimport { useProjectSession } from '../project-session'\nimport { useStudioClipboard } from './clipboard-actions'\nimport { beginDrag, endDrag, onDragSessionEnd } from './drag-session'\nimport {\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n Input,\n Popover,\n PopoverContent,\n PopoverTrigger,\n Row,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useBuilder } from './builder-store'\nimport { blockGroups, presetTiles } from './blocks-model'\nimport type { BlockTile, BlockVariant } from './blocks-model'\nimport { PlaceholderThumbnail, PreviewImageThumbnail } from './fixture-thumbnail'\n\n/**\n * Blocks panel (EC-164, redesigned by EC-439): the visual insert surface.\n * Every canvas-eligible registry component is ONE tile; a component with 2+\n * fixtures carries a compact \"n variants\" chip opening a flyout with one\n * insert/copy row per fixture. Clicking a tile (or dragging it) inserts the\n * DEFAULT — the first declared fixture's configured subtree, else the seeded\n * manifest default — through the builder's `insert` action (one history entry).\n * Previews are artifacts (curated `previewImage` → generated artifact →\n * icon+name placeholder), never in-document runtime renders.\n */\n\nexport interface InsertContext {\n target: InsertTarget\n /** When set, only these component ids may be inserted into the slot (slot.allow). */\n slotAllow?: string[]\n /** Human label of where the insert lands. */\n destinationLabel: string\n}\n\n/**\n * \"Copy exact configuration\" (EC-175, vision 3.3): the EC-174 fragment for a\n * blocks-panel variant. Variants backed by a declared manifest fixture go\n * through `buildFragmentFromRegistryFixture` (the fixture's exact\n * configuration); implicit-default and preset variants fragment the same\n * subtree they insert. Both paths share `buildFragmentFromSubtree`, so the\n * fragment shape — and the paste path — is identical to copying a live canvas\n * instance.\n */\nexport function tileFragment(\n studio: StudioRegistry,\n tile: BlockTile,\n variant: BlockVariant,\n projectId: string,\n): ClipboardFragment | null {\n const manifest = studio.registry.getManifest(tile.componentId)\n const fixture = manifest?.fixtures?.find((candidate) => candidate.name === variant.name)\n if (manifest && fixture) {\n const built = buildFragmentFromRegistryFixture(manifest, fixture, {\n source: { projectId },\n })\n return built.ok ? built.fragment : null\n }\n return buildFragmentFromSubtree(variant.build(), { source: { projectId } })\n}\n\n/**\n * The Insert dialog never dismisses on pointer interactions outside its\n * content. Rows scrolled below the list's fold keep their layout position\n * directly above the modal overlay, so a coordinate-targeted click on a\n * clipped row (agents click what the accessibility tree lists; humans can hit\n * the boundary too) lands on the overlay — with default Radix behaviour that\n * pointerdown dismissed the dialog and silently dropped the insert. Cancel\n * stays explicit: Escape or the Close button.\n */\nexport function preventOutsideDismiss(event: { preventDefault: () => void }): void {\n event.preventDefault()\n}\n\nexport function BlocksPanel({\n context,\n open,\n onOpenChange,\n}: {\n context: InsertContext | null\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const builder = useBuilder()\n const studio = getStudioRegistry()\n const { projectId } = useProjectSession()\n const clipboard = useStudioClipboard()\n const [query, setQuery] = useState('')\n\n useEffect(() => {\n if (open) setQuery('')\n }, [open])\n\n const slotAllow = context?.slotAllow\n const trimmed = query.trim()\n // EC-439 AD-2: the generated-preview seam (EC-440) — a config-sourced pure\n // lookup, `undefined` until a consumer wires `defineStudio({ previews })`.\n const generatedPreview = resolveDeploymentGeneratedPreviews()\n const groups = useMemo(\n () => blockGroups(studio, { query: trimmed || undefined, slotAllow }, generatedPreview),\n [studio, trimmed, slotAllow, generatedPreview],\n )\n // Layout helpers keep their dedicated group while browsing (hidden once a\n // query narrows the panel to registry matches), filtered by slot.allow.\n const presets = useMemo(\n () => (trimmed ? [] : presetTiles(slotAllow)),\n [trimmed, slotAllow],\n )\n\n // Drag-to-insert (EC-160): a tile is also a drag source. While the drag is\n // live the modal (content + overlay + Radix body pointer lock) gets out of\n // the way so the canvas and the page tree can receive the drop; the session\n // ending restores it — and a successful drop closes the panel. The drop\n // surfaces dispatch the single insert action; the panel only carries the\n // payload (`tile.build`, the same default-subtree model click-insert uses).\n const dragRestoreRef = useRef<(() => void) | null>(null)\n useEffect(\n () =>\n onDragSessionEnd((outcome, session) => {\n if (session.source !== 'blocks') return\n dragRestoreRef.current?.()\n dragRestoreRef.current = null\n if (outcome === 'dropped') onOpenChange(false)\n }),\n [onOpenChange],\n )\n\n if (!context) return null\n\n function insertVariant(variant: BlockVariant) {\n builder.dispatch({ type: 'insert', target: context!.target, node: variant.build() })\n onOpenChange(false)\n }\n\n // \"Copy exact configuration\" (EC-175): same fragment + paste path as\n // copying a live canvas instance, per variant.\n function copyVariant(tile: BlockTile, variant: BlockVariant) {\n const fragment = tileFragment(studio, tile, variant, projectId)\n if (fragment) clipboard.copyFragment(fragment, `${tile.title} — ${variant.name}`)\n }\n\n function handleTileDragStart(tile: BlockTile, e: React.DragEvent) {\n beginDrag(\n {\n subject: { kind: 'component', componentId: tile.componentId },\n source: 'blocks',\n // AD-4: one tile per component — the drag labels the component title.\n label: tile.title,\n buildNode: tile.build,\n },\n e.dataTransfer,\n )\n // Radix's modal layer locks pointer events on the body and the overlay\n // sits above every drop surface. Lift both for the duration of the drag —\n // after a tick, so the browser captures the tile as the drag image first.\n const content = (e.currentTarget as HTMLElement).closest('[role=\"dialog\"]') as HTMLElement | null\n const overlay = (content?.previousElementSibling ?? null) as HTMLElement | null\n const previousBodyPointerEvents = document.body.style.pointerEvents\n const hide = window.setTimeout(() => {\n document.body.style.pointerEvents = 'auto'\n for (const el of [content, overlay]) {\n if (el) {\n el.style.pointerEvents = 'none'\n el.style.opacity = '0'\n }\n }\n }, 0)\n dragRestoreRef.current = () => {\n window.clearTimeout(hide)\n document.body.style.pointerEvents = previousBodyPointerEvents\n for (const el of [content, overlay]) {\n if (el) {\n el.style.pointerEvents = ''\n el.style.opacity = ''\n }\n }\n }\n }\n\n function handleTileDragEnd() {\n // Trailing dragend after a handled drop is a no-op (endDrag is idempotent).\n endDrag('cancelled')\n }\n\n const empty = groups.length === 0 && presets.length === 0\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-3xl\" onInteractOutside={preventOutsideDismiss}>\n <DialogHeader>\n <DialogTitle>{translate('builder.blocksPanel.title')}</DialogTitle>\n <DialogDescription>\n Adds into <Code tone=\"muted\">{context.destinationLabel}</Code>.\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n autoFocus\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('builder.blocksPanel.searchPlaceholder')}\n aria-label={translate('builder.blocksPanel.searchAria')}\n className=\"pl-8\"\n />\n </div>\n\n {/* Viewport-bounded so the dialog never extends past the fold on short windows\n (a grid taller than the screen would put tiles outside the clickable area). */}\n <div className=\"max-h-[min(30rem,calc(100dvh-14rem))] overflow-auto pr-1\">\n {presets.length > 0 ? (\n <TileGroup\n heading={translate('builder.blocksPanel.layoutHelpers')}\n tiles={presets}\n onInsert={insertVariant}\n onCopy={copyVariant}\n onTileDragStart={handleTileDragStart}\n onTileDragEnd={handleTileDragEnd}\n />\n ) : null}\n {groups.map((group) => (\n <TileGroup\n key={group.category}\n heading={group.category}\n tiles={group.tiles}\n onInsert={insertVariant}\n onCopy={copyVariant}\n onTileDragStart={handleTileDragStart}\n onTileDragEnd={handleTileDragEnd}\n />\n ))}\n {empty ? (\n <Text size=\"sm\" tone=\"muted\" className=\"p-3\">\n {slotAllow\n ? translate('builder.blocksPanel.noAllowed')\n : translate('builder.blocksPanel.noMatch', { query })}\n </Text>\n ) : null}\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction TileGroup({\n heading,\n tiles,\n onInsert,\n onCopy,\n onTileDragStart,\n onTileDragEnd,\n}: {\n heading: string\n tiles: BlockTile[]\n onInsert: (variant: BlockVariant) => void\n onCopy: (tile: BlockTile, variant: BlockVariant) => void\n onTileDragStart: (tile: BlockTile, e: React.DragEvent) => void\n onTileDragEnd: () => void\n}) {\n return (\n <section className=\"mb-3\" aria-label={heading}>\n <Row gap=\"2\" className=\"px-1 py-1.5\">\n <Code tone=\"muted\" className=\"capitalize\">\n {heading}\n </Code>\n <Text size=\"xs\" tone=\"muted\">\n {tiles.length}\n </Text>\n </Row>\n <div className=\"grid grid-cols-3 gap-2\">\n {tiles.map((tile) => (\n <BlockTileButton\n key={tile.key}\n tile={tile}\n onInsert={onInsert}\n onCopy={onCopy}\n onDragStart={(e) => onTileDragStart(tile, e)}\n onDragEnd={onTileDragEnd}\n />\n ))}\n </div>\n </section>\n )\n}\n\n/**\n * One component tile (exported for markup tests).\n *\n * EC-463: the tile's insert affordance is a REAL `<button>`. It used to be a\n * `div[role=\"button\"]` with a hand-rolled Enter/Space handler, because a tile\n * carries two more controls (copy-exact, the variants chip) and nested\n * `<button>`s are invalid HTML — a nested interactive control is also an\n * accessibility failure, not just a validator complaint. The fix is the standard\n * \"stretched primary action\" card: the button is an inset overlay UNDER the\n * visual content, the content is `pointer-events-none`, and the two secondary\n * controls sit above it. One frame, identical layout, native activation\n * semantics — and `find role button` resolves it like every other control.\n */\nexport function BlockTileButton({\n tile,\n onInsert,\n onCopy,\n onDragStart,\n onDragEnd,\n}: {\n tile: BlockTile\n onInsert: (variant: BlockVariant) => void\n onCopy: (tile: BlockTile, variant: BlockVariant) => void\n onDragStart: (e: React.DragEvent) => void\n onDragEnd: () => void\n}) {\n const degraded = tile.issues.length > 0\n const defaultVariant = tile.variants[0]!\n return (\n <div\n draggable\n onDragStart={onDragStart}\n onDragEnd={onDragEnd}\n data-block-tile={tile.key}\n data-block-degraded={degraded || undefined}\n className={cn(\n // AD-3: this outer border is the tile's ONE frame — the preview inset\n // below is deliberately borderless.\n 'group/tile relative flex cursor-pointer flex-col gap-1.5 rounded-lg border bg-card p-2 text-left transition-colors',\n 'hover:border-ring/60 hover:bg-muted/60',\n degraded ? 'border-amber-500/50' : 'border-border',\n )}\n >\n {/* The tile's primary action, covering the whole frame. */}\n <button\n type=\"button\"\n data-block-insert={tile.key}\n onClick={() => onInsert(defaultVariant)}\n aria-label={translate('builder.blocksPanel.insertComponentAria', { title: tile.title })}\n title={tile.description}\n className=\"absolute inset-0 z-0 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-ring/60\"\n />\n {/* Copy exact configuration (EC-175/vision 3.3): the EC-174 fragment of\n the tile's DEFAULT variant; per-fixture copies live in the flyout. */}\n <button\n type=\"button\"\n aria-label={translate('builder.blocksPanel.copyExactAria', {\n title: tile.title,\n variant: defaultVariant.name,\n })}\n title={translate('builder.blocksPanel.copyExactConfiguration')}\n data-block-copy={tile.key}\n onClick={() => onCopy(tile, defaultVariant)}\n className={cn(\n 'absolute right-1.5 top-1.5 z-20 rounded-md border border-border bg-popover/90 p-1 text-muted-foreground',\n 'opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover/tile:opacity-100',\n )}\n >\n <ClipboardCopy className=\"size-3.5\" aria-hidden />\n </button>\n {/* AD-2 preview resolution: curated/generated artifact, else the\n icon+name placeholder — never an in-document live render. The visual\n body never takes the pointer: clicks belong to the insert button. */}\n <div className=\"pointer-events-none relative z-10 flex flex-col gap-1.5\">\n {tile.previewUrl ? (\n <PreviewImageThumbnail src={tile.previewUrl} alt={tile.title} />\n ) : (\n <PlaceholderThumbnail title={tile.title} />\n )}\n <Row gap=\"1\" className=\"w-full min-w-0\">\n <span className=\"truncate text-xs font-medium\">{tile.title}</span>\n {tile.variants.length > 1 ? (\n <VariantsChip tile={tile} onInsert={onInsert} onCopy={onCopy} />\n ) : null}\n </Row>\n {degraded ? (\n <Row gap=\"1\" className=\"w-full min-w-0 text-amber-700 dark:text-amber-400\">\n <AlertTriangle aria-hidden className=\"size-3 shrink-0\" />\n <span className=\"truncate text-[0.7rem]\" title={tile.issues.join('\\n')}>\n {tile.issues[0]}\n </span>\n </Row>\n ) : null}\n </div>\n </div>\n )\n}\n\n/**\n * The compact variant affordance (EC-439 AD-1): an \"n variants\" chip in the\n * tile footer opening a flyout of per-fixture rows. When the active search\n * query matched fixture names ({@link BlockTile.matchedVariants}) the chip is\n * highlighted so \"found by fixture name\" is visible at the grid.\n */\nfunction VariantsChip({\n tile,\n onInsert,\n onCopy,\n}: {\n tile: BlockTile\n onInsert: (variant: BlockVariant) => void\n onCopy: (tile: BlockTile, variant: BlockVariant) => void\n}) {\n const matched = (tile.matchedVariants?.length ?? 0) > 0\n // The tile already surfaces the DEFAULT variant's issues; the chip warns\n // when a deeper variant is degraded so it is visible before the flyout opens.\n const laterVariantDegraded = tile.variants.slice(1).some((variant) => variant.issues.length > 0)\n return (\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n data-block-variants={tile.key}\n data-block-variants-match={matched || undefined}\n aria-label={translate('builder.blocksPanel.variantsAria', {\n count: tile.variants.length,\n title: tile.title,\n })}\n title={laterVariantDegraded ? translate('builder.blocksPanel.variantIssuesHint') : undefined}\n className={cn(\n // EC-463: the tile body is `pointer-events-none` so the stretched\n // insert button underneath owns plain clicks — the chip opts back in.\n 'pointer-events-auto relative z-20',\n 'ml-auto flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[0.65rem] leading-none transition-colors',\n matched\n ? 'bg-primary/15 text-primary'\n : 'bg-muted text-muted-foreground hover:bg-muted/70 hover:text-foreground',\n )}\n >\n {laterVariantDegraded ? (\n <AlertTriangle aria-hidden className=\"size-2.5 text-amber-600 dark:text-amber-400\" />\n ) : null}\n {translate('builder.blocksPanel.variantCount', { count: tile.variants.length })}\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-64 p-1.5\" onClick={(event) => event.stopPropagation()}>\n <Text size=\"xs\" tone=\"muted\" className=\"px-1.5 pb-1 pt-0.5\">\n {translate('builder.blocksPanel.variantsHeading')}\n </Text>\n <VariantRows tile={tile} onInsert={onInsert} onCopy={onCopy} />\n </PopoverContent>\n </Popover>\n )\n}\n\n/**\n * The flyout's variant rows (exported for markup tests): one insert row +\n * copy-exact button per fixture (EC-175 semantics per variant). Rows matched\n * by the active fixture-name search are highlighted.\n */\nexport function VariantRows({\n tile,\n onInsert,\n onCopy,\n}: {\n tile: BlockTile\n onInsert: (variant: BlockVariant) => void\n onCopy: (tile: BlockTile, variant: BlockVariant) => void\n}) {\n return (\n <div className=\"flex flex-col gap-0.5\">\n {tile.variants.map((variant) => {\n const matched = tile.matchedVariants?.includes(variant.name) ?? false\n const variantDegraded = variant.issues.length > 0\n return (\n <Row key={variant.name} gap=\"1\" className=\"w-full min-w-0\">\n <button\n type=\"button\"\n data-block-variant={`${tile.componentId}::${variant.name}`}\n data-block-variant-match={matched || undefined}\n data-block-variant-degraded={variantDegraded || undefined}\n aria-label={translate('builder.blocksPanel.insertTileAria', {\n title: tile.title,\n variant: variant.name,\n })}\n title={variantDegraded ? variant.issues.join('\\n') : undefined}\n onClick={() => onInsert(variant)}\n className={cn(\n 'flex min-w-0 flex-1 items-center gap-2 rounded-md px-1.5 py-1 text-left text-xs transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none',\n matched ? 'text-primary' : 'text-foreground',\n )}\n >\n {variant.previewUrl ? (\n <img\n src={variant.previewUrl}\n alt=\"\"\n draggable={false}\n className=\"pointer-events-none h-7 w-11 shrink-0 select-none rounded bg-muted/40 object-cover\"\n />\n ) : null}\n <span className=\"truncate\">{variant.name}</span>\n {variantDegraded ? (\n <AlertTriangle\n aria-hidden\n className=\"ml-auto size-3 shrink-0 text-amber-600 dark:text-amber-400\"\n />\n ) : null}\n </button>\n <button\n type=\"button\"\n data-block-variant-copy={`${tile.componentId}::${variant.name}`}\n aria-label={translate('builder.blocksPanel.copyExactAria', {\n title: tile.title,\n variant: variant.name,\n })}\n title={translate('builder.blocksPanel.copyExactConfiguration')}\n onClick={() => onCopy(tile, variant)}\n className=\"shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:bg-muted focus-visible:outline-none\"\n >\n <ClipboardCopy className=\"size-3.5\" aria-hidden />\n </button>\n </Row>\n )\n })}\n </div>\n )\n}\n"]}
@@ -121,10 +121,32 @@ export declare function makeNodeIdCounter(start: number): Counter;
121
121
  * properties, not render behavior.
122
122
  */
123
123
  export declare function builderReducer(state: BuilderState, action: Action): BuilderState;
124
+ /**
125
+ * The composition FIELD whose value the store edits (EC-554). Static for the
126
+ * store's lifetime and deliberately NOT reducer state: it is the declaration the
127
+ * edited tree is judged against, not part of the tree. It travels WITH the store
128
+ * so every placement surface under the provider — the canvas and page-tree
129
+ * drag-and-drop, paste (canvas, tree, keyboard), and the insert offer — reads the
130
+ * same value from the one context it already requires; none can be mounted
131
+ * without it, and `BuilderProvider` cannot be mounted without declaring it.
132
+ */
133
+ export interface CompositionFieldScope {
134
+ /** The field's display label — what a refusal notice names (e.g. "Canvas"). */
135
+ label: string;
136
+ /**
137
+ * The field's top-level `allow` vocabulary (EC-188; a closed set per EC-544),
138
+ * or `undefined` when the field declares none (any registered component).
139
+ * Applied only through `fieldAllowAt` (`lib/dnd/drop-rules`), which scopes it
140
+ * to direct children of the composition root.
141
+ */
142
+ allow: readonly string[] | undefined;
143
+ }
124
144
  export interface BuilderStore extends BuilderState {
125
145
  canUndo: boolean;
126
146
  canRedo: boolean;
127
147
  selectedNode: ComponentNode | null;
148
+ /** The composition field being edited (EC-554) — see {@link CompositionFieldScope}. */
149
+ field: CompositionFieldScope;
128
150
  dispatch: (action: Action) => void;
129
151
  }
130
152
  declare const useBuilder: () => BuilderStore;
@@ -137,9 +159,11 @@ export declare function initialBuilderState(composition: ComponentNode): Builder
137
159
  * its committed composition into the open CMS document via `onChange`
138
160
  * (`composition-field-editor`); the document's own Save persists it as content.
139
161
  */
140
- export declare function BuilderProvider({ composition, children, }: {
162
+ export declare function BuilderProvider({ composition, field, children, }: {
141
163
  /** The composition root to edit (the composition-field editor's synthetic root). */
142
164
  composition: ComponentNode;
165
+ /** The field the composition is the value of (EC-554) — required, never defaulted. */
166
+ field: CompositionFieldScope;
143
167
  children: ReactNode;
144
168
  }): import("react").JSX.Element;
145
169
  export { useBuilder };
@@ -186,7 +186,7 @@ export function initialBuilderState(composition) {
186
186
  * its committed composition into the open CMS document via `onChange`
187
187
  * (`composition-field-editor`); the document's own Save persists it as content.
188
188
  */
189
- export function BuilderProvider({ composition, children, }) {
189
+ export function BuilderProvider({ composition, field, children, }) {
190
190
  // EC-287: nothing persists a graph — committed edits are lifted into the CMS
191
191
  // document by the composition-field editor's `onChange`. `useReducer` drives the
192
192
  // pure `builderReducer` (consecutive dispatches in one gesture apply in sequence);
@@ -201,9 +201,10 @@ export function BuilderProvider({ composition, children, }) {
201
201
  canUndo: state.past.length > 0,
202
202
  canRedo: state.future.length > 0,
203
203
  selectedNode,
204
+ field,
204
205
  dispatch,
205
206
  };
206
- }, [state, dispatch]);
207
+ }, [state, field, dispatch]);
207
208
  return _jsx(BuilderContext.Provider, { value: store, children: children });
208
209
  }
209
210
  export { useBuilder };
@@ -1 +1 @@
1
- {"version":3,"file":"builder-store.js","sourceRoot":"","sources":["../../../src/components/builder/builder-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useReducer } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n cloneWithNewIds,\n duplicateNode,\n getNode,\n getParentInfo,\n insertNode,\n maxNodeIdSeq,\n moveNode,\n normalizeContainers,\n removeNode,\n removeNodeProp,\n reorderNode,\n replaceNode,\n setNodeProp,\n} from '@elytracms/core/project-graph'\nimport type { ComponentNode, InsertTarget, PropValue } from '@elytracms/core/project-graph'\nimport { applyWrap } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\n\nconst HISTORY_LIMIT = 100\n\n/**\n * A canvas preview-width key (EC-250): `'full'` (unconstrained) or a configured\n * breakpoint name (`'sm'`, `'md'`, …). Dynamic because the set is sourced from\n * the consuming site's breakpoints, so it is a string, not a fixed union — the\n * switcher resolves the available options via {@link resolveCanvasViewports}.\n */\nexport type Viewport = string\n\n/**\n * The builder store's action union. EC-287 retired the DB-resident project graph\n * and its persistence mirror, so these actions are pure in-memory editor state —\n * no action maps to a persisted `graph.*` command anymore. The block-field editor\n * lifts committed edits into the CMS document via `onChange`\n * (`composition-field-editor`); nothing persists a graph.\n */\nexport type BuilderAction =\n | { type: 'select'; id: string | null }\n | { type: 'hover'; id: string | null }\n | { type: 'viewport'; viewport: Viewport }\n | { type: 'undo' }\n | { type: 'redo' }\n | { type: 'insert'; target: InsertTarget; node: ComponentNode; select?: boolean }\n | { type: 'remove'; id: string }\n | { type: 'duplicate'; id: string }\n | { type: 'reorder'; id: string; delta: -1 | 1 }\n | { type: 'move'; id: string; target: InsertTarget }\n /**\n * EC-282 reuse gesture: replace a node with zero or more nodes at its\n * position (clone-with-fresh-ids, like `insert`). Promote → 1 embed node;\n * detach → the referenced element's inline blocks. One atomic structural\n * edit, one undo step.\n */\n | { type: 'replace'; id: string; nodes: ComponentNode[]; select?: boolean }\n /**\n * EC-162 layout gesture: wrap the reference node and the dragged subject in\n * a two-column Grid container as ONE atomic structural edit (one undo step).\n */\n | {\n type: 'wrap'\n referenceId: string\n side: 'left' | 'right'\n subject: { kind: 'node'; nodeId: string } | { kind: 'component'; node: ComponentNode }\n }\n | { type: 'setProp'; id: string; name: string; value: PropValue; coalesceKey?: string }\n | { type: 'removeProp'; id: string; name: string }\n\nexport interface BuilderState {\n /**\n * The edited composition: one root-bearing component tree (EC-287). The canvas\n * wraps a page document's `body` blocks into a synthetic root and edits it\n * directly — no page/layout wrapper. Undo/redo snapshot this root.\n */\n past: ComponentNode[]\n present: ComponentNode\n future: ComponentNode[]\n selectedId: string | null\n hoveredId: string | null\n viewport: Viewport\n /** Monotonic counter feeding fresh node ids. */\n nodeSeq: number\n /** Coalescing key of the last edit (consecutive same-key edits don't stack history). */\n lastCoalesceKey: string | null\n}\n\ntype Action = BuilderAction\n\nexport interface Counter {\n genId: () => string\n count: number\n}\n/**\n * The single fresh-node-id counter (`node-<n>`), shared by the reducer's structural\n * actions AND the clipboard paste path (`clipboard-actions.tsx`) so the paste-issue\n * deep links can mirror the exact ids the reducer will mint — one implementation,\n * no drift. `count` mutates as `genId` is called, so the caller reads it afterward\n * to advance `nodeSeq`.\n */\nexport function makeNodeIdCounter(start: number): Counter {\n const c: Counter = {\n count: start,\n genId: () => {\n c.count += 1\n return `node-${c.count}`\n },\n }\n return c\n}\n\n/**\n * Push a new graph onto history, coalescing consecutive edits with the same key.\n *\n * Every commit normalizes layout containers (EC-162 ledger fix): the\n * operations layer normalizes every persisted `graph.*` write (EC-161), so\n * the optimistic working graph must apply the same `normalizeContainers` pass\n * or the UI state diverges from the persisted revision after\n * container-emptying moves/removes. The mirrored command stays un-normalized\n * (`graphInvocationFor` sees the raw action); both sides normalize\n * identically, so they land on the same graph.\n */\nfunction commit(\n state: BuilderState,\n root: ComponentNode,\n opts: { coalesceKey?: string; nodeSeq?: number; selectedId?: string | null } = {},\n): BuilderState {\n const normalized = normalizeContainers(root)\n const present = normalized\n const coalesce = opts.coalesceKey != null && opts.coalesceKey === state.lastCoalesceKey\n // Normalization can dissolve the node a caller wanted selected (e.g. the\n // parent container of a removed child) — selection never points at a ghost.\n const desired = opts.selectedId !== undefined ? opts.selectedId : state.selectedId\n const selectedId = desired !== null && getNode(normalized, desired) ? desired : null\n return {\n ...state,\n past: coalesce ? state.past : [...state.past, state.present].slice(-HISTORY_LIMIT),\n present,\n future: [],\n nodeSeq: opts.nodeSeq ?? state.nodeSeq,\n lastCoalesceKey: opts.coalesceKey ?? null,\n selectedId,\n }\n}\n\n/**\n * The pure state transition. Exported (with `initialBuilderState`) for unit\n * tests — gesture determinism and normalization lockstep are reducer-level\n * properties, not render behavior.\n */\nexport function builderReducer(state: BuilderState, action: Action): BuilderState {\n const root = state.present\n switch (action.type) {\n case 'select':\n return { ...state, selectedId: action.id }\n case 'hover':\n return state.hoveredId === action.id ? state : { ...state, hoveredId: action.id }\n case 'viewport':\n return { ...state, viewport: action.viewport }\n\n case 'undo': {\n const prev = state.past.at(-1)\n if (!prev) return state\n return {\n ...state,\n past: state.past.slice(0, -1),\n present: prev,\n future: [state.present, ...state.future],\n lastCoalesceKey: null,\n selectedId: getNode(prev, state.selectedId ?? '') ? state.selectedId : null,\n }\n }\n case 'redo': {\n const next = state.future[0]\n if (!next) return state\n return {\n ...state,\n past: [...state.past, state.present],\n present: next,\n future: state.future.slice(1),\n lastCoalesceKey: null,\n selectedId: getNode(next, state.selectedId ?? '') ? state.selectedId : null,\n }\n }\n\n case 'insert': {\n const c = makeNodeIdCounter(state.nodeSeq)\n const node = cloneWithNewIds(action.node, c.genId)\n const next = insertNode(root, action.target, node)\n return commit(state, next, {\n nodeSeq: c.count,\n selectedId: action.select === false ? state.selectedId : node.id,\n })\n }\n case 'remove': {\n const parent = getParentInfo(root, action.id)\n const next = removeNode(root, action.id)\n const selectedId =\n state.selectedId === action.id ? (parent?.parent.id ?? null) : state.selectedId\n return commit(state, next, { selectedId })\n }\n case 'duplicate': {\n const c = makeNodeIdCounter(state.nodeSeq)\n const { root: next, newId } = duplicateNode(root, action.id, c.genId)\n return commit(state, next, { nodeSeq: c.count, selectedId: newId ?? state.selectedId })\n }\n case 'reorder':\n return commit(state, reorderNode(root, action.id, action.delta))\n case 'move':\n return commit(state, moveNode(root, action.id, action.target))\n case 'replace': {\n // Mint fresh ids for the incoming nodes (page-local namespace) so a detached\n // copy never collides with the page, and a promoted block's source subtree\n // (kept for the new reusable document) stays untouched.\n const c = makeNodeIdCounter(state.nodeSeq)\n const cloned = action.nodes.map((n) => cloneWithNewIds(n, c.genId))\n const next = replaceNode(root, action.id, cloned)\n if (next === root) return state // root or absent id — no-op\n return commit(state, next, {\n nodeSeq: c.count,\n selectedId: action.select === false ? state.selectedId : (cloned[0]?.id ?? state.selectedId),\n })\n }\n case 'wrap': {\n // Layout gesture (EC-162): the reference and the dragged subject become\n // the two columns of a new Grid container — ONE structural edit, one\n // history entry, one mirrored command, so a single undo dissolves the\n // whole gesture.\n const c = makeNodeIdCounter(state.nodeSeq)\n const partner =\n action.subject.kind === 'node'\n ? { kind: 'node' as const, nodeId: action.subject.nodeId }\n : { kind: 'subtree' as const, node: cloneWithNewIds(action.subject.node, c.genId) }\n const containerId = c.genId()\n const wrapped = applyWrap({\n root,\n referenceId: action.referenceId,\n side: action.side,\n partner,\n containerId,\n })\n if (!wrapped) return state\n return commit(state, wrapped.root, { nodeSeq: c.count, selectedId: containerId })\n }\n case 'setProp':\n return commit(state, setNodeProp(root, action.id, action.name, action.value), {\n coalesceKey: action.coalesceKey,\n })\n case 'removeProp':\n return commit(state, removeNodeProp(root, action.id, action.name))\n default:\n return state\n }\n}\n\nexport interface BuilderStore extends BuilderState {\n canUndo: boolean\n canRedo: boolean\n selectedNode: ComponentNode | null\n dispatch: (action: Action) => void\n}\n\nconst { Context: BuilderContext, useRequired: useBuilder } =\n createRequiredContext<BuilderStore>('Builder')\n\nexport function initialBuilderState(composition: ComponentNode): BuilderState {\n // Start container-normal (idempotent on healthy trees) so the session and the\n // lifted baseline agree from the first edit.\n const present = normalizeContainers(composition)\n return {\n past: [],\n present,\n future: [],\n selectedId: present.id,\n hoveredId: null,\n viewport: 'full',\n // Seed past the ids the composition already carries (EC-203): a restored\n // tree holds minted `node-<n>` ids, so starting at 0 would re-mint `node-1`\n // on the first insert and produce a duplicate-node-id.\n nodeSeq: maxNodeIdSeq(present),\n lastCoalesceKey: null,\n }\n}\n\n/**\n * Undo/redo design (EC-141): the history stack is *UI state* — an in-memory\n * stack of composition-root snapshots. EC-287 retired the DB-resident project\n * graph and its append-only revision log, so the store no longer mirrors edits onto a\n * persistence layer: it is a pure in-memory editor. The block-field editor lifts\n * its committed composition into the open CMS document via `onChange`\n * (`composition-field-editor`); the document's own Save persists it as content.\n */\nexport function BuilderProvider({\n composition,\n children,\n}: {\n /** The composition root to edit (the composition-field editor's synthetic root). */\n composition: ComponentNode\n children: ReactNode\n}) {\n // EC-287: nothing persists a graph — committed edits are lifted into the CMS\n // document by the composition-field editor's `onChange`. `useReducer` drives the\n // pure `builderReducer` (consecutive dispatches in one gesture apply in sequence);\n // the store below decorates the state with the derived undo/redo + selection flags.\n const [state, dispatch] = useReducer(builderReducer, composition, initialBuilderState)\n\n const store = useMemo<BuilderStore>(() => {\n const selectedNode = state.selectedId\n ? (getNode(state.present, state.selectedId) ?? null)\n : null\n return {\n ...state,\n canUndo: state.past.length > 0,\n canRedo: state.future.length > 0,\n selectedNode,\n dispatch,\n }\n }, [state, dispatch])\n\n return <BuilderContext.Provider value={store}>{children}</BuilderContext.Provider>\n}\n\nexport { useBuilder }\n"]}
1
+ {"version":3,"file":"builder-store.js","sourceRoot":"","sources":["../../../src/components/builder/builder-store.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,EACL,eAAe,EACf,aAAa,EACb,OAAO,EACP,aAAa,EACb,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,mBAAmB,EACnB,UAAU,EACV,cAAc,EACd,WAAW,EACX,WAAW,EACX,WAAW,GACZ,MAAM,+BAA+B,CAAA;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAA;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAE9E,MAAM,aAAa,GAAG,GAAG,CAAA;AAwEzB;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,MAAM,CAAC,GAAY;QACjB,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,GAAG,EAAE;YACV,CAAC,CAAC,KAAK,IAAI,CAAC,CAAA;YACZ,OAAO,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAA;QAC1B,CAAC;KACF,CAAA;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,MAAM,CACb,KAAmB,EACnB,IAAmB,EACnB,OAA+E,EAAE;IAEjF,MAAM,UAAU,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;IAC5C,MAAM,OAAO,GAAG,UAAU,CAAA;IAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK,CAAC,eAAe,CAAA;IACvF,yEAAyE;IACzE,4EAA4E;IAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAA;IAClF,MAAM,UAAU,GAAG,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;IACpF,OAAO;QACL,GAAG,KAAK;QACR,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC;QAClF,OAAO;QACP,MAAM,EAAE,EAAE;QACV,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO;QACtC,eAAe,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;QACzC,UAAU;KACX,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAmB,EAAE,MAAc;IAChE,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAA;IAC1B,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,EAAE,CAAA;QAC5C,KAAK,OAAO;YACV,OAAO,KAAK,CAAC,SAAS,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,EAAE,CAAA;QACnF,KAAK,UAAU;YACb,OAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAA;QAEhD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YAC9B,IAAI,CAAC,IAAI;gBAAE,OAAO,KAAK,CAAA;YACvB,OAAO;gBACL,GAAG,KAAK;gBACR,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC7B,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC;gBACxC,eAAe,EAAE,IAAI;gBACrB,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;aAC5E,CAAA;QACH,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;YAC5B,IAAI,CAAC,IAAI;gBAAE,OAAO,KAAK,CAAA;YACvB,OAAO;gBACL,GAAG,KAAK;gBACR,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC;gBACpC,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBAC7B,eAAe,EAAE,IAAI;gBACrB,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;aAC5E,CAAA;QACH,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YAC1C,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;YAClD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YAClD,OAAO,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE;gBACzB,OAAO,EAAE,CAAC,CAAC,KAAK;gBAChB,UAAU,EAAE,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE;aACjE,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;YAC7C,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;YACxC,MAAM,UAAU,GACd,KAAK,CAAC,UAAU,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAA;YACjF,OAAO,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,CAAC,CAAA;QAC5C,CAAC;QACD,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YAC1C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;YACrE,OAAO,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC,CAAA;QACzF,CAAC;QACD,KAAK,SAAS;YACZ,OAAO,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAClE,KAAK,MAAM;YACT,OAAO,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;QAChE,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,6EAA6E;YAC7E,2EAA2E;YAC3E,wDAAwD;YACxD,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;YACnE,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;YACjD,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAA,CAAC,4BAA4B;YAC5D,OAAO,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE;gBACzB,OAAO,EAAE,CAAC,CAAC,KAAK;gBAChB,UAAU,EAAE,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,KAAK,CAAC,UAAU,CAAC;aAC7F,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,wEAAwE;YACxE,qEAAqE;YACrE,sEAAsE;YACtE,iBAAiB;YACjB,MAAM,CAAC,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YAC1C,MAAM,OAAO,GACX,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM;gBAC5B,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE;gBAC1D,CAAC,CAAC,EAAE,IAAI,EAAE,SAAkB,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAA;YACvF,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,EAAE,CAAA;YAC7B,MAAM,OAAO,GAAG,SAAS,CAAC;gBACxB,IAAI;gBACJ,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,OAAO;gBACP,WAAW;aACZ,CAAC,CAAA;YACF,IAAI,CAAC,OAAO;gBAAE,OAAO,KAAK,CAAA;YAC1B,OAAO,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAA;QACnF,CAAC;QACD,KAAK,SAAS;YACZ,OAAO,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE;gBAC5E,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC,CAAC,CAAA;QACJ,KAAK,YAAY;YACf,OAAO,MAAM,CAAC,KAAK,EAAE,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;QACpE;YACE,OAAO,KAAK,CAAA;IAChB,CAAC;AACH,CAAC;AAgCD,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,GACxD,qBAAqB,CAAe,SAAS,CAAC,CAAA;AAEhD,MAAM,UAAU,mBAAmB,CAAC,WAA0B;IAC5D,8EAA8E;IAC9E,6CAA6C;IAC7C,MAAM,OAAO,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAA;IAChD,OAAO;QACL,IAAI,EAAE,EAAE;QACR,OAAO;QACP,MAAM,EAAE,EAAE;QACV,UAAU,EAAE,OAAO,CAAC,EAAE;QACtB,SAAS,EAAE,IAAI;QACf,QAAQ,EAAE,MAAM;QAChB,yEAAyE;QACzE,4EAA4E;QAC5E,uDAAuD;QACvD,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC;QAC9B,eAAe,EAAE,IAAI;KACtB,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,WAAW,EACX,KAAK,EACL,QAAQ,GAOT;IACC,6EAA6E;IAC7E,iFAAiF;IACjF,mFAAmF;IACnF,oFAAoF;IACpF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,cAAc,EAAE,WAAW,EAAE,mBAAmB,CAAC,CAAA;IAEtF,MAAM,KAAK,GAAG,OAAO,CAAe,GAAG,EAAE;QACvC,MAAM,YAAY,GAAG,KAAK,CAAC,UAAU;YACnC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;YACpD,CAAC,CAAC,IAAI,CAAA;QACR,OAAO;YACL,GAAG,KAAK;YACR,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;YAC9B,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;YAChC,YAAY;YACZ,KAAK;YACL,QAAQ;SACT,CAAA;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE5B,OAAO,KAAC,cAAc,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA2B,CAAA;AACpF,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,CAAA","sourcesContent":["import { useMemo, useReducer } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n cloneWithNewIds,\n duplicateNode,\n getNode,\n getParentInfo,\n insertNode,\n maxNodeIdSeq,\n moveNode,\n normalizeContainers,\n removeNode,\n removeNodeProp,\n reorderNode,\n replaceNode,\n setNodeProp,\n} from '@elytracms/core/project-graph'\nimport type { ComponentNode, InsertTarget, PropValue } from '@elytracms/core/project-graph'\nimport { applyWrap } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\n\nconst HISTORY_LIMIT = 100\n\n/**\n * A canvas preview-width key (EC-250): `'full'` (unconstrained) or a configured\n * breakpoint name (`'sm'`, `'md'`, …). Dynamic because the set is sourced from\n * the consuming site's breakpoints, so it is a string, not a fixed union — the\n * switcher resolves the available options via {@link resolveCanvasViewports}.\n */\nexport type Viewport = string\n\n/**\n * The builder store's action union. EC-287 retired the DB-resident project graph\n * and its persistence mirror, so these actions are pure in-memory editor state —\n * no action maps to a persisted `graph.*` command anymore. The block-field editor\n * lifts committed edits into the CMS document via `onChange`\n * (`composition-field-editor`); nothing persists a graph.\n */\nexport type BuilderAction =\n | { type: 'select'; id: string | null }\n | { type: 'hover'; id: string | null }\n | { type: 'viewport'; viewport: Viewport }\n | { type: 'undo' }\n | { type: 'redo' }\n | { type: 'insert'; target: InsertTarget; node: ComponentNode; select?: boolean }\n | { type: 'remove'; id: string }\n | { type: 'duplicate'; id: string }\n | { type: 'reorder'; id: string; delta: -1 | 1 }\n | { type: 'move'; id: string; target: InsertTarget }\n /**\n * EC-282 reuse gesture: replace a node with zero or more nodes at its\n * position (clone-with-fresh-ids, like `insert`). Promote → 1 embed node;\n * detach → the referenced element's inline blocks. One atomic structural\n * edit, one undo step.\n */\n | { type: 'replace'; id: string; nodes: ComponentNode[]; select?: boolean }\n /**\n * EC-162 layout gesture: wrap the reference node and the dragged subject in\n * a two-column Grid container as ONE atomic structural edit (one undo step).\n */\n | {\n type: 'wrap'\n referenceId: string\n side: 'left' | 'right'\n subject: { kind: 'node'; nodeId: string } | { kind: 'component'; node: ComponentNode }\n }\n | { type: 'setProp'; id: string; name: string; value: PropValue; coalesceKey?: string }\n | { type: 'removeProp'; id: string; name: string }\n\nexport interface BuilderState {\n /**\n * The edited composition: one root-bearing component tree (EC-287). The canvas\n * wraps a page document's `body` blocks into a synthetic root and edits it\n * directly — no page/layout wrapper. Undo/redo snapshot this root.\n */\n past: ComponentNode[]\n present: ComponentNode\n future: ComponentNode[]\n selectedId: string | null\n hoveredId: string | null\n viewport: Viewport\n /** Monotonic counter feeding fresh node ids. */\n nodeSeq: number\n /** Coalescing key of the last edit (consecutive same-key edits don't stack history). */\n lastCoalesceKey: string | null\n}\n\ntype Action = BuilderAction\n\nexport interface Counter {\n genId: () => string\n count: number\n}\n/**\n * The single fresh-node-id counter (`node-<n>`), shared by the reducer's structural\n * actions AND the clipboard paste path (`clipboard-actions.tsx`) so the paste-issue\n * deep links can mirror the exact ids the reducer will mint — one implementation,\n * no drift. `count` mutates as `genId` is called, so the caller reads it afterward\n * to advance `nodeSeq`.\n */\nexport function makeNodeIdCounter(start: number): Counter {\n const c: Counter = {\n count: start,\n genId: () => {\n c.count += 1\n return `node-${c.count}`\n },\n }\n return c\n}\n\n/**\n * Push a new graph onto history, coalescing consecutive edits with the same key.\n *\n * Every commit normalizes layout containers (EC-162 ledger fix): the\n * operations layer normalizes every persisted `graph.*` write (EC-161), so\n * the optimistic working graph must apply the same `normalizeContainers` pass\n * or the UI state diverges from the persisted revision after\n * container-emptying moves/removes. The mirrored command stays un-normalized\n * (`graphInvocationFor` sees the raw action); both sides normalize\n * identically, so they land on the same graph.\n */\nfunction commit(\n state: BuilderState,\n root: ComponentNode,\n opts: { coalesceKey?: string; nodeSeq?: number; selectedId?: string | null } = {},\n): BuilderState {\n const normalized = normalizeContainers(root)\n const present = normalized\n const coalesce = opts.coalesceKey != null && opts.coalesceKey === state.lastCoalesceKey\n // Normalization can dissolve the node a caller wanted selected (e.g. the\n // parent container of a removed child) — selection never points at a ghost.\n const desired = opts.selectedId !== undefined ? opts.selectedId : state.selectedId\n const selectedId = desired !== null && getNode(normalized, desired) ? desired : null\n return {\n ...state,\n past: coalesce ? state.past : [...state.past, state.present].slice(-HISTORY_LIMIT),\n present,\n future: [],\n nodeSeq: opts.nodeSeq ?? state.nodeSeq,\n lastCoalesceKey: opts.coalesceKey ?? null,\n selectedId,\n }\n}\n\n/**\n * The pure state transition. Exported (with `initialBuilderState`) for unit\n * tests — gesture determinism and normalization lockstep are reducer-level\n * properties, not render behavior.\n */\nexport function builderReducer(state: BuilderState, action: Action): BuilderState {\n const root = state.present\n switch (action.type) {\n case 'select':\n return { ...state, selectedId: action.id }\n case 'hover':\n return state.hoveredId === action.id ? state : { ...state, hoveredId: action.id }\n case 'viewport':\n return { ...state, viewport: action.viewport }\n\n case 'undo': {\n const prev = state.past.at(-1)\n if (!prev) return state\n return {\n ...state,\n past: state.past.slice(0, -1),\n present: prev,\n future: [state.present, ...state.future],\n lastCoalesceKey: null,\n selectedId: getNode(prev, state.selectedId ?? '') ? state.selectedId : null,\n }\n }\n case 'redo': {\n const next = state.future[0]\n if (!next) return state\n return {\n ...state,\n past: [...state.past, state.present],\n present: next,\n future: state.future.slice(1),\n lastCoalesceKey: null,\n selectedId: getNode(next, state.selectedId ?? '') ? state.selectedId : null,\n }\n }\n\n case 'insert': {\n const c = makeNodeIdCounter(state.nodeSeq)\n const node = cloneWithNewIds(action.node, c.genId)\n const next = insertNode(root, action.target, node)\n return commit(state, next, {\n nodeSeq: c.count,\n selectedId: action.select === false ? state.selectedId : node.id,\n })\n }\n case 'remove': {\n const parent = getParentInfo(root, action.id)\n const next = removeNode(root, action.id)\n const selectedId =\n state.selectedId === action.id ? (parent?.parent.id ?? null) : state.selectedId\n return commit(state, next, { selectedId })\n }\n case 'duplicate': {\n const c = makeNodeIdCounter(state.nodeSeq)\n const { root: next, newId } = duplicateNode(root, action.id, c.genId)\n return commit(state, next, { nodeSeq: c.count, selectedId: newId ?? state.selectedId })\n }\n case 'reorder':\n return commit(state, reorderNode(root, action.id, action.delta))\n case 'move':\n return commit(state, moveNode(root, action.id, action.target))\n case 'replace': {\n // Mint fresh ids for the incoming nodes (page-local namespace) so a detached\n // copy never collides with the page, and a promoted block's source subtree\n // (kept for the new reusable document) stays untouched.\n const c = makeNodeIdCounter(state.nodeSeq)\n const cloned = action.nodes.map((n) => cloneWithNewIds(n, c.genId))\n const next = replaceNode(root, action.id, cloned)\n if (next === root) return state // root or absent id — no-op\n return commit(state, next, {\n nodeSeq: c.count,\n selectedId: action.select === false ? state.selectedId : (cloned[0]?.id ?? state.selectedId),\n })\n }\n case 'wrap': {\n // Layout gesture (EC-162): the reference and the dragged subject become\n // the two columns of a new Grid container — ONE structural edit, one\n // history entry, one mirrored command, so a single undo dissolves the\n // whole gesture.\n const c = makeNodeIdCounter(state.nodeSeq)\n const partner =\n action.subject.kind === 'node'\n ? { kind: 'node' as const, nodeId: action.subject.nodeId }\n : { kind: 'subtree' as const, node: cloneWithNewIds(action.subject.node, c.genId) }\n const containerId = c.genId()\n const wrapped = applyWrap({\n root,\n referenceId: action.referenceId,\n side: action.side,\n partner,\n containerId,\n })\n if (!wrapped) return state\n return commit(state, wrapped.root, { nodeSeq: c.count, selectedId: containerId })\n }\n case 'setProp':\n return commit(state, setNodeProp(root, action.id, action.name, action.value), {\n coalesceKey: action.coalesceKey,\n })\n case 'removeProp':\n return commit(state, removeNodeProp(root, action.id, action.name))\n default:\n return state\n }\n}\n\n/**\n * The composition FIELD whose value the store edits (EC-554). Static for the\n * store's lifetime and deliberately NOT reducer state: it is the declaration the\n * edited tree is judged against, not part of the tree. It travels WITH the store\n * so every placement surface under the provider — the canvas and page-tree\n * drag-and-drop, paste (canvas, tree, keyboard), and the insert offer — reads the\n * same value from the one context it already requires; none can be mounted\n * without it, and `BuilderProvider` cannot be mounted without declaring it.\n */\nexport interface CompositionFieldScope {\n /** The field's display label — what a refusal notice names (e.g. \"Canvas\"). */\n label: string\n /**\n * The field's top-level `allow` vocabulary (EC-188; a closed set per EC-544),\n * or `undefined` when the field declares none (any registered component).\n * Applied only through `fieldAllowAt` (`lib/dnd/drop-rules`), which scopes it\n * to direct children of the composition root.\n */\n allow: readonly string[] | undefined\n}\n\nexport interface BuilderStore extends BuilderState {\n canUndo: boolean\n canRedo: boolean\n selectedNode: ComponentNode | null\n /** The composition field being edited (EC-554) — see {@link CompositionFieldScope}. */\n field: CompositionFieldScope\n dispatch: (action: Action) => void\n}\n\nconst { Context: BuilderContext, useRequired: useBuilder } =\n createRequiredContext<BuilderStore>('Builder')\n\nexport function initialBuilderState(composition: ComponentNode): BuilderState {\n // Start container-normal (idempotent on healthy trees) so the session and the\n // lifted baseline agree from the first edit.\n const present = normalizeContainers(composition)\n return {\n past: [],\n present,\n future: [],\n selectedId: present.id,\n hoveredId: null,\n viewport: 'full',\n // Seed past the ids the composition already carries (EC-203): a restored\n // tree holds minted `node-<n>` ids, so starting at 0 would re-mint `node-1`\n // on the first insert and produce a duplicate-node-id.\n nodeSeq: maxNodeIdSeq(present),\n lastCoalesceKey: null,\n }\n}\n\n/**\n * Undo/redo design (EC-141): the history stack is *UI state* — an in-memory\n * stack of composition-root snapshots. EC-287 retired the DB-resident project\n * graph and its append-only revision log, so the store no longer mirrors edits onto a\n * persistence layer: it is a pure in-memory editor. The block-field editor lifts\n * its committed composition into the open CMS document via `onChange`\n * (`composition-field-editor`); the document's own Save persists it as content.\n */\nexport function BuilderProvider({\n composition,\n field,\n children,\n}: {\n /** The composition root to edit (the composition-field editor's synthetic root). */\n composition: ComponentNode\n /** The field the composition is the value of (EC-554) — required, never defaulted. */\n field: CompositionFieldScope\n children: ReactNode\n}) {\n // EC-287: nothing persists a graph — committed edits are lifted into the CMS\n // document by the composition-field editor's `onChange`. `useReducer` drives the\n // pure `builderReducer` (consecutive dispatches in one gesture apply in sequence);\n // the store below decorates the state with the derived undo/redo + selection flags.\n const [state, dispatch] = useReducer(builderReducer, composition, initialBuilderState)\n\n const store = useMemo<BuilderStore>(() => {\n const selectedNode = state.selectedId\n ? (getNode(state.present, state.selectedId) ?? null)\n : null\n return {\n ...state,\n canUndo: state.past.length > 0,\n canRedo: state.future.length > 0,\n selectedNode,\n field,\n dispatch,\n }\n }, [state, field, dispatch])\n\n return <BuilderContext.Provider value={store}>{children}</BuilderContext.Provider>\n}\n\nexport { useBuilder }\n"]}
@@ -208,23 +208,6 @@ const CHROME_UI_CSS = `
208
208
  color: rgb(225,228,236) !important;
209
209
  font: 12px/1 ui-sans-serif, system-ui, sans-serif !important; outline: none !important; }
210
210
 
211
- /* ── Locale-fallback chips (EC-176) ─────────────────────────────────────
212
- A node whose bound content is currently served via locale fallback wears a
213
- small corner chip naming the locale it actually came from — the canvas
214
- never silently mixes locales. A pseudo-element cannot carry .ec-chrome, so
215
- every declaration is !important (contract item 3). */
216
- .ec-editable[data-ec-locale-fallback] { position: relative; }
217
- .ec-editable[data-ec-locale-fallback]::after {
218
- content: attr(data-ec-locale-fallback) !important;
219
- position: absolute !important; top: 2px !important; right: 2px !important;
220
- z-index: 5 !important; pointer-events: none !important;
221
- padding: 0 4px !important; border-radius: 4px !important;
222
- font: 600 9px/1.5 ui-sans-serif, system-ui, sans-serif !important;
223
- letter-spacing: 0.05em !important; text-transform: uppercase !important;
224
- color: #fbbf24 !important; background: rgba(180,83,9,0.25) !important;
225
- border: 1px solid rgba(245,158,11,0.45) !important;
226
- }
227
-
228
211
  /* ── Drag & drop states (EC-160/EC-162) ─────────────────────────────────
229
212
  Eligibility comes from the shared pure rules (lib/dnd/drop-rules); these
230
213
  classes only *show* the resolution. Rejection is explicit and visible