@elytracms/studio 0.0.88 → 0.0.91

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 (246) hide show
  1. package/convex/asset-revalidate.test.ts +49 -43
  2. package/convex/cms.ts +3 -4
  3. package/convex/delivery.ts +8 -11
  4. package/convex/document-revalidate.test.ts +188 -0
  5. package/convex/redirect-revalidate.test.ts +50 -37
  6. package/convex/revalidate.ts +18 -30
  7. package/convex/triggers.ts +47 -38
  8. package/convex/webhooks.ts +77 -62
  9. package/dist/components/account/self-account-view-source.js +2 -1
  10. package/dist/components/account/self-account-view-source.js.map +1 -1
  11. package/dist/components/ai/ai-chat-panel.js +1 -1
  12. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  13. package/dist/components/app-views/default-modules.js +3 -3
  14. package/dist/components/app-views/default-modules.js.map +1 -1
  15. package/dist/components/assets/asset-grid.js +1 -1
  16. package/dist/components/assets/asset-grid.js.map +1 -1
  17. package/dist/components/assets/asset-selection.d.ts +8 -9
  18. package/dist/components/assets/asset-selection.js +26 -16
  19. package/dist/components/assets/asset-selection.js.map +1 -1
  20. package/dist/components/assets/assets-collection-view-source.js +3 -2
  21. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  22. package/dist/components/builder/blocks-model.d.ts +7 -5
  23. package/dist/components/builder/blocks-model.js +11 -9
  24. package/dist/components/builder/blocks-model.js.map +1 -1
  25. package/dist/components/builder/blocks-panel.js +1 -1
  26. package/dist/components/builder/blocks-panel.js.map +1 -1
  27. package/dist/components/builder/builder-store.d.ts +11 -3
  28. package/dist/components/builder/builder-store.js +12 -5
  29. package/dist/components/builder/builder-store.js.map +1 -1
  30. package/dist/components/builder/canvas-dnd.d.ts +7 -1
  31. package/dist/components/builder/canvas-dnd.js +6 -3
  32. package/dist/components/builder/canvas-dnd.js.map +1 -1
  33. package/dist/components/builder/canvas-render-context.d.ts +13 -1
  34. package/dist/components/builder/canvas-render-context.js +28 -18
  35. package/dist/components/builder/canvas-render-context.js.map +1 -1
  36. package/dist/components/builder/canvas.js +1 -1
  37. package/dist/components/builder/canvas.js.map +1 -1
  38. package/dist/components/builder/clipboard-actions.d.ts +0 -6
  39. package/dist/components/builder/clipboard-actions.js +9 -4
  40. package/dist/components/builder/clipboard-actions.js.map +1 -1
  41. package/dist/components/builder/composition-field-editor.d.ts +22 -0
  42. package/dist/components/builder/composition-field-editor.js +47 -8
  43. package/dist/components/builder/composition-field-editor.js.map +1 -1
  44. package/dist/components/builder/inline-edit.d.ts +7 -4
  45. package/dist/components/builder/inline-edit.js +11 -8
  46. package/dist/components/builder/inline-edit.js.map +1 -1
  47. package/dist/components/builder/insert-context.js +9 -5
  48. package/dist/components/builder/insert-context.js.map +1 -1
  49. package/dist/components/builder/layout-inspector.d.ts +2 -2
  50. package/dist/components/builder/layout-inspector.js +8 -6
  51. package/dist/components/builder/layout-inspector.js.map +1 -1
  52. package/dist/components/builder/node-factory.d.ts +31 -14
  53. package/dist/components/builder/node-factory.js +73 -93
  54. package/dist/components/builder/node-factory.js.map +1 -1
  55. package/dist/components/builder/page-tree.d.ts +3 -1
  56. package/dist/components/builder/page-tree.js +6 -4
  57. package/dist/components/builder/page-tree.js.map +1 -1
  58. package/dist/components/builder/property-inspector.js +2 -1
  59. package/dist/components/builder/property-inspector.js.map +1 -1
  60. package/dist/components/builder/reuse-context.js +2 -2
  61. package/dist/components/builder/reuse-context.js.map +1 -1
  62. package/dist/components/builder/reuse.d.ts +7 -3
  63. package/dist/components/builder/reuse.js +19 -11
  64. package/dist/components/builder/reuse.js.map +1 -1
  65. package/dist/components/builder/selection-overlay.d.ts +2 -2
  66. package/dist/components/builder/selection-overlay.js +3 -3
  67. package/dist/components/builder/selection-overlay.js.map +1 -1
  68. package/dist/components/builder/spacing-handles.d.ts +2 -2
  69. package/dist/components/builder/spacing-handles.js +3 -3
  70. package/dist/components/builder/spacing-handles.js.map +1 -1
  71. package/dist/components/cms/asset-field.d.ts +1 -1
  72. package/dist/components/cms/asset-field.js +15 -13
  73. package/dist/components/cms/asset-field.js.map +1 -1
  74. package/dist/components/cms/asset-picker-core.js +3 -4
  75. package/dist/components/cms/asset-picker-core.js.map +1 -1
  76. package/dist/components/cms/cell-edit.d.ts +0 -6
  77. package/dist/components/cms/cell-edit.js +5 -23
  78. package/dist/components/cms/cell-edit.js.map +1 -1
  79. package/dist/components/cms/cms-store.d.ts +3 -2
  80. package/dist/components/cms/cms-store.js +23 -24
  81. package/dist/components/cms/cms-store.js.map +1 -1
  82. package/dist/components/cms/collection-table.js +14 -4
  83. package/dist/components/cms/collection-table.js.map +1 -1
  84. package/dist/components/cms/detail-editor.js +28 -24
  85. package/dist/components/cms/detail-editor.js.map +1 -1
  86. package/dist/components/cms/discard-draft.d.ts +1 -2
  87. package/dist/components/cms/discard-draft.js +1 -1
  88. package/dist/components/cms/discard-draft.js.map +1 -1
  89. package/dist/components/cms/duplicate-document.d.ts +3 -2
  90. package/dist/components/cms/duplicate-document.js +5 -5
  91. package/dist/components/cms/duplicate-document.js.map +1 -1
  92. package/dist/components/cms/editable-cell.js +11 -7
  93. package/dist/components/cms/editable-cell.js.map +1 -1
  94. package/dist/components/cms/field-control-lib.d.ts +10 -0
  95. package/dist/components/cms/field-control-lib.js +20 -0
  96. package/dist/components/cms/field-control-lib.js.map +1 -1
  97. package/dist/components/cms/field-control.js +10 -9
  98. package/dist/components/cms/field-control.js.map +1 -1
  99. package/dist/components/cms/object-array-field.js +3 -5
  100. package/dist/components/cms/object-array-field.js.map +1 -1
  101. package/dist/components/cms/prop-fields.d.ts +16 -3
  102. package/dist/components/cms/prop-fields.js +57 -12
  103. package/dist/components/cms/prop-fields.js.map +1 -1
  104. package/dist/components/cms/publish-clean.d.ts +13 -6
  105. package/dist/components/cms/publish-clean.js +14 -6
  106. package/dist/components/cms/publish-clean.js.map +1 -1
  107. package/dist/components/cms/rich-text/embed-props.d.ts +6 -1
  108. package/dist/components/cms/rich-text/embed-props.js +7 -12
  109. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  110. package/dist/components/cms/rich-text/insert.d.ts +13 -6
  111. package/dist/components/cms/rich-text/insert.js +16 -17
  112. package/dist/components/cms/rich-text/insert.js.map +1 -1
  113. package/dist/components/cms/use-cell-edit.js +7 -12
  114. package/dist/components/cms/use-cell-edit.js.map +1 -1
  115. package/dist/components/cms/use-discard-controller.d.ts +1 -2
  116. package/dist/components/cms/use-discard-controller.js +1 -2
  117. package/dist/components/cms/use-discard-controller.js.map +1 -1
  118. package/dist/components/cms/use-publish-controller.js +3 -2
  119. package/dist/components/cms/use-publish-controller.js.map +1 -1
  120. package/dist/components/cms/versions-pane.js +2 -2
  121. package/dist/components/cms/versions-pane.js.map +1 -1
  122. package/dist/components/components/component-detail.d.ts +1 -1
  123. package/dist/components/components/component-detail.js +2 -4
  124. package/dist/components/components/component-detail.js.map +1 -1
  125. package/dist/components/components/component-preview.js +9 -4
  126. package/dist/components/components/component-preview.js.map +1 -1
  127. package/dist/components/components/components-index.d.ts +2 -2
  128. package/dist/components/components/components-index.js +3 -6
  129. package/dist/components/components/components-index.js.map +1 -1
  130. package/dist/components/components/field-inspector.js +5 -2
  131. package/dist/components/components/field-inspector.js.map +1 -1
  132. package/dist/components/components/registry-browser.d.ts +6 -5
  133. package/dist/components/components/registry-browser.js +11 -37
  134. package/dist/components/components/registry-browser.js.map +1 -1
  135. package/dist/components/components/taxonomy.d.ts +14 -21
  136. package/dist/components/components/taxonomy.js +5 -29
  137. package/dist/components/components/taxonomy.js.map +1 -1
  138. package/dist/components/components/transfer-dialog.js +1 -0
  139. package/dist/components/components/transfer-dialog.js.map +1 -1
  140. package/dist/components/components/transfer.d.ts +20 -12
  141. package/dist/components/components/transfer.js +27 -21
  142. package/dist/components/components/transfer.js.map +1 -1
  143. package/dist/components/delivery-health-banner.d.ts +3 -2
  144. package/dist/components/delivery-health-banner.js +3 -2
  145. package/dist/components/delivery-health-banner.js.map +1 -1
  146. package/dist/components/redirects/redirects-collection-view-source.js +3 -2
  147. package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
  148. package/dist/components/users/users-collection-view-source.js +2 -1
  149. package/dist/components/users/users-collection-view-source.js.map +1 -1
  150. package/dist/lib/ai/apply-content.js +32 -28
  151. package/dist/lib/ai/apply-content.js.map +1 -1
  152. package/dist/lib/ai/constraints.d.ts +16 -14
  153. package/dist/lib/ai/constraints.js +64 -48
  154. package/dist/lib/ai/constraints.js.map +1 -1
  155. package/dist/lib/ai/translate-document.js +11 -7
  156. package/dist/lib/ai/translate-document.js.map +1 -1
  157. package/dist/lib/ai/types.d.ts +9 -5
  158. package/dist/lib/ai/types.js.map +1 -1
  159. package/dist/lib/ai/update-fields.js +15 -32
  160. package/dist/lib/ai/update-fields.js.map +1 -1
  161. package/dist/lib/ai/vite.d.ts +22 -2
  162. package/dist/lib/ai/vite.js +12 -3
  163. package/dist/lib/ai/vite.js.map +1 -1
  164. package/dist/lib/assets/asset-placements.d.ts +3 -10
  165. package/dist/lib/assets/asset-placements.js +17 -8
  166. package/dist/lib/assets/asset-placements.js.map +1 -1
  167. package/dist/lib/clipboard/paste-target.js +3 -3
  168. package/dist/lib/clipboard/paste-target.js.map +1 -1
  169. package/dist/lib/cms/published-documents.d.ts +14 -3
  170. package/dist/lib/cms/published-documents.js +12 -3
  171. package/dist/lib/cms/published-documents.js.map +1 -1
  172. package/dist/lib/cms-views/view-model.d.ts +4 -4
  173. package/dist/lib/cms-views/view-model.js +26 -20
  174. package/dist/lib/cms-views/view-model.js.map +1 -1
  175. package/dist/lib/data/collection-content.d.ts +10 -7
  176. package/dist/lib/data/collection-content.js +70 -29
  177. package/dist/lib/data/collection-content.js.map +1 -1
  178. package/dist/lib/delivery/instant-publish.d.ts +0 -3
  179. package/dist/lib/delivery/instant-publish.js +0 -1
  180. package/dist/lib/delivery/instant-publish.js.map +1 -1
  181. package/dist/lib/dnd/drop-rules.d.ts +22 -7
  182. package/dist/lib/dnd/drop-rules.js +21 -14
  183. package/dist/lib/dnd/drop-rules.js.map +1 -1
  184. package/dist/lib/engine-version.d.ts +1 -1
  185. package/dist/lib/engine-version.js +1 -1
  186. package/dist/lib/engine-version.js.map +1 -1
  187. package/dist/lib/i18n/messages.de.js +25 -9
  188. package/dist/lib/i18n/messages.de.js.map +1 -1
  189. package/dist/lib/i18n/messages.en.d.ts +21 -7
  190. package/dist/lib/i18n/messages.en.js +28 -9
  191. package/dist/lib/i18n/messages.en.js.map +1 -1
  192. package/dist/lib/locale/fallback.d.ts +5 -19
  193. package/dist/lib/locale/fallback.js +18 -39
  194. package/dist/lib/locale/fallback.js.map +1 -1
  195. package/dist/lib/locale/locale-store.js +2 -1
  196. package/dist/lib/locale/locale-store.js.map +1 -1
  197. package/dist/lib/operations/cms-persistence.js +7 -4
  198. package/dist/lib/operations/cms-persistence.js.map +1 -1
  199. package/dist/lib/operations/studio-operations.js +4 -6
  200. package/dist/lib/operations/studio-operations.js.map +1 -1
  201. package/dist/lib/persistence/backend-mode.d.ts +5 -25
  202. package/dist/lib/persistence/backend-mode.js +13 -30
  203. package/dist/lib/persistence/backend-mode.js.map +1 -1
  204. package/dist/lib/persona/labels.d.ts +4 -9
  205. package/dist/lib/persona/labels.js +5 -12
  206. package/dist/lib/persona/labels.js.map +1 -1
  207. package/dist/lib/references/reference-model.d.ts +17 -5
  208. package/dist/lib/references/reference-model.js +20 -28
  209. package/dist/lib/references/reference-model.js.map +1 -1
  210. package/dist/lib/references/references-store.js +7 -0
  211. package/dist/lib/references/references-store.js.map +1 -1
  212. package/dist/lib/registry/__fixtures__/demo-components.d.ts +32 -0
  213. package/dist/lib/registry/{project-components.js → __fixtures__/demo-components.js} +18 -5
  214. package/dist/lib/registry/__fixtures__/demo-components.js.map +1 -0
  215. package/dist/lib/registry/index.d.ts +9 -13
  216. package/dist/lib/registry/index.js +34 -61
  217. package/dist/lib/registry/index.js.map +1 -1
  218. package/dist/lib/registry/preview.d.ts +22 -7
  219. package/dist/lib/registry/preview.js +26 -89
  220. package/dist/lib/registry/preview.js.map +1 -1
  221. package/dist/lib/workspace/index.d.ts +0 -7
  222. package/dist/lib/workspace/index.js +0 -9
  223. package/dist/lib/workspace/index.js.map +1 -1
  224. package/dist/previews/enumerate.d.ts +23 -27
  225. package/dist/previews/enumerate.js +27 -48
  226. package/dist/previews/enumerate.js.map +1 -1
  227. package/dist/previews/harness.js +1 -1
  228. package/dist/previews/harness.js.map +1 -1
  229. package/package.json +4 -7
  230. package/dist/lib/registry/project-components.d.ts +0 -16
  231. package/dist/lib/registry/project-components.js.map +0 -1
  232. package/dist/lib/webhooks/config.d.ts +0 -15
  233. package/dist/lib/webhooks/config.js +0 -65
  234. package/dist/lib/webhooks/config.js.map +0 -1
  235. package/dist/lib/webhooks/dispatcher.d.ts +0 -70
  236. package/dist/lib/webhooks/dispatcher.js +0 -61
  237. package/dist/lib/webhooks/dispatcher.js.map +0 -1
  238. package/dist/lib/webhooks/index.d.ts +0 -11
  239. package/dist/lib/webhooks/index.js +0 -10
  240. package/dist/lib/webhooks/index.js.map +0 -1
  241. package/dist/lib/webhooks/signing.d.ts +0 -15
  242. package/dist/lib/webhooks/signing.js +0 -24
  243. package/dist/lib/webhooks/signing.js.map +0 -1
  244. package/dist/lib/webhooks/tags.d.ts +0 -25
  245. package/dist/lib/webhooks/tags.js +0 -289
  246. package/dist/lib/webhooks/tags.js.map +0 -1
@@ -50,13 +50,36 @@ export function controlKind(spec) {
50
50
  return 'text';
51
51
  }
52
52
  }
53
+ /**
54
+ * The manifest `default` as the inspector's greyed HINT (EC-591, ratified 2f) — a
55
+ * scalar shown as a placeholder for an absent prop, never as its value: an absent
56
+ * prop is `undefined` and renders the implementation's own default; the manifest
57
+ * `default` only suggests what to type. A `select` hint shows the option's label.
58
+ * Structured defaults (rich text, objects, assets) have no one-line hint → undefined.
59
+ * Pure, so the hint wording is pinned without mounting a control.
60
+ */
61
+ export function propDefaultHint(spec) {
62
+ const value = spec.default;
63
+ if (spec.type === 'select' && typeof value === 'string') {
64
+ const option = spec.options.find((o) => o.value === value);
65
+ return option ? localizedLabel(option.label, option.value) : value;
66
+ }
67
+ if (typeof value === 'string')
68
+ return value === '' ? undefined : value;
69
+ if (typeof value === 'number')
70
+ return String(value);
71
+ if (typeof value === 'boolean')
72
+ return translate(value ? 'common.true' : 'common.false');
73
+ return undefined;
74
+ }
53
75
  /**
54
76
  * EC-387: route a rich-text editor emission to the prop value callbacks.
55
77
  *
56
78
  * The shared {@link RichTextEditor} emits either an emptied-editor `undefined`, the
57
79
  * exact stored envelope (an untouched/reverted edit), or a fresh `{ version, doc }`
58
- * value — never a plain string. An `undefined` clears the prop (reverting to the
59
- * manifest default, matching the asset/relation/object controls); anything else is a
80
+ * value — never a plain string. An `undefined` clears the prop (absent — the
81
+ * implementation's own default, EC-591/2f — matching the asset/relation/object
82
+ * controls); anything else is a
60
83
  * structured `JsonValue` envelope set verbatim. Extracted pure so the wiring — the
61
84
  * exact bug the retired textarea stub had (it wrote a bare string over the envelope) —
62
85
  * is regression-pinned without driving TipTap.
@@ -73,7 +96,12 @@ export function applyRichTextPropChange(next, handlers) {
73
96
  * prop's field-def validator.
74
97
  */
75
98
  export function PropValueControl({ id, name, spec, value, onSet, onClear }) {
76
- const current = isStaticProp(value) ? value.value : spec.default;
99
+ // EC-591 / ratified 2f: an absent prop shows EMPTY — the manifest `default` is a
100
+ // greyed hint (placeholder), never presented as the value. Until EC-591 this read
101
+ // `spec.default` as the current value, so the inspector showed a value the canvas
102
+ // and delivery did not render (round2-C Q9, measured).
103
+ const current = isStaticProp(value) ? value.value : undefined;
104
+ const hint = propDefaultHint(spec);
77
105
  // The `asset`/`relation`/`object` controls delegate to the CMS `FieldControl`,
78
106
  // which reads the ambient `CollectionViewSource` (asset library, relation
79
107
  // candidates, nested form). The component-registry preview surface has no
@@ -96,14 +124,27 @@ export function PropValueControl({ id, name, spec, value, onSet, onClear }) {
96
124
  const readOnly = spec.form?.readOnly === true;
97
125
  let control;
98
126
  if (kind === 'boolean') {
99
- control = (_jsxs("label", { className: "flex h-8 items-center gap-2 text-sm", children: [_jsx("input", { id: id, type: "checkbox", checked: current === true, disabled: readOnly, onChange: (e) => onSet(e.target.checked), className: "size-4 accent-primary disabled:opacity-50" }), _jsx(Text, { size: "sm", tone: "muted", children: current === true ? translate('common.true') : translate('common.false') })] }));
127
+ control = (_jsxs("label", { className: "flex h-8 items-center gap-2 text-sm", children: [_jsx("input", { id: id, type: "checkbox", checked: current === true, disabled: readOnly, onChange: (e) => onSet(e.target.checked), className: "size-4 accent-primary disabled:opacity-50" }), _jsx(Text, { size: "sm", tone: "muted", children: current === undefined
128
+ ? hint !== undefined
129
+ ? translate('builder.inspector.unsetWithHint', { hint })
130
+ : translate('builder.inspector.unset')
131
+ : current === true
132
+ ? translate('common.true')
133
+ : translate('common.false') })] }));
100
134
  }
101
135
  else if (kind === 'select') {
102
- control = (_jsx(Select, { id: id, disabled: readOnly, value: String(current ?? ''), onChange: (e) => {
136
+ control = (_jsxs(Select, { id: id, disabled: readOnly, value: String(current ?? ''), onChange: (e) => {
137
+ // EC-591: the empty entry clears the prop (absent).
138
+ if (e.target.value === '') {
139
+ onClear();
140
+ return;
141
+ }
103
142
  // EC-190: select option values are strings (field-def `options`).
104
143
  const opt = options.find((o) => o.value === e.target.value);
105
144
  onSet(opt ? opt.value : e.target.value);
106
- }, children: options.map((opt) => (_jsx("option", { value: opt.value, children: localizedLabel(opt.label, opt.value) }, opt.value))) }));
145
+ }, children: [_jsx("option", { value: "", children: hint !== undefined
146
+ ? translate('builder.inspector.unsetWithHint', { hint })
147
+ : translate('builder.inspector.unset') }), options.map((opt) => (_jsx("option", { value: opt.value, children: localizedLabel(opt.label, opt.value) }, opt.value)))] }));
107
148
  }
108
149
  else if (kind === 'options') {
109
150
  // EC-393: reuse the EC-295 segmented control the document form renders (via
@@ -113,13 +154,13 @@ export function PropValueControl({ id, name, spec, value, onSet, onClear }) {
113
154
  control = (_jsx(SegmentedOptions, { id: id, field: { ...spec, name }, value: current, onChange: (v) => onSet(v), readOnly: readOnly }));
114
155
  }
115
156
  else if (kind === 'number') {
116
- control = (_jsx(NumberControl, { id: id, current: current, onSet: onSet, onClear: onClear, readOnly: readOnly }));
157
+ control = (_jsx(NumberControl, { id: id, current: current, onSet: onSet, onClear: onClear, readOnly: readOnly, placeholder: hint }));
117
158
  }
118
159
  else if (kind === 'json') {
119
160
  control = _jsx(JsonControl, { id: id, current: current, onSet: onSet, readOnly: readOnly });
120
161
  }
121
162
  else if (kind === 'color') {
122
- control = (_jsxs(Row, { gap: "2", children: [_jsx("input", { id: id, type: "color", disabled: readOnly, value: typeof current === 'string' && /^#[0-9a-f]{6}$/i.test(current) ? current : '#000000', onChange: (e) => onSet(e.target.value), className: "h-8 w-10 shrink-0 rounded-md border border-input bg-background disabled:opacity-50" }), _jsx(Input, { disabled: readOnly, value: typeof current === 'string' ? current : '', onChange: (e) => onSet(e.target.value) })] }));
163
+ control = (_jsxs(Row, { gap: "2", children: [_jsx("input", { id: id, type: "color", disabled: readOnly, value: typeof current === 'string' && /^#[0-9a-f]{6}$/i.test(current) ? current : '#000000', onChange: (e) => onSet(e.target.value), className: "h-8 w-10 shrink-0 rounded-md border border-input bg-background disabled:opacity-50" }), _jsx(Input, { disabled: readOnly, value: typeof current === 'string' ? current : '', placeholder: hint, onChange: (e) => (e.target.value === '' ? onClear() : onSet(e.target.value)) })] }));
123
164
  }
124
165
  else if (kind === 'richtext') {
125
166
  // EC-387: a richText PROP now uses the SAME real editor as the collection-field
@@ -145,7 +186,9 @@ export function PropValueControl({ id, name, spec, value, onSet, onClear }) {
145
186
  label: localizedLabel(spec.form?.label, name) })) : (_jsx(WorkspaceValueFallback, { value: current }));
146
187
  }
147
188
  else if (kind === 'textarea') {
148
- control = (_jsx("textarea", { id: id, rows: 2, disabled: readOnly, value: typeof current === 'string' ? current : '', onChange: (e) => onSet(e.target.value), className: "flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-sm text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none disabled:cursor-not-allowed disabled:opacity-50" }));
189
+ control = (_jsx("textarea", { id: id, rows: 2, disabled: readOnly, value: typeof current === 'string' ? current : '', placeholder: hint,
190
+ // EC-591/2f: emptying the field clears the prop (absent), not an explicit ''.
191
+ onChange: (e) => (e.target.value === '' ? onClear() : onSet(e.target.value)), className: "flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-sm text-foreground shadow-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none disabled:cursor-not-allowed disabled:opacity-50" }));
149
192
  }
150
193
  else if (kind === 'asset' || kind === 'object' || kind === 'relation') {
151
194
  // EC-195/EC-253/EC-262: reuse the CMS field control. `asset` = library picker +
@@ -157,7 +200,9 @@ export function PropValueControl({ id, name, spec, value, onSet, onClear }) {
157
200
  control = hasWorkspaceContext ? (_jsx(FieldDensityProvider, { density: "compact", children: _jsx(FieldControl, { id: id, field: { ...spec, name }, value: current, onChange: (v) => (v === undefined || v === null ? onClear() : onSet(v)), readOnly: readOnly }) })) : (_jsx(WorkspaceValueFallback, { value: current }));
158
201
  }
159
202
  else {
160
- control = (_jsx(Input, { id: id, disabled: readOnly, value: typeof current === 'string' ? current : String(current ?? ''), onChange: (e) => onSet(e.target.value) }));
203
+ control = (_jsx(Input, { id: id, disabled: readOnly, value: typeof current === 'string' ? current : String(current ?? ''), placeholder: hint,
204
+ // EC-591/2f: emptying the field clears the prop (absent), not an explicit ''.
205
+ onChange: (e) => (e.target.value === '' ? onClear() : onSet(e.target.value)) }));
161
206
  }
162
207
  return (_jsxs(Stack, { gap: "1", children: [control, !valid && !readOnly ? (_jsx(Text, { size: "xs", className: "text-destructive", children: translate('builder.inspector.invalidValue') })) : null] }));
163
208
  }
@@ -176,12 +221,12 @@ function WorkspaceValueFallback({ value }) {
176
221
  return (_jsxs(Stack, { gap: "1", children: [_jsx("div", { className: "truncate rounded-md border border-input bg-muted/30 px-2.5 py-1.5 text-sm text-muted-foreground", children: text }), _jsx(Text, { size: "xs", tone: "muted", children: translate('components.preview.propNeedsDocument') })] }));
177
222
  }
178
223
  /** Number input with a local buffer: commits only finite values, never NaN; empty clears the prop. */
179
- function NumberControl({ id, current, onSet, onClear, readOnly, }) {
224
+ function NumberControl({ id, current, onSet, onClear, readOnly, placeholder, }) {
180
225
  const [raw, setRaw] = useState(typeof current === 'number' ? String(current) : '');
181
226
  useEffect(() => {
182
227
  setRaw(typeof current === 'number' ? String(current) : '');
183
228
  }, [current]);
184
- return (_jsx(Input, { id: id, type: "number", disabled: readOnly, value: raw, onChange: (e) => {
229
+ return (_jsx(Input, { id: id, type: "number", disabled: readOnly, value: raw, placeholder: placeholder, onChange: (e) => {
185
230
  const v = e.target.value;
186
231
  setRaw(v);
187
232
  if (v.trim() === '') {
@@ -1 +1 @@
1
- {"version":3,"file":"prop-fields.js","sourceRoot":"","sources":["../../../src/components/cms/prop-fields.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState } from 'react'\nimport { isStaticProp } from '@elytracms/core/project-graph'\nimport type { JsonValue, PropValue } from '@elytracms/core/project-graph'\nimport type { PropField } from '@elytracms/core/component-registry'\nimport { fieldValueValidator } from '@elytracms/core/cms-core'\nimport type { FieldDef, SelectFieldDef, SelectOption } from '@elytracms/core/cms-core'\nimport { Input, Row, Select, Stack, Text } from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { FieldControl } from './field-control'\nimport { SegmentedOptions } from './field-control-lib'\nimport { FieldDensityProvider } from './field-density'\nimport { RichTextEditor } from './rich-text'\nimport { useOptionalCollectionView } from '../collection-view/collection-view-context'\n\n/**\n * The EC-190 \"props-as-fields\" value control — the single renderer for a\n * manifest-declared prop field, shared by the canvas property inspector\n * (`builder/property-inspector.tsx`) and the rich-text component-embed prop editor\n * (`rich-text/embed-props.tsx`). Extracted here so both surfaces render prop fields\n * through *one* control set (asset → library picker, relation → document picker,\n * object → nested form, …) rather than two parallel form systems.\n *\n * It is deliberately store-agnostic: it reads a prop's current value and reports\n * edits through `onSet(rawValue)` / `onClear()`. The caller owns *where* the value\n * lands — the inspector dispatches a builder command; the embed editor rewrites a\n * ProseMirror node attr — and is responsible for wrapping the raw value in a\n * `staticProp` envelope.\n */\n\n/**\n * The inspector control to render for a prop field-def. The `control.kind`\n * vocabulary derives from the field `type` plus the optional `form.control`\n * presentation hint on `text` fields.\n */\nexport type ControlKind =\n | 'text'\n | 'textarea'\n | 'number'\n | 'boolean'\n | 'select'\n | 'options'\n | 'json'\n | 'color'\n | 'richtext'\n | 'asset'\n | 'object'\n | 'relation'\n\nexport function controlKind(spec: PropField): ControlKind {\n switch (spec.type) {\n case 'boolean':\n return 'boolean'\n case 'number':\n return 'number'\n case 'select':\n // EC-393: a single-select prop opting into `form.control: 'options'` renders the\n // SAME segmented button group the document form uses (EC-295), not the native\n // dropdown — parity with `field-control.tsx`. Segmented is single-select only,\n // so a `multiple` select stays on the native control (mirroring field-control).\n if (spec.form?.control === 'options' && !spec.multiple) return 'options'\n return 'select'\n case 'richText':\n return 'richtext'\n // EC-195: an `asset`-typed prop (e.g. the Image `src`) picks from the asset\n // library — never a raw asset id typed into a text box (the EC-192 persona).\n case 'asset':\n return 'asset'\n // EC-253: an `object`/repeater prop reuses the CMS repeater control below.\n case 'object':\n return 'object'\n // EC-262: a `relation`-typed prop (e.g. a ProductGrid's `category`) reuses the\n // CMS relation picker — never a text box that stringifies the ref to \"[object Object]\".\n case 'relation':\n return 'relation'\n case 'text': {\n const c = spec.form?.control\n if (c === 'textarea') return 'textarea'\n if (c === 'color') return 'color'\n if (c === 'json') return 'json'\n return 'text'\n }\n default:\n return 'text'\n }\n}\n\n/**\n * EC-387: route a rich-text editor emission to the prop value callbacks.\n *\n * The shared {@link RichTextEditor} emits either an emptied-editor `undefined`, the\n * exact stored envelope (an untouched/reverted edit), or a fresh `{ version, doc }`\n * value — never a plain string. An `undefined` clears the prop (reverting to the\n * manifest default, matching the asset/relation/object controls); anything else is a\n * structured `JsonValue` envelope set verbatim. Extracted pure so the wiring — the\n * exact bug the retired textarea stub had (it wrote a bare string over the envelope) —\n * is regression-pinned without driving TipTap.\n */\nexport function applyRichTextPropChange(\n next: unknown,\n handlers: { onSet: (value: JsonValue) => void; onClear: () => void },\n): void {\n if (next === undefined) handlers.onClear()\n else handlers.onSet(next as JsonValue)\n}\n\nexport interface PropValueControlProps {\n /** DOM id for the control's primary input (label `htmlFor` target). */\n id: string\n /** The prop's field name — needed to synthesize the `FieldDef` for reused controls. */\n name: string\n /** The manifest prop field-def driving the control kind + validation. */\n spec: PropField\n /** The prop's current value envelope (or `undefined` when unset). */\n value: PropValue | undefined\n /** Commit a new raw value (the caller wraps it in a `staticProp`). */\n onSet: (value: JsonValue) => void\n /** Remove the prop (revert to the manifest default). */\n onClear: () => void\n}\n\n/**\n * Render + edit a single manifest prop value. Store-agnostic: emits through\n * `onSet`/`onClear`. Shows a validation issue when the current value fails the\n * prop's field-def validator.\n */\nexport function PropValueControl({ id, name, spec, value, onSet, onClear }: PropValueControlProps) {\n const current = isStaticProp(value) ? value.value : (spec.default as JsonValue | undefined)\n // The `asset`/`relation`/`object` controls delegate to the CMS `FieldControl`,\n // which reads the ambient `CollectionViewSource` (asset library, relation\n // candidates, nested form). The component-registry preview surface has no\n // document/collection context, so outside a provider those controls must degrade\n // to a read-only value display instead of throwing \"useCollectionView must be\n // used within a <CollectionViewProvider>\" (the Konditorei Components-view crash).\n const hasWorkspaceContext = useOptionalCollectionView() != null\n\n // EC-190: validate with the prop's field-def value validator.\n const valid = current === undefined || fieldValueValidator({ ...spec, name } as FieldDef)(current)\n // EC-190: derive the editor control from the field `type` + optional `form.control`.\n const kind = controlKind(spec)\n // Select options live on the `select` field-def.\n const options: readonly SelectOption[] = spec.type === 'select' ? spec.options : []\n // EC-395: `form.readOnly` parity with the document form. `cms-store.tsx`'s\n // `isFieldReadOnly` reads the identical `field.form?.readOnly === true` check —\n // every control kind below (including the delegated `FieldControl`/\n // `SegmentedOptions` mounts, which previously hardcoded `readOnly={false}`)\n // degrades to the SAME disabled/inert interaction the document form's\n // `FieldControl` renders for a read-only field, not a parallel treatment.\n const readOnly = spec.form?.readOnly === true\n\n let control\n if (kind === 'boolean') {\n control = (\n <label className=\"flex h-8 items-center gap-2 text-sm\">\n <input\n id={id}\n type=\"checkbox\"\n checked={current === true}\n disabled={readOnly}\n onChange={(e) => onSet(e.target.checked)}\n className=\"size-4 accent-primary disabled:opacity-50\"\n />\n <Text size=\"sm\" tone=\"muted\">\n {current === true ? translate('common.true') : translate('common.false')}\n </Text>\n </label>\n )\n } else if (kind === 'select') {\n control = (\n <Select\n id={id}\n disabled={readOnly}\n value={String(current ?? '')}\n onChange={(e) => {\n // EC-190: select option values are strings (field-def `options`).\n const opt = options.find((o) => o.value === e.target.value)\n onSet(opt ? opt.value : e.target.value)\n }}\n >\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {localizedLabel(opt.label, opt.value)}\n </option>\n ))}\n </Select>\n )\n } else if (kind === 'options') {\n // EC-393: reuse the EC-295 segmented control the document form renders (via\n // `field-control.tsx`) — delegation over a parallel dropdown. It reads the\n // field-def's own `options` (label + per-option `description` tooltip); single-\n // select only (picks one, no unset — an unset select keeps the native dropdown).\n control = (\n <SegmentedOptions\n id={id}\n field={{ ...spec, name } as SelectFieldDef}\n value={current}\n onChange={(v) => onSet(v as JsonValue)}\n readOnly={readOnly}\n />\n )\n } else if (kind === 'number') {\n control = (\n <NumberControl id={id} current={current} onSet={onSet} onClear={onClear} readOnly={readOnly} />\n )\n } else if (kind === 'json') {\n control = <JsonControl id={id} current={current} onSet={onSet} readOnly={readOnly} />\n } else if (kind === 'color') {\n control = (\n <Row gap=\"2\">\n <input\n id={id}\n type=\"color\"\n disabled={readOnly}\n value={typeof current === 'string' && /^#[0-9a-f]{6}$/i.test(current) ? current : '#000000'}\n onChange={(e) => onSet(e.target.value)}\n className=\"h-8 w-10 shrink-0 rounded-md border border-input bg-background disabled:opacity-50\"\n />\n <Input\n disabled={readOnly}\n value={typeof current === 'string' ? current : ''}\n onChange={(e) => onSet(e.target.value)}\n />\n </Row>\n )\n } else if (kind === 'richtext') {\n // EC-387: a richText PROP now uses the SAME real editor as the collection-field\n // path (`field-control.tsx` `case 'richText'`) — one editing surface, one\n // `{ version, doc }` value contract, never a second implementation. The retired\n // textarea stub read `typeof current === 'string'`, so a stored envelope showed\n // EMPTY and any edit clobbered the structured document with a bare string\n // (silent data destruction). The editor reads the envelope (or a legacy string,\n // or empty), always emits an envelope, and routes an emptied editor's `undefined`\n // to a prop clear via {@link applyRichTextPropChange}.\n //\n // Like the asset/relation/object controls below, the editor needs the ambient\n // workspace — its asset-image picker reads the REQUIRED collection-view context.\n // The block property inspector (the EC-387 target) is inside the detail editor's\n // `CollectionViewProvider`, so it gets the full editor; the component-registry\n // preview surface (no provider, nothing to edit against) degrades to the same\n // read-only value display the sibling controls use, instead of crashing with\n // \"useCollectionView must be used within a <CollectionViewProvider>\".\n control = hasWorkspaceContext ? (\n <RichTextEditor\n id={id}\n value={current}\n onChange={(next) => applyRichTextPropChange(next, { onSet, onClear })}\n allow={spec.type === 'richText' ? spec.allow : undefined}\n readOnly={readOnly}\n // EC-436: the SAME resolved localized label the prop row renders (the\n // property inspector / embed form's `Field label`) — titles the expand\n // stage \"<label> — Rich text\".\n label={localizedLabel(spec.form?.label, name)}\n />\n ) : (\n <WorkspaceValueFallback value={current} />\n )\n } else if (kind === 'textarea') {\n control = (\n <textarea\n id={id}\n rows={2}\n disabled={readOnly}\n value={typeof current === 'string' ? current : ''}\n onChange={(e) => onSet(e.target.value)}\n className=\"flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-sm text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n />\n )\n } else if (kind === 'asset' || kind === 'object' || kind === 'relation') {\n // EC-195/EC-253/EC-262: reuse the CMS field control. `asset` = library picker +\n // preview; `object` = the nested/repeater form; `relation` = the document picker\n // (resolves the ref to its title, instead of stringifying it to \"[object Object]\").\n // All edit a structured static prop value through the same shared control documents use.\n // Without a workspace context (the Components preview), degrade to a read-only\n // value display so the pane renders instead of throwing.\n control = hasWorkspaceContext ? (\n <FieldDensityProvider density=\"compact\">\n <FieldControl\n id={id}\n field={{ ...spec, name } as FieldDef}\n value={current}\n onChange={(v) => (v === undefined || v === null ? onClear() : onSet(v as JsonValue))}\n readOnly={readOnly}\n />\n </FieldDensityProvider>\n ) : (\n <WorkspaceValueFallback value={current} />\n )\n } else {\n control = (\n <Input\n id={id}\n disabled={readOnly}\n value={typeof current === 'string' ? current : String(current ?? '')}\n onChange={(e) => onSet(e.target.value)}\n />\n )\n }\n\n return (\n <Stack gap=\"1\">\n {control}\n {/* EC-395: a read-only prop's value can't be fixed here — suppress the\n invalid-value flag exactly like the document form suppresses its field\n error on a read-only row (`detail-editor.tsx`'s\n `error={fieldReadOnly ? undefined : ...}`). */}\n {!valid && !readOnly ? (\n <Text size=\"xs\" className=\"text-destructive\">\n {translate('builder.inspector.invalidValue')}\n </Text>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * Read-only stand-in for an `asset`/`relation`/`object` prop when there is no\n * ambient `CollectionViewSource` (the component-registry preview, which has no\n * document/collection context). Shows the current value and a hint that it is\n * editable inside a document — never the live picker, which needs a workspace.\n */\nfunction WorkspaceValueFallback({ value }: { value: JsonValue | undefined }) {\n const text =\n value === undefined || value === null\n ? '—'\n : typeof value === 'string'\n ? value\n : JSON.stringify(value)\n return (\n <Stack gap=\"1\">\n <div className=\"truncate rounded-md border border-input bg-muted/30 px-2.5 py-1.5 text-sm text-muted-foreground\">\n {text}\n </div>\n <Text size=\"xs\" tone=\"muted\">\n {translate('components.preview.propNeedsDocument')}\n </Text>\n </Stack>\n )\n}\n\n/** Number input with a local buffer: commits only finite values, never NaN; empty clears the prop. */\nfunction NumberControl({\n id,\n current,\n onSet,\n onClear,\n readOnly,\n}: {\n id: string\n current: JsonValue | undefined\n onSet: (v: number) => void\n onClear: () => void\n readOnly: boolean\n}) {\n const [raw, setRaw] = useState(typeof current === 'number' ? String(current) : '')\n useEffect(() => {\n setRaw(typeof current === 'number' ? String(current) : '')\n }, [current])\n return (\n <Input\n id={id}\n type=\"number\"\n disabled={readOnly}\n value={raw}\n onChange={(e) => {\n const v = e.target.value\n setRaw(v)\n if (v.trim() === '') {\n onClear()\n return\n }\n const n = Number(v)\n if (Number.isFinite(n)) onSet(n)\n }}\n />\n )\n}\n\n/** JSON textarea with a local buffer: commits only when the text parses; flags parse errors. */\nfunction JsonControl({\n id,\n current,\n onSet,\n readOnly,\n}: {\n id: string\n current: JsonValue | undefined\n onSet: (v: JsonValue) => void\n readOnly: boolean\n}) {\n const [raw, setRaw] = useState(() => JSON.stringify(current ?? null))\n const [error, setError] = useState<string | null>(null)\n useEffect(() => {\n setRaw(JSON.stringify(current ?? null))\n setError(null)\n }, [current])\n return (\n <Stack gap=\"1\">\n <textarea\n id={id}\n rows={3}\n disabled={readOnly}\n value={raw}\n onChange={(e) => {\n const v = e.target.value\n setRaw(v)\n try {\n onSet(JSON.parse(v) as JsonValue)\n setError(null)\n } catch {\n setError(translate('builder.inspector.invalidJson'))\n }\n }}\n className=\"flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 font-mono text-xs text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n />\n {error ? (\n <Text size=\"xs\" className=\"text-destructive\">\n {error}\n </Text>\n ) : null}\n </Stack>\n )\n}\n"]}
1
+ {"version":3,"file":"prop-fields.js","sourceRoot":"","sources":["../../../src/components/cms/prop-fields.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState } from 'react'\nimport { isStaticProp } from '@elytracms/core/project-graph'\nimport type { JsonValue, PropValue } from '@elytracms/core/project-graph'\nimport type { PropField } from '@elytracms/core/component-registry'\nimport { fieldValueValidator } from '@elytracms/core/cms-core'\nimport type { FieldDef, SelectFieldDef, SelectOption } from '@elytracms/core/cms-core'\nimport { Input, Row, Select, Stack, Text } from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { FieldControl } from './field-control'\nimport { SegmentedOptions } from './field-control-lib'\nimport { FieldDensityProvider } from './field-density'\nimport { RichTextEditor } from './rich-text'\nimport { useOptionalCollectionView } from '../collection-view/collection-view-context'\n\n/**\n * The EC-190 \"props-as-fields\" value control — the single renderer for a\n * manifest-declared prop field, shared by the canvas property inspector\n * (`builder/property-inspector.tsx`) and the rich-text component-embed prop editor\n * (`rich-text/embed-props.tsx`). Extracted here so both surfaces render prop fields\n * through *one* control set (asset → library picker, relation → document picker,\n * object → nested form, …) rather than two parallel form systems.\n *\n * It is deliberately store-agnostic: it reads a prop's current value and reports\n * edits through `onSet(rawValue)` / `onClear()`. The caller owns *where* the value\n * lands — the inspector dispatches a builder command; the embed editor rewrites a\n * ProseMirror node attr — and is responsible for wrapping the raw value in a\n * `staticProp` envelope.\n */\n\n/**\n * The inspector control to render for a prop field-def. The `control.kind`\n * vocabulary derives from the field `type` plus the optional `form.control`\n * presentation hint on `text` fields.\n */\nexport type ControlKind =\n | 'text'\n | 'textarea'\n | 'number'\n | 'boolean'\n | 'select'\n | 'options'\n | 'json'\n | 'color'\n | 'richtext'\n | 'asset'\n | 'object'\n | 'relation'\n\nexport function controlKind(spec: PropField): ControlKind {\n switch (spec.type) {\n case 'boolean':\n return 'boolean'\n case 'number':\n return 'number'\n case 'select':\n // EC-393: a single-select prop opting into `form.control: 'options'` renders the\n // SAME segmented button group the document form uses (EC-295), not the native\n // dropdown — parity with `field-control.tsx`. Segmented is single-select only,\n // so a `multiple` select stays on the native control (mirroring field-control).\n if (spec.form?.control === 'options' && !spec.multiple) return 'options'\n return 'select'\n case 'richText':\n return 'richtext'\n // EC-195: an `asset`-typed prop (e.g. the Image `src`) picks from the asset\n // library — never a raw asset id typed into a text box (the EC-192 persona).\n case 'asset':\n return 'asset'\n // EC-253: an `object`/repeater prop reuses the CMS repeater control below.\n case 'object':\n return 'object'\n // EC-262: a `relation`-typed prop (e.g. a ProductGrid's `category`) reuses the\n // CMS relation picker — never a text box that stringifies the ref to \"[object Object]\".\n case 'relation':\n return 'relation'\n case 'text': {\n const c = spec.form?.control\n if (c === 'textarea') return 'textarea'\n if (c === 'color') return 'color'\n if (c === 'json') return 'json'\n return 'text'\n }\n default:\n return 'text'\n }\n}\n\n/**\n * The manifest `default` as the inspector's greyed HINT (EC-591, ratified 2f) — a\n * scalar shown as a placeholder for an absent prop, never as its value: an absent\n * prop is `undefined` and renders the implementation's own default; the manifest\n * `default` only suggests what to type. A `select` hint shows the option's label.\n * Structured defaults (rich text, objects, assets) have no one-line hint → undefined.\n * Pure, so the hint wording is pinned without mounting a control.\n */\nexport function propDefaultHint(spec: PropField): string | undefined {\n const value = spec.default\n if (spec.type === 'select' && typeof value === 'string') {\n const option = spec.options.find((o) => o.value === value)\n return option ? localizedLabel(option.label, option.value) : value\n }\n if (typeof value === 'string') return value === '' ? undefined : value\n if (typeof value === 'number') return String(value)\n if (typeof value === 'boolean') return translate(value ? 'common.true' : 'common.false')\n return undefined\n}\n\n/**\n * EC-387: route a rich-text editor emission to the prop value callbacks.\n *\n * The shared {@link RichTextEditor} emits either an emptied-editor `undefined`, the\n * exact stored envelope (an untouched/reverted edit), or a fresh `{ version, doc }`\n * value — never a plain string. An `undefined` clears the prop (absent — the\n * implementation's own default, EC-591/2f — matching the asset/relation/object\n * controls); anything else is a\n * structured `JsonValue` envelope set verbatim. Extracted pure so the wiring — the\n * exact bug the retired textarea stub had (it wrote a bare string over the envelope) —\n * is regression-pinned without driving TipTap.\n */\nexport function applyRichTextPropChange(\n next: unknown,\n handlers: { onSet: (value: JsonValue) => void; onClear: () => void },\n): void {\n if (next === undefined) handlers.onClear()\n else handlers.onSet(next as JsonValue)\n}\n\nexport interface PropValueControlProps {\n /** DOM id for the control's primary input (label `htmlFor` target). */\n id: string\n /** The prop's field name — needed to synthesize the `FieldDef` for reused controls. */\n name: string\n /** The manifest prop field-def driving the control kind + validation. */\n spec: PropField\n /** The prop's current value envelope (or `undefined` when unset). */\n value: PropValue | undefined\n /** Commit a new raw value (the caller wraps it in a `staticProp`). */\n onSet: (value: JsonValue) => void\n /**\n * Remove the prop — \"clear\" means ABSENT (EC-591/2f): the implementation's own\n * default applies, never the manifest `default` (a hint only).\n */\n onClear: () => void\n}\n\n/**\n * Render + edit a single manifest prop value. Store-agnostic: emits through\n * `onSet`/`onClear`. Shows a validation issue when the current value fails the\n * prop's field-def validator.\n */\nexport function PropValueControl({ id, name, spec, value, onSet, onClear }: PropValueControlProps) {\n // EC-591 / ratified 2f: an absent prop shows EMPTY — the manifest `default` is a\n // greyed hint (placeholder), never presented as the value. Until EC-591 this read\n // `spec.default` as the current value, so the inspector showed a value the canvas\n // and delivery did not render (round2-C Q9, measured).\n const current = isStaticProp(value) ? value.value : undefined\n const hint = propDefaultHint(spec)\n // The `asset`/`relation`/`object` controls delegate to the CMS `FieldControl`,\n // which reads the ambient `CollectionViewSource` (asset library, relation\n // candidates, nested form). The component-registry preview surface has no\n // document/collection context, so outside a provider those controls must degrade\n // to a read-only value display instead of throwing \"useCollectionView must be\n // used within a <CollectionViewProvider>\" (the Konditorei Components-view crash).\n const hasWorkspaceContext = useOptionalCollectionView() != null\n\n // EC-190: validate with the prop's field-def value validator.\n const valid = current === undefined || fieldValueValidator({ ...spec, name } as FieldDef)(current)\n // EC-190: derive the editor control from the field `type` + optional `form.control`.\n const kind = controlKind(spec)\n // Select options live on the `select` field-def.\n const options: readonly SelectOption[] = spec.type === 'select' ? spec.options : []\n // EC-395: `form.readOnly` parity with the document form. `cms-store.tsx`'s\n // `isFieldReadOnly` reads the identical `field.form?.readOnly === true` check —\n // every control kind below (including the delegated `FieldControl`/\n // `SegmentedOptions` mounts, which previously hardcoded `readOnly={false}`)\n // degrades to the SAME disabled/inert interaction the document form's\n // `FieldControl` renders for a read-only field, not a parallel treatment.\n const readOnly = spec.form?.readOnly === true\n\n let control\n if (kind === 'boolean') {\n control = (\n <label className=\"flex h-8 items-center gap-2 text-sm\">\n <input\n id={id}\n type=\"checkbox\"\n checked={current === true}\n disabled={readOnly}\n onChange={(e) => onSet(e.target.checked)}\n className=\"size-4 accent-primary disabled:opacity-50\"\n />\n <Text size=\"sm\" tone=\"muted\">\n {current === undefined\n ? hint !== undefined\n ? translate('builder.inspector.unsetWithHint', { hint })\n : translate('builder.inspector.unset')\n : current === true\n ? translate('common.true')\n : translate('common.false')}\n </Text>\n </label>\n )\n } else if (kind === 'select') {\n control = (\n <Select\n id={id}\n disabled={readOnly}\n value={String(current ?? '')}\n onChange={(e) => {\n // EC-591: the empty entry clears the prop (absent).\n if (e.target.value === '') {\n onClear()\n return\n }\n // EC-190: select option values are strings (field-def `options`).\n const opt = options.find((o) => o.value === e.target.value)\n onSet(opt ? opt.value : e.target.value)\n }}\n >\n <option value=\"\">\n {hint !== undefined\n ? translate('builder.inspector.unsetWithHint', { hint })\n : translate('builder.inspector.unset')}\n </option>\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {localizedLabel(opt.label, opt.value)}\n </option>\n ))}\n </Select>\n )\n } else if (kind === 'options') {\n // EC-393: reuse the EC-295 segmented control the document form renders (via\n // `field-control.tsx`) — delegation over a parallel dropdown. It reads the\n // field-def's own `options` (label + per-option `description` tooltip); single-\n // select only (picks one, no unset — an unset select keeps the native dropdown).\n control = (\n <SegmentedOptions\n id={id}\n field={{ ...spec, name } as SelectFieldDef}\n value={current}\n onChange={(v) => onSet(v as JsonValue)}\n readOnly={readOnly}\n />\n )\n } else if (kind === 'number') {\n control = (\n <NumberControl\n id={id}\n current={current}\n onSet={onSet}\n onClear={onClear}\n readOnly={readOnly}\n placeholder={hint}\n />\n )\n } else if (kind === 'json') {\n control = <JsonControl id={id} current={current} onSet={onSet} readOnly={readOnly} />\n } else if (kind === 'color') {\n control = (\n <Row gap=\"2\">\n <input\n id={id}\n type=\"color\"\n disabled={readOnly}\n value={typeof current === 'string' && /^#[0-9a-f]{6}$/i.test(current) ? current : '#000000'}\n onChange={(e) => onSet(e.target.value)}\n className=\"h-8 w-10 shrink-0 rounded-md border border-input bg-background disabled:opacity-50\"\n />\n <Input\n disabled={readOnly}\n value={typeof current === 'string' ? current : ''}\n placeholder={hint}\n onChange={(e) => (e.target.value === '' ? onClear() : onSet(e.target.value))}\n />\n </Row>\n )\n } else if (kind === 'richtext') {\n // EC-387: a richText PROP now uses the SAME real editor as the collection-field\n // path (`field-control.tsx` `case 'richText'`) — one editing surface, one\n // `{ version, doc }` value contract, never a second implementation. The retired\n // textarea stub read `typeof current === 'string'`, so a stored envelope showed\n // EMPTY and any edit clobbered the structured document with a bare string\n // (silent data destruction). The editor reads the envelope (or a legacy string,\n // or empty), always emits an envelope, and routes an emptied editor's `undefined`\n // to a prop clear via {@link applyRichTextPropChange}.\n //\n // Like the asset/relation/object controls below, the editor needs the ambient\n // workspace — its asset-image picker reads the REQUIRED collection-view context.\n // The block property inspector (the EC-387 target) is inside the detail editor's\n // `CollectionViewProvider`, so it gets the full editor; the component-registry\n // preview surface (no provider, nothing to edit against) degrades to the same\n // read-only value display the sibling controls use, instead of crashing with\n // \"useCollectionView must be used within a <CollectionViewProvider>\".\n control = hasWorkspaceContext ? (\n <RichTextEditor\n id={id}\n value={current}\n onChange={(next) => applyRichTextPropChange(next, { onSet, onClear })}\n allow={spec.type === 'richText' ? spec.allow : undefined}\n readOnly={readOnly}\n // EC-436: the SAME resolved localized label the prop row renders (the\n // property inspector / embed form's `Field label`) — titles the expand\n // stage \"<label> — Rich text\".\n label={localizedLabel(spec.form?.label, name)}\n />\n ) : (\n <WorkspaceValueFallback value={current} />\n )\n } else if (kind === 'textarea') {\n control = (\n <textarea\n id={id}\n rows={2}\n disabled={readOnly}\n value={typeof current === 'string' ? current : ''}\n placeholder={hint}\n // EC-591/2f: emptying the field clears the prop (absent), not an explicit ''.\n onChange={(e) => (e.target.value === '' ? onClear() : onSet(e.target.value))}\n className=\"flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-sm text-foreground shadow-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n />\n )\n } else if (kind === 'asset' || kind === 'object' || kind === 'relation') {\n // EC-195/EC-253/EC-262: reuse the CMS field control. `asset` = library picker +\n // preview; `object` = the nested/repeater form; `relation` = the document picker\n // (resolves the ref to its title, instead of stringifying it to \"[object Object]\").\n // All edit a structured static prop value through the same shared control documents use.\n // Without a workspace context (the Components preview), degrade to a read-only\n // value display so the pane renders instead of throwing.\n control = hasWorkspaceContext ? (\n <FieldDensityProvider density=\"compact\">\n <FieldControl\n id={id}\n field={{ ...spec, name } as FieldDef}\n value={current}\n onChange={(v) => (v === undefined || v === null ? onClear() : onSet(v as JsonValue))}\n readOnly={readOnly}\n />\n </FieldDensityProvider>\n ) : (\n <WorkspaceValueFallback value={current} />\n )\n } else {\n control = (\n <Input\n id={id}\n disabled={readOnly}\n value={typeof current === 'string' ? current : String(current ?? '')}\n placeholder={hint}\n // EC-591/2f: emptying the field clears the prop (absent), not an explicit ''.\n onChange={(e) => (e.target.value === '' ? onClear() : onSet(e.target.value))}\n />\n )\n }\n\n return (\n <Stack gap=\"1\">\n {control}\n {/* EC-395: a read-only prop's value can't be fixed here — suppress the\n invalid-value flag exactly like the document form suppresses its field\n error on a read-only row (`detail-editor.tsx`'s\n `error={fieldReadOnly ? undefined : ...}`). */}\n {!valid && !readOnly ? (\n <Text size=\"xs\" className=\"text-destructive\">\n {translate('builder.inspector.invalidValue')}\n </Text>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * Read-only stand-in for an `asset`/`relation`/`object` prop when there is no\n * ambient `CollectionViewSource` (the component-registry preview, which has no\n * document/collection context). Shows the current value and a hint that it is\n * editable inside a document — never the live picker, which needs a workspace.\n */\nfunction WorkspaceValueFallback({ value }: { value: JsonValue | undefined }) {\n const text =\n value === undefined || value === null\n ? '—'\n : typeof value === 'string'\n ? value\n : JSON.stringify(value)\n return (\n <Stack gap=\"1\">\n <div className=\"truncate rounded-md border border-input bg-muted/30 px-2.5 py-1.5 text-sm text-muted-foreground\">\n {text}\n </div>\n <Text size=\"xs\" tone=\"muted\">\n {translate('components.preview.propNeedsDocument')}\n </Text>\n </Stack>\n )\n}\n\n/** Number input with a local buffer: commits only finite values, never NaN; empty clears the prop. */\nfunction NumberControl({\n id,\n current,\n onSet,\n onClear,\n readOnly,\n placeholder,\n}: {\n id: string\n current: JsonValue | undefined\n onSet: (v: number) => void\n onClear: () => void\n readOnly: boolean\n /** The manifest `default` as a greyed hint (EC-591/2f), never the value. */\n placeholder?: string\n}) {\n const [raw, setRaw] = useState(typeof current === 'number' ? String(current) : '')\n useEffect(() => {\n setRaw(typeof current === 'number' ? String(current) : '')\n }, [current])\n return (\n <Input\n id={id}\n type=\"number\"\n disabled={readOnly}\n value={raw}\n placeholder={placeholder}\n onChange={(e) => {\n const v = e.target.value\n setRaw(v)\n if (v.trim() === '') {\n onClear()\n return\n }\n const n = Number(v)\n if (Number.isFinite(n)) onSet(n)\n }}\n />\n )\n}\n\n/** JSON textarea with a local buffer: commits only when the text parses; flags parse errors. */\nfunction JsonControl({\n id,\n current,\n onSet,\n readOnly,\n}: {\n id: string\n current: JsonValue | undefined\n onSet: (v: JsonValue) => void\n readOnly: boolean\n}) {\n const [raw, setRaw] = useState(() => JSON.stringify(current ?? null))\n const [error, setError] = useState<string | null>(null)\n useEffect(() => {\n setRaw(JSON.stringify(current ?? null))\n setError(null)\n }, [current])\n return (\n <Stack gap=\"1\">\n <textarea\n id={id}\n rows={3}\n disabled={readOnly}\n value={raw}\n onChange={(e) => {\n const v = e.target.value\n setRaw(v)\n try {\n onSet(JSON.parse(v) as JsonValue)\n setError(null)\n } catch {\n setError(translate('builder.inspector.invalidJson'))\n }\n }}\n className=\"flex w-full rounded-md border border-input bg-background px-2.5 py-1.5 font-mono text-xs text-foreground shadow-sm focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 outline-none disabled:cursor-not-allowed disabled:opacity-50\"\n />\n {error ? (\n <Text size=\"xs\" className=\"text-destructive\">\n {error}\n </Text>\n ) : null}\n </Stack>\n )\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { CmsDocument, CollectionDef } from '@elytracms/core/cms-core';
1
+ import type { CmsDocument, CollectionDef, LocaleConfig } from '@elytracms/core/cms-core';
2
2
  import type { ManifestResolver } from '@elytracms/core/component-registry';
3
3
  /**
4
4
  * EC-425 — THE publish-time hidden-strip, extracted so single-document publish
@@ -6,16 +6,23 @@ import type { ManifestResolver } from '@elytracms/core/component-registry';
6
6
  * share ONE clean step instead of the bulk bar bypassing it:
7
7
  *
8
8
  * - EC-273 `clearHiddenValues`: drop conditionally-hidden document field values
9
- * (predicate-hidden, non-`retainWhenHidden`) from `values`/`localized`.
9
+ * (predicate-hidden, non-`retainWhenHidden`) from `values`/`localized`, each
10
+ * judged in the locale view the editor displayed (EC-580 — the strip sees what
11
+ * the editor saw, so it needs the project's `LocaleConfig`).
10
12
  * - EC-394 `clearHiddenProps`: the same strip for component props across every
11
13
  * `blocks`/canvas composition, so a hidden relation/asset prop's ref leaves
12
14
  * the EC-277 reference index (derived from the stored draft at the EC-286
13
15
  * write chokepoint) and stops blocking its target's delete/unpublish.
14
16
  *
15
- * The manifests resolve through the SESSION registry by default (client-side-
16
- * with-user-session, the ratified trust boundary — same binding `doPublish`
17
- * used inline since EC-394); tests inject a resolver to pin behaviour against
17
+ * This is the projection point for "hidden means not delivered" (EC-565 Q3):
18
+ * delivery stays predicate-free because the values are gone from the snapshot.
19
+ *
20
+ * The manifests AND the locale config resolve through the SESSION by default
21
+ * (client-side-with-user-session, the ratified trust boundary — the registry
22
+ * binding `doPublish` used inline since EC-394, and the project locales the CMS
23
+ * session installs, EC-479). Both production callers pass the session's
24
+ * `localeConfig` explicitly; tests inject a resolver to pin behaviour against
18
25
  * fixture manifests. Identity-preserving: returns the input document when
19
26
  * nothing clears, so callers can save-if-changed.
20
27
  */
21
- export declare function cleanHiddenForPublish(collection: CollectionDef, doc: CmsDocument, resolveManifest?: ManifestResolver): CmsDocument;
28
+ export declare function cleanHiddenForPublish(collection: CollectionDef, doc: CmsDocument, resolveManifest?: ManifestResolver, localeConfig?: LocaleConfig | undefined): CmsDocument;
@@ -1,26 +1,34 @@
1
1
  import { clearHiddenValues } from '@elytracms/core/cms-core';
2
2
  import { clearHiddenProps } from '@elytracms/core/component-registry';
3
3
  import { getStudioRegistry } from '@elytracms/studio/lib/registry';
4
+ import { getStudioLocaleConfig } from '@elytracms/studio/lib/locale';
4
5
  /**
5
6
  * EC-425 — THE publish-time hidden-strip, extracted so single-document publish
6
7
  * (`doPublish`, use-publish-controller) and bulk publish (`runBulk`, cms-store)
7
8
  * share ONE clean step instead of the bulk bar bypassing it:
8
9
  *
9
10
  * - EC-273 `clearHiddenValues`: drop conditionally-hidden document field values
10
- * (predicate-hidden, non-`retainWhenHidden`) from `values`/`localized`.
11
+ * (predicate-hidden, non-`retainWhenHidden`) from `values`/`localized`, each
12
+ * judged in the locale view the editor displayed (EC-580 — the strip sees what
13
+ * the editor saw, so it needs the project's `LocaleConfig`).
11
14
  * - EC-394 `clearHiddenProps`: the same strip for component props across every
12
15
  * `blocks`/canvas composition, so a hidden relation/asset prop's ref leaves
13
16
  * the EC-277 reference index (derived from the stored draft at the EC-286
14
17
  * write chokepoint) and stops blocking its target's delete/unpublish.
15
18
  *
16
- * The manifests resolve through the SESSION registry by default (client-side-
17
- * with-user-session, the ratified trust boundary — same binding `doPublish`
18
- * used inline since EC-394); tests inject a resolver to pin behaviour against
19
+ * This is the projection point for "hidden means not delivered" (EC-565 Q3):
20
+ * delivery stays predicate-free because the values are gone from the snapshot.
21
+ *
22
+ * The manifests AND the locale config resolve through the SESSION by default
23
+ * (client-side-with-user-session, the ratified trust boundary — the registry
24
+ * binding `doPublish` used inline since EC-394, and the project locales the CMS
25
+ * session installs, EC-479). Both production callers pass the session's
26
+ * `localeConfig` explicitly; tests inject a resolver to pin behaviour against
19
27
  * fixture manifests. Identity-preserving: returns the input document when
20
28
  * nothing clears, so callers can save-if-changed.
21
29
  */
22
- export function cleanHiddenForPublish(collection, doc, resolveManifest = sessionManifestResolver()) {
23
- return clearHiddenProps(collection, clearHiddenValues(collection, doc), resolveManifest);
30
+ export function cleanHiddenForPublish(collection, doc, resolveManifest = sessionManifestResolver(), localeConfig = getStudioLocaleConfig() ?? undefined) {
31
+ return clearHiddenProps(collection, clearHiddenValues(collection, doc, localeConfig), resolveManifest);
24
32
  }
25
33
  /** The session component registry as a {@link ManifestResolver}. */
26
34
  function sessionManifestResolver() {
@@ -1 +1 @@
1
- {"version":3,"file":"publish-clean.js","sourceRoot":"","sources":["../../../src/components/cms/publish-clean.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAElE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAyB,EACzB,GAAgB,EAChB,kBAAoC,uBAAuB,EAAE;IAE7D,OAAO,gBAAgB,CAAC,UAAU,EAAE,iBAAiB,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,eAAe,CAAC,CAAA;AAC1F,CAAC;AAED,oEAAoE;AACpE,SAAS,uBAAuB;IAC9B,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACxC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;AACzC,CAAC","sourcesContent":["import { clearHiddenValues } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef } from '@elytracms/core/cms-core'\nimport { clearHiddenProps } from '@elytracms/core/component-registry'\nimport type { ManifestResolver } from '@elytracms/core/component-registry'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\n\n/**\n * EC-425 — THE publish-time hidden-strip, extracted so single-document publish\n * (`doPublish`, use-publish-controller) and bulk publish (`runBulk`, cms-store)\n * share ONE clean step instead of the bulk bar bypassing it:\n *\n * - EC-273 `clearHiddenValues`: drop conditionally-hidden document field values\n * (predicate-hidden, non-`retainWhenHidden`) from `values`/`localized`.\n * - EC-394 `clearHiddenProps`: the same strip for component props across every\n * `blocks`/canvas composition, so a hidden relation/asset prop's ref leaves\n * the EC-277 reference index (derived from the stored draft at the EC-286\n * write chokepoint) and stops blocking its target's delete/unpublish.\n *\n * The manifests resolve through the SESSION registry by default (client-side-\n * with-user-session, the ratified trust boundary — same binding `doPublish`\n * used inline since EC-394); tests inject a resolver to pin behaviour against\n * fixture manifests. Identity-preserving: returns the input document when\n * nothing clears, so callers can save-if-changed.\n */\nexport function cleanHiddenForPublish(\n collection: CollectionDef,\n doc: CmsDocument,\n resolveManifest: ManifestResolver = sessionManifestResolver(),\n): CmsDocument {\n return clearHiddenProps(collection, clearHiddenValues(collection, doc), resolveManifest)\n}\n\n/** The session component registry as a {@link ManifestResolver}. */\nfunction sessionManifestResolver(): ManifestResolver {\n const { registry } = getStudioRegistry()\n return (id) => registry.getManifest(id)\n}\n"]}
1
+ {"version":3,"file":"publish-clean.js","sourceRoot":"","sources":["../../../src/components/cms/publish-clean.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAyB,EACzB,GAAgB,EAChB,kBAAoC,uBAAuB,EAAE,EAC7D,eAAyC,qBAAqB,EAAE,IAAI,SAAS;IAE7E,OAAO,gBAAgB,CAAC,UAAU,EAAE,iBAAiB,CAAC,UAAU,EAAE,GAAG,EAAE,YAAY,CAAC,EAAE,eAAe,CAAC,CAAA;AACxG,CAAC;AAED,oEAAoE;AACpE,SAAS,uBAAuB;IAC9B,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACxC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;AACzC,CAAC","sourcesContent":["import { clearHiddenValues } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, LocaleConfig } from '@elytracms/core/cms-core'\nimport { clearHiddenProps } from '@elytracms/core/component-registry'\nimport type { ManifestResolver } from '@elytracms/core/component-registry'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { getStudioLocaleConfig } from '@elytracms/studio/lib/locale'\n\n/**\n * EC-425 — THE publish-time hidden-strip, extracted so single-document publish\n * (`doPublish`, use-publish-controller) and bulk publish (`runBulk`, cms-store)\n * share ONE clean step instead of the bulk bar bypassing it:\n *\n * - EC-273 `clearHiddenValues`: drop conditionally-hidden document field values\n * (predicate-hidden, non-`retainWhenHidden`) from `values`/`localized`, each\n * judged in the locale view the editor displayed (EC-580 — the strip sees what\n * the editor saw, so it needs the project's `LocaleConfig`).\n * - EC-394 `clearHiddenProps`: the same strip for component props across every\n * `blocks`/canvas composition, so a hidden relation/asset prop's ref leaves\n * the EC-277 reference index (derived from the stored draft at the EC-286\n * write chokepoint) and stops blocking its target's delete/unpublish.\n *\n * This is the projection point for \"hidden means not delivered\" (EC-565 Q3):\n * delivery stays predicate-free because the values are gone from the snapshot.\n *\n * The manifests AND the locale config resolve through the SESSION by default\n * (client-side-with-user-session, the ratified trust boundary — the registry\n * binding `doPublish` used inline since EC-394, and the project locales the CMS\n * session installs, EC-479). Both production callers pass the session's\n * `localeConfig` explicitly; tests inject a resolver to pin behaviour against\n * fixture manifests. Identity-preserving: returns the input document when\n * nothing clears, so callers can save-if-changed.\n */\nexport function cleanHiddenForPublish(\n collection: CollectionDef,\n doc: CmsDocument,\n resolveManifest: ManifestResolver = sessionManifestResolver(),\n localeConfig: LocaleConfig | undefined = getStudioLocaleConfig() ?? undefined,\n): CmsDocument {\n return clearHiddenProps(collection, clearHiddenValues(collection, doc, localeConfig), resolveManifest)\n}\n\n/** The session component registry as a {@link ManifestResolver}. */\nfunction sessionManifestResolver(): ManifestResolver {\n const { registry } = getStudioRegistry()\n return (id) => registry.getManifest(id)\n}\n"]}
@@ -32,7 +32,12 @@ import type { ComponentManifest, PropField } from '@elytracms/core/component-reg
32
32
  export declare function setComponentNodeProp(node: ComponentNode, name: string, value: JsonValue): ComponentNode;
33
33
  /** Remove a prop (revert to the manifest default), preserving everything else. */
34
34
  export declare function clearComponentNodeProp(node: ComponentNode, name: string): ComponentNode;
35
- /** Key-order-insensitive structural equality — a redundant "save" must not write. */
35
+ /**
36
+ * Key-order-insensitive structural equality — a redundant "save" must not write. The
37
+ * core `stableStringify` (EC-586: one serialization; this used to be a studio copy).
38
+ * Deliberately STRUCTURAL, not `semanticValueEqual`: a no-op guard must never swallow a
39
+ * real edit that a tolerant reader would read as "the same".
40
+ */
36
41
  export declare function componentNodesEqual(a: ComponentNode, b: ComponentNode): boolean;
37
42
  /**
38
43
  * The no-dirty guard for immediate per-prop write-back (0.0.41 sidebar inspector,
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Lock } from 'lucide-react';
3
3
  import { isStaticProp, staticProp } from '@elytracms/core/project-graph';
4
+ import { stableStringify } from '@elytracms/core/cms-core';
4
5
  import { Badge, ExpandableSurface, Field, Row, Separator, Stack, Text, } from '@elytracms/studio/ui';
5
6
  import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
6
7
  import { humanizeName } from '@elytracms/studio/lib/utils';
@@ -45,7 +46,12 @@ export function clearComponentNodeProp(node, name) {
45
46
  delete props[name];
46
47
  return { ...node, props };
47
48
  }
48
- /** Key-order-insensitive structural equality — a redundant "save" must not write. */
49
+ /**
50
+ * Key-order-insensitive structural equality — a redundant "save" must not write. The
51
+ * core `stableStringify` (EC-586: one serialization; this used to be a studio copy).
52
+ * Deliberately STRUCTURAL, not `semanticValueEqual`: a no-op guard must never swallow a
53
+ * real edit that a tolerant reader would read as "the same".
54
+ */
49
55
  export function componentNodesEqual(a, b) {
50
56
  return stableStringify(a) === stableStringify(b);
51
57
  }
@@ -58,17 +64,6 @@ export function componentNodesEqual(a, b) {
58
64
  export function resolveEmbedWrite(current, next) {
59
65
  return componentNodesEqual(next, current) ? undefined : next;
60
66
  }
61
- function stableStringify(value) {
62
- if (value === null || typeof value !== 'object')
63
- return JSON.stringify(value) ?? 'null';
64
- if (Array.isArray(value))
65
- return `[${value.map(stableStringify).join(',')}]`;
66
- const entries = Object.entries(value)
67
- .filter(([, v]) => v !== undefined)
68
- .sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0))
69
- .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`);
70
- return `{${entries.join(',')}}`;
71
- }
72
67
  /** The manifest's `blocks` (composition) props — excluded from the form, shown as a note. */
73
68
  export function blocksEmbedProps(manifest) {
74
69
  return Object.entries(manifest.props).filter(([, spec]) => spec.type === 'blocks');
@@ -1 +1 @@
1
- {"version":3,"file":"embed-props.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-props.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Lock } from 'lucide-react'\nimport { isStaticProp, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport {\n Badge,\n ExpandableSurface,\n Field,\n Row,\n Separator,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { humanizeName } from '@elytracms/studio/lib/utils'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { PropValueControl } from '../prop-fields'\nimport { visiblePropFields } from '../prop-visibility'\n\n/**\n * Prop editing for rich-text component embeds (EC-314 phase 4; stage surface\n * EC-430/AD-D).\n *\n * An embedded component is a full `ComponentNode` stored verbatim at\n * `componentEmbed.attrs.node`. The {@link EmbedPropsStage} edits that node's *props*\n * through the SAME EC-190 \"props-as-fields\" control stack the canvas inspector and\n * `blocks` field use ({@link PropValueControl}) — asset props open the library\n * picker, relation props the document picker, etc. — never a parallel form system.\n *\n * One surface, reachable everywhere (ratified AD-D, 2026-07-16): \"Edit settings\" on\n * an embed card opens the centered stage (`ExpandableSurface variant=\"stage\"\n * preset=\"form\"`) identically from the CANVAS tab and the form view. It replaced\n * BOTH halves of the 0.0.41 fork — the selection-driven sidebar swap (which only\n * rendered in the form view, leaving the canvas tab's \"Edit settings\" dead) and the\n * modal fallback.\n *\n * Value contract (unchanged from the 0.0.41 sidebar inspector): write-back is\n * **immediate per prop change** — each edit commits through `onChange` (one\n * ProseMirror `updateAttributes` transaction upstream, undoable), guarded by\n * {@link resolveEmbedWrite} so an unchanged write never dirties the field. There is\n * no draft+Apply and no save/cancel of its own: the stage is the same value flow\n * relocated, lightly dismissible (Escape / backdrop / header close) per the\n * forms-never-modal spirit. The dialog fork's draft+Apply semantics died with it.\n *\n * `blocks`-typed props (nested composition) are NOT edited here — that needs the\n * canvas, not a form. They are excluded from the form and surfaced as a note.\n */\n\n/** Set a prop to a fresh static value, preserving identity, slots, and sibling props. */\nexport function setComponentNodeProp(\n node: ComponentNode,\n name: string,\n value: JsonValue,\n): ComponentNode {\n return { ...node, props: { ...node.props, [name]: staticProp(value) } }\n}\n\n/** Remove a prop (revert to the manifest default), preserving everything else. */\nexport function clearComponentNodeProp(node: ComponentNode, name: string): ComponentNode {\n const props = { ...node.props }\n delete props[name]\n return { ...node, props }\n}\n\n/** Key-order-insensitive structural equality — a redundant \"save\" must not write. */\nexport function componentNodesEqual(a: ComponentNode, b: ComponentNode): boolean {\n return stableStringify(a) === stableStringify(b)\n}\n\n/**\n * The no-dirty guard for immediate per-prop write-back (0.0.41 sidebar inspector,\n * inherited verbatim by the EC-430 stage form): the node to commit for an edit, or\n * `undefined` when it is a structural no-op so an unchanged write never dirties the\n * field ({@link componentNodesEqual}), applied per keystroke.\n */\nexport function resolveEmbedWrite(current: ComponentNode, next: ComponentNode): ComponentNode | undefined {\n return componentNodesEqual(next, current) ? undefined : next\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null'\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0))\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n return `{${entries.join(',')}}`\n}\n\n/** The manifest's `blocks` (composition) props — excluded from the form, shown as a note. */\nexport function blocksEmbedProps(manifest: ComponentManifest): Array<[string, PropField]> {\n return Object.entries(manifest.props).filter(([, spec]) => spec.type === 'blocks')\n}\n\n/**\n * A lean one-line summary of an embed's static scalar props (`Label: value`,\n * up to three), for the card. Skips unset props and structured values (refs /\n * objects / arrays) — those are edited in the stage form, not summarised.\n */\nexport function embedPropsSummary(\n node: ComponentNode,\n manifest?: ComponentManifest,\n): string | undefined {\n const parts: string[] = []\n for (const [name, value] of Object.entries(node.props ?? {})) {\n if (!isStaticProp(value)) continue\n const raw = value.value\n if (raw === null || raw === undefined || typeof raw === 'object') continue\n const label = manifest?.props[name]?.form?.label\n const labelText = label ? localizedLabel(label, name) : humanizeName(name)\n parts.push(`${labelText}: ${String(raw)}`)\n if (parts.length >= 3) break\n }\n return parts.length > 0 ? parts.join(' · ') : undefined\n}\n\nexport interface EmbedStageFormProps {\n /** The embedded component node whose props are being edited (the STORED node). */\n node: ComponentNode\n /** Its manifest — drives which prop fields render. */\n manifest: ComponentManifest\n /**\n * Commit an edited node. Upstream writes it through one ProseMirror\n * `updateAttributes` transaction (undoable); the no-dirty guard has already been\n * applied here, so this only fires on a real structural change.\n */\n onChange: (node: ComponentNode) => void\n}\n\n/**\n * The embed prop form body — pure of surface chrome so it smoke-renders in the node\n * test env (the stage itself portals and renders `null` without a DOM).\n *\n * EC-393 parity: rows filter through the SAME {@link visiblePropFields} core the\n * canvas property inspector uses — a predicate-hidden row (`form.hidden` over the\n * sibling prop values) unmounts/remounts live as its driver prop flips, because the\n * committed node re-renders this form. EC-395 parity: `form.readOnly` renders the\n * document form's Lock badge next to the label and disables the delegated control\n * (`PropValueControl` reads the same flag); `form.control` and `form.description`\n * route through the same delegation.\n */\nexport function EmbedStageForm({ node, manifest, onChange }: EmbedStageFormProps) {\n const visible = visiblePropFields(manifest, node)\n const blocks = blocksEmbedProps(manifest)\n\n function set(name: string, value: JsonValue) {\n const next = resolveEmbedWrite(node, setComponentNodeProp(node, name, value))\n if (next) onChange(next)\n }\n function clear(name: string) {\n const next = resolveEmbedWrite(node, clearComponentNodeProp(node, name))\n if (next) onChange(next)\n }\n\n return (\n <Stack gap=\"4\">\n {visible.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('richText.embed.noProps')}\n </Text>\n ) : (\n visible.map(([name, spec]) => {\n const id = `embed-${node.id}-${name}`\n return (\n <Field\n key={name}\n label={\n spec.form?.readOnly === true ? (\n // EC-395: the SAME read-only badge the document form and the canvas\n // prop inspector render next to a locked label — identical icon,\n // i18n key, and `Badge` variant, never a bespoke treatment.\n <Row gap=\"2\">\n <span>{localizedLabel(spec.form?.label, name)}</span>\n <Badge variant=\"outline\">\n <Lock className=\"size-3\" /> {translate('cms.editor.readOnlyBadge')}\n </Badge>\n </Row>\n ) : (\n localizedLabel(spec.form?.label, name)\n )\n }\n htmlFor={id}\n hint={localizedLabel(spec.form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec}\n value={node.props?.[name]}\n onSet={(v) => set(name, v)}\n onClear={() => clear(name)}\n />\n </Field>\n )\n })\n )}\n {blocks.length > 0 ? (\n <>\n <Separator />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('richText.embed.blocksNote')}\n </Text>\n </>\n ) : null}\n </Stack>\n )\n}\n\nexport interface EmbedPropsStageProps extends EmbedStageFormProps {\n /** Dismiss request (Escape / backdrop / header close). The caller owns the open state. */\n onDismiss: () => void\n}\n\n/**\n * The stage-body focus handoff (a module-level constant so React never re-invokes it\n * on re-renders — a per-keystroke re-invocation would churn the guard). The stage\n * opens from a card INSIDE a focused TipTap editor, and prosemirror-view\n * preventDefaults every Escape while its contenteditable holds focus\n * (`capturekeys.ts` — its \"dangerous default\" backdrop map), which the surface's\n * EC-392 bridge reads as \"handled by a layer above\" — so Escape would never dismiss,\n * and stray typing would land in the covered document. Moving focus into the stage\n * on mount hands Escape to the layer stack; the `focusin` guard keeps it there,\n * because ProseMirror re-asserts focus on its editable off deferred click handling\n * and window-refocus events. The guard is NOT a focus trap: it only reclaims from a\n * ProseMirror editable OUTSIDE the stage (unreachable to the user behind the\n * backdrop — only programmatic steals hit this), never from the stage's own controls\n * (incl. nested rich-text prop editors, `el.contains`) or a picker dialog above.\n * Per-caller policy, deliberately NOT in ExpandableSurface: the rich-text prose\n * stage RELOCATES the editor, so there the editor keeping focus is the point.\n * React 19 ref-cleanup unregisters the guard when the stage unmounts.\n */\nconst focusStageBody = (el: HTMLDivElement | null) => {\n if (!el) return undefined\n el.focus()\n const guard = () => {\n const active = document.activeElement\n if (el.contains(active)) return\n if (active instanceof HTMLElement && active.closest('.ProseMirror')) el.focus()\n }\n document.addEventListener('focusin', guard)\n return () => document.removeEventListener('focusin', guard)\n}\n\n/**\n * The embed prop inspector on the centered stage (EC-430/AD-D) — mounted by the\n * embed card's \"Edit settings\" from ANY rich-text surface (form view, canvas-tab\n * prop inspector, or inside the expanded rich-text stage — a later mount rides the\n * EC-392 layer stack above it, and pickers opened from these controls stack above\n * this stage in turn). Hook-free by construction so the geometry contract is\n * pinnable by direct invocation in the node test env.\n */\nexport function EmbedPropsStage({ node, manifest, onChange, onDismiss }: EmbedPropsStageProps) {\n return (\n <ExpandableSurface\n variant=\"stage\"\n preset=\"form\"\n title={translate('richText.embed.editTitle', {\n name: componentDisplayName(node.componentId, manifest),\n })}\n closeLabel={translate('common.close')}\n onDismiss={onDismiss}\n >\n <div\n ref={focusStageBody}\n tabIndex={-1}\n className=\"min-h-0 flex-1 overflow-y-auto p-6 outline-none\"\n >\n <EmbedStageForm node={node} manifest={manifest} onChange={onChange} />\n </div>\n </ExpandableSurface>\n )\n}\n"]}
1
+ {"version":3,"file":"embed-props.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-props.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Lock } from 'lucide-react'\nimport { isStaticProp, staticProp } from '@elytracms/core/project-graph'\nimport { stableStringify } from '@elytracms/core/cms-core'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport {\n Badge,\n ExpandableSurface,\n Field,\n Row,\n Separator,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { humanizeName } from '@elytracms/studio/lib/utils'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { PropValueControl } from '../prop-fields'\nimport { visiblePropFields } from '../prop-visibility'\n\n/**\n * Prop editing for rich-text component embeds (EC-314 phase 4; stage surface\n * EC-430/AD-D).\n *\n * An embedded component is a full `ComponentNode` stored verbatim at\n * `componentEmbed.attrs.node`. The {@link EmbedPropsStage} edits that node's *props*\n * through the SAME EC-190 \"props-as-fields\" control stack the canvas inspector and\n * `blocks` field use ({@link PropValueControl}) — asset props open the library\n * picker, relation props the document picker, etc. — never a parallel form system.\n *\n * One surface, reachable everywhere (ratified AD-D, 2026-07-16): \"Edit settings\" on\n * an embed card opens the centered stage (`ExpandableSurface variant=\"stage\"\n * preset=\"form\"`) identically from the CANVAS tab and the form view. It replaced\n * BOTH halves of the 0.0.41 fork — the selection-driven sidebar swap (which only\n * rendered in the form view, leaving the canvas tab's \"Edit settings\" dead) and the\n * modal fallback.\n *\n * Value contract (unchanged from the 0.0.41 sidebar inspector): write-back is\n * **immediate per prop change** — each edit commits through `onChange` (one\n * ProseMirror `updateAttributes` transaction upstream, undoable), guarded by\n * {@link resolveEmbedWrite} so an unchanged write never dirties the field. There is\n * no draft+Apply and no save/cancel of its own: the stage is the same value flow\n * relocated, lightly dismissible (Escape / backdrop / header close) per the\n * forms-never-modal spirit. The dialog fork's draft+Apply semantics died with it.\n *\n * `blocks`-typed props (nested composition) are NOT edited here — that needs the\n * canvas, not a form. They are excluded from the form and surfaced as a note.\n */\n\n/** Set a prop to a fresh static value, preserving identity, slots, and sibling props. */\nexport function setComponentNodeProp(\n node: ComponentNode,\n name: string,\n value: JsonValue,\n): ComponentNode {\n return { ...node, props: { ...node.props, [name]: staticProp(value) } }\n}\n\n/** Remove a prop (revert to the manifest default), preserving everything else. */\nexport function clearComponentNodeProp(node: ComponentNode, name: string): ComponentNode {\n const props = { ...node.props }\n delete props[name]\n return { ...node, props }\n}\n\n/**\n * Key-order-insensitive structural equality — a redundant \"save\" must not write. The\n * core `stableStringify` (EC-586: one serialization; this used to be a studio copy).\n * Deliberately STRUCTURAL, not `semanticValueEqual`: a no-op guard must never swallow a\n * real edit that a tolerant reader would read as \"the same\".\n */\nexport function componentNodesEqual(a: ComponentNode, b: ComponentNode): boolean {\n return stableStringify(a) === stableStringify(b)\n}\n\n/**\n * The no-dirty guard for immediate per-prop write-back (0.0.41 sidebar inspector,\n * inherited verbatim by the EC-430 stage form): the node to commit for an edit, or\n * `undefined` when it is a structural no-op so an unchanged write never dirties the\n * field ({@link componentNodesEqual}), applied per keystroke.\n */\nexport function resolveEmbedWrite(current: ComponentNode, next: ComponentNode): ComponentNode | undefined {\n return componentNodesEqual(next, current) ? undefined : next\n}\n\n/** The manifest's `blocks` (composition) props — excluded from the form, shown as a note. */\nexport function blocksEmbedProps(manifest: ComponentManifest): Array<[string, PropField]> {\n return Object.entries(manifest.props).filter(([, spec]) => spec.type === 'blocks')\n}\n\n/**\n * A lean one-line summary of an embed's static scalar props (`Label: value`,\n * up to three), for the card. Skips unset props and structured values (refs /\n * objects / arrays) — those are edited in the stage form, not summarised.\n */\nexport function embedPropsSummary(\n node: ComponentNode,\n manifest?: ComponentManifest,\n): string | undefined {\n const parts: string[] = []\n for (const [name, value] of Object.entries(node.props ?? {})) {\n if (!isStaticProp(value)) continue\n const raw = value.value\n if (raw === null || raw === undefined || typeof raw === 'object') continue\n const label = manifest?.props[name]?.form?.label\n const labelText = label ? localizedLabel(label, name) : humanizeName(name)\n parts.push(`${labelText}: ${String(raw)}`)\n if (parts.length >= 3) break\n }\n return parts.length > 0 ? parts.join(' · ') : undefined\n}\n\nexport interface EmbedStageFormProps {\n /** The embedded component node whose props are being edited (the STORED node). */\n node: ComponentNode\n /** Its manifest — drives which prop fields render. */\n manifest: ComponentManifest\n /**\n * Commit an edited node. Upstream writes it through one ProseMirror\n * `updateAttributes` transaction (undoable); the no-dirty guard has already been\n * applied here, so this only fires on a real structural change.\n */\n onChange: (node: ComponentNode) => void\n}\n\n/**\n * The embed prop form body — pure of surface chrome so it smoke-renders in the node\n * test env (the stage itself portals and renders `null` without a DOM).\n *\n * EC-393 parity: rows filter through the SAME {@link visiblePropFields} core the\n * canvas property inspector uses — a predicate-hidden row (`form.hidden` over the\n * sibling prop values) unmounts/remounts live as its driver prop flips, because the\n * committed node re-renders this form. EC-395 parity: `form.readOnly` renders the\n * document form's Lock badge next to the label and disables the delegated control\n * (`PropValueControl` reads the same flag); `form.control` and `form.description`\n * route through the same delegation.\n */\nexport function EmbedStageForm({ node, manifest, onChange }: EmbedStageFormProps) {\n const visible = visiblePropFields(manifest, node)\n const blocks = blocksEmbedProps(manifest)\n\n function set(name: string, value: JsonValue) {\n const next = resolveEmbedWrite(node, setComponentNodeProp(node, name, value))\n if (next) onChange(next)\n }\n function clear(name: string) {\n const next = resolveEmbedWrite(node, clearComponentNodeProp(node, name))\n if (next) onChange(next)\n }\n\n return (\n <Stack gap=\"4\">\n {visible.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('richText.embed.noProps')}\n </Text>\n ) : (\n visible.map(([name, spec]) => {\n const id = `embed-${node.id}-${name}`\n return (\n <Field\n key={name}\n label={\n spec.form?.readOnly === true ? (\n // EC-395: the SAME read-only badge the document form and the canvas\n // prop inspector render next to a locked label — identical icon,\n // i18n key, and `Badge` variant, never a bespoke treatment.\n <Row gap=\"2\">\n <span>{localizedLabel(spec.form?.label, name)}</span>\n <Badge variant=\"outline\">\n <Lock className=\"size-3\" /> {translate('cms.editor.readOnlyBadge')}\n </Badge>\n </Row>\n ) : (\n localizedLabel(spec.form?.label, name)\n )\n }\n htmlFor={id}\n hint={localizedLabel(spec.form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec}\n value={node.props?.[name]}\n onSet={(v) => set(name, v)}\n onClear={() => clear(name)}\n />\n </Field>\n )\n })\n )}\n {blocks.length > 0 ? (\n <>\n <Separator />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('richText.embed.blocksNote')}\n </Text>\n </>\n ) : null}\n </Stack>\n )\n}\n\nexport interface EmbedPropsStageProps extends EmbedStageFormProps {\n /** Dismiss request (Escape / backdrop / header close). The caller owns the open state. */\n onDismiss: () => void\n}\n\n/**\n * The stage-body focus handoff (a module-level constant so React never re-invokes it\n * on re-renders — a per-keystroke re-invocation would churn the guard). The stage\n * opens from a card INSIDE a focused TipTap editor, and prosemirror-view\n * preventDefaults every Escape while its contenteditable holds focus\n * (`capturekeys.ts` — its \"dangerous default\" backdrop map), which the surface's\n * EC-392 bridge reads as \"handled by a layer above\" — so Escape would never dismiss,\n * and stray typing would land in the covered document. Moving focus into the stage\n * on mount hands Escape to the layer stack; the `focusin` guard keeps it there,\n * because ProseMirror re-asserts focus on its editable off deferred click handling\n * and window-refocus events. The guard is NOT a focus trap: it only reclaims from a\n * ProseMirror editable OUTSIDE the stage (unreachable to the user behind the\n * backdrop — only programmatic steals hit this), never from the stage's own controls\n * (incl. nested rich-text prop editors, `el.contains`) or a picker dialog above.\n * Per-caller policy, deliberately NOT in ExpandableSurface: the rich-text prose\n * stage RELOCATES the editor, so there the editor keeping focus is the point.\n * React 19 ref-cleanup unregisters the guard when the stage unmounts.\n */\nconst focusStageBody = (el: HTMLDivElement | null) => {\n if (!el) return undefined\n el.focus()\n const guard = () => {\n const active = document.activeElement\n if (el.contains(active)) return\n if (active instanceof HTMLElement && active.closest('.ProseMirror')) el.focus()\n }\n document.addEventListener('focusin', guard)\n return () => document.removeEventListener('focusin', guard)\n}\n\n/**\n * The embed prop inspector on the centered stage (EC-430/AD-D) — mounted by the\n * embed card's \"Edit settings\" from ANY rich-text surface (form view, canvas-tab\n * prop inspector, or inside the expanded rich-text stage — a later mount rides the\n * EC-392 layer stack above it, and pickers opened from these controls stack above\n * this stage in turn). Hook-free by construction so the geometry contract is\n * pinnable by direct invocation in the node test env.\n */\nexport function EmbedPropsStage({ node, manifest, onChange, onDismiss }: EmbedPropsStageProps) {\n return (\n <ExpandableSurface\n variant=\"stage\"\n preset=\"form\"\n title={translate('richText.embed.editTitle', {\n name: componentDisplayName(node.componentId, manifest),\n })}\n closeLabel={translate('common.close')}\n onDismiss={onDismiss}\n >\n <div\n ref={focusStageBody}\n tabIndex={-1}\n className=\"min-h-0 flex-1 overflow-y-auto p-6 outline-none\"\n >\n <EmbedStageForm node={node} manifest={manifest} onChange={onChange} />\n </div>\n </ExpandableSurface>\n )\n}\n"]}
@@ -24,14 +24,21 @@ export declare function insertAssetImage(editor: Editor, assetId: string, assets
24
24
  export declare function insertComponentEmbed(editor: Editor, manifest: ComponentManifest, options?: {
25
25
  select?: boolean;
26
26
  }): void;
27
- /** A fresh ComponentNode from a manifest's prop defaults (mirrors builder node-factory). */
27
+ /**
28
+ * A fresh embedded ComponentNode (mirrors builder node-factory). EC-591 / ratified
29
+ * 2f: NO props — an absent prop is `undefined`, so the implementation's own default
30
+ * applies; the manifest `default` is a hint, never baked into the stored node.
31
+ */
28
32
  export declare function embedNodeFromManifest(manifest: ComponentManifest): ComponentNode;
29
33
  /**
30
34
  * The components embeddable in a rich-text field: the field's `allow` list (a
31
- * closed set — EC-544: an empty one offers nothing), or every non-primitive
32
- * component when absent — matching the `blocks` field and the
33
- * `validateRichTextEmbeds` leash. Eligibility is `isCanvasEligible`, the one
34
- * predicate the blocks panel and `elytra previews` read too (EC-514) — never a
35
- * local reading of the manifest flag.
35
+ * closed set — EC-544: an empty one offers nothing), or EVERY registered
36
+ * component when absent — the ONE semantic the `blocks` field, the blocks panel
37
+ * and the `validateRichTextEmbeds` leash already apply (fields.ts: "absent = any").
38
+ * EC-591: the old "every non-primitive" reading keyed on the `base.*` id prefix,
39
+ * which no longer means anything (and diverged from the validator — COMPOSITION
40
+ * #6); a field that wants a narrower offer declares `allow`. Eligibility is
41
+ * `isCanvasEligible`, the one predicate the blocks panel and `elytra previews`
42
+ * read too (EC-514) — never a local reading of the manifest flag.
36
43
  */
37
44
  export declare function listEmbeddable(allow: string[] | undefined, registered: ComponentManifest[]): ComponentManifest[];