@elytracms/studio 0.0.91 → 0.0.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/convex/asset-alt-migration.test.ts +127 -0
  2. package/convex/asset-replace.test.ts +168 -0
  3. package/convex/asset-revalidate.test.ts +6 -6
  4. package/convex/assets.ts +41 -5
  5. package/convex/cms.ts +537 -177
  6. package/convex/contract-parity.test.ts +173 -19
  7. package/convex/delivery.ts +26 -13
  8. package/convex/document-revalidate.test.ts +17 -16
  9. package/convex/orphan-hydration.test.ts +116 -0
  10. package/convex/presence-cost.test.ts +89 -0
  11. package/convex/rail-write-parity.test.ts +17 -14
  12. package/convex/referenceMaintenance.ts +15 -15
  13. package/convex/schema.ts +70 -5
  14. package/convex/triggers.ts +21 -8
  15. package/convex/write-limits.ts +1 -1
  16. package/dist/components/ai/ai-chat-panel.js +33 -22
  17. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  18. package/dist/components/ai/proposal-card.js +2 -1
  19. package/dist/components/ai/proposal-card.js.map +1 -1
  20. package/dist/components/ai/translate-document-action.d.ts +8 -2
  21. package/dist/components/ai/translate-document-action.js.map +1 -1
  22. package/dist/components/ai/translate-document-dialog.d.ts +1 -1
  23. package/dist/components/ai/translate-document-dialog.js +14 -8
  24. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  25. package/dist/components/ai/translate-skipped.d.ts +19 -0
  26. package/dist/components/ai/translate-skipped.js +46 -0
  27. package/dist/components/ai/translate-skipped.js.map +1 -0
  28. package/dist/components/app-views/default-modules.d.ts +23 -6
  29. package/dist/components/app-views/default-modules.js +33 -14
  30. package/dist/components/app-views/default-modules.js.map +1 -1
  31. package/dist/components/assets/asset-detail.js +18 -8
  32. package/dist/components/assets/asset-detail.js.map +1 -1
  33. package/dist/components/assets/asset-metadata.d.ts +15 -4
  34. package/dist/components/assets/asset-metadata.js +15 -2
  35. package/dist/components/assets/asset-metadata.js.map +1 -1
  36. package/dist/components/assets/asset-search.js +4 -1
  37. package/dist/components/assets/asset-search.js.map +1 -1
  38. package/dist/components/assets/asset-selection.d.ts +0 -8
  39. package/dist/components/assets/asset-selection.js +4 -23
  40. package/dist/components/assets/asset-selection.js.map +1 -1
  41. package/dist/components/assets/asset-thumb.js +3 -2
  42. package/dist/components/assets/asset-thumb.js.map +1 -1
  43. package/dist/components/assets/assets-collection-view-source.d.ts +3 -3
  44. package/dist/components/assets/assets-collection-view-source.js +9 -5
  45. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  46. package/dist/components/assets/assets-store.d.ts +14 -0
  47. package/dist/components/assets/assets-store.js +23 -1
  48. package/dist/components/assets/assets-store.js.map +1 -1
  49. package/dist/components/assets/replace-file.d.ts +22 -0
  50. package/dist/components/assets/replace-file.js +59 -0
  51. package/dist/components/assets/replace-file.js.map +1 -0
  52. package/dist/components/builder/blocks-model.d.ts +14 -26
  53. package/dist/components/builder/blocks-model.js +35 -41
  54. package/dist/components/builder/blocks-model.js.map +1 -1
  55. package/dist/components/builder/blocks-panel.d.ts +7 -3
  56. package/dist/components/builder/blocks-panel.js +6 -5
  57. package/dist/components/builder/blocks-panel.js.map +1 -1
  58. package/dist/components/builder/builder-store.d.ts +126 -30
  59. package/dist/components/builder/builder-store.js +169 -67
  60. package/dist/components/builder/builder-store.js.map +1 -1
  61. package/dist/components/builder/canvas-body.d.ts +30 -0
  62. package/dist/components/builder/canvas-body.js +38 -0
  63. package/dist/components/builder/canvas-body.js.map +1 -0
  64. package/dist/components/builder/canvas-chrome-css.js +7 -0
  65. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  66. package/dist/components/builder/canvas-dnd.d.ts +7 -7
  67. package/dist/components/builder/canvas-dnd.js +5 -5
  68. package/dist/components/builder/canvas-dnd.js.map +1 -1
  69. package/dist/components/builder/canvas-render-context.d.ts +30 -22
  70. package/dist/components/builder/canvas-render-context.js +44 -44
  71. package/dist/components/builder/canvas-render-context.js.map +1 -1
  72. package/dist/components/builder/canvas-runs.d.ts +45 -0
  73. package/dist/components/builder/canvas-runs.js +51 -0
  74. package/dist/components/builder/canvas-runs.js.map +1 -0
  75. package/dist/components/builder/canvas.d.ts +5 -4
  76. package/dist/components/builder/canvas.js +34 -6
  77. package/dist/components/builder/canvas.js.map +1 -1
  78. package/dist/components/builder/clipboard-actions.d.ts +15 -0
  79. package/dist/components/builder/clipboard-actions.js +50 -13
  80. package/dist/components/builder/clipboard-actions.js.map +1 -1
  81. package/dist/components/builder/composition-field-editor.d.ts +10 -16
  82. package/dist/components/builder/composition-field-editor.js +30 -40
  83. package/dist/components/builder/composition-field-editor.js.map +1 -1
  84. package/dist/components/builder/composition-patches.d.ts +7 -0
  85. package/dist/components/builder/composition-patches.js +256 -0
  86. package/dist/components/builder/composition-patches.js.map +1 -0
  87. package/dist/components/builder/delete-key.d.ts +5 -0
  88. package/dist/components/builder/delete-key.js +4 -0
  89. package/dist/components/builder/delete-key.js.map +1 -1
  90. package/dist/components/builder/insert-context.d.ts +17 -25
  91. package/dist/components/builder/insert-context.js +45 -92
  92. package/dist/components/builder/insert-context.js.map +1 -1
  93. package/dist/components/builder/page-tree.d.ts +3 -3
  94. package/dist/components/builder/page-tree.js +3 -3
  95. package/dist/components/builder/page-tree.js.map +1 -1
  96. package/dist/components/builder/placement-refusal.d.ts +18 -0
  97. package/dist/components/builder/placement-refusal.js +46 -0
  98. package/dist/components/builder/placement-refusal.js.map +1 -0
  99. package/dist/components/builder/property-inspector.js +4 -0
  100. package/dist/components/builder/property-inspector.js.map +1 -1
  101. package/dist/components/builder/reuse-context.js +65 -26
  102. package/dist/components/builder/reuse-context.js.map +1 -1
  103. package/dist/components/builder/reuse.d.ts +79 -8
  104. package/dist/components/builder/reuse.js +71 -23
  105. package/dist/components/builder/reuse.js.map +1 -1
  106. package/dist/components/builder/run-inspector.d.ts +11 -0
  107. package/dist/components/builder/run-inspector.js +33 -0
  108. package/dist/components/builder/run-inspector.js.map +1 -0
  109. package/dist/components/cms/asset-field.d.ts +38 -1
  110. package/dist/components/cms/asset-field.js +182 -58
  111. package/dist/components/cms/asset-field.js.map +1 -1
  112. package/dist/components/cms/asset-picker-core.d.ts +16 -0
  113. package/dist/components/cms/asset-picker-core.js +20 -0
  114. package/dist/components/cms/asset-picker-core.js.map +1 -1
  115. package/dist/components/cms/asset-picker.js +12 -18
  116. package/dist/components/cms/asset-picker.js.map +1 -1
  117. package/dist/components/cms/bulk-actions.d.ts +4 -20
  118. package/dist/components/cms/bulk-actions.js +3 -24
  119. package/dist/components/cms/bulk-actions.js.map +1 -1
  120. package/dist/components/cms/cell-edit.d.ts +5 -5
  121. package/dist/components/cms/cell-edit.js.map +1 -1
  122. package/dist/components/cms/cell.js.map +1 -1
  123. package/dist/components/cms/cms-store.d.ts +87 -45
  124. package/dist/components/cms/cms-store.js +347 -203
  125. package/dist/components/cms/cms-store.js.map +1 -1
  126. package/dist/components/cms/collection-table.js +7 -2
  127. package/dist/components/cms/collection-table.js.map +1 -1
  128. package/dist/components/cms/commit-policy.d.ts +24 -0
  129. package/dist/components/cms/commit-policy.js +37 -0
  130. package/dist/components/cms/commit-policy.js.map +1 -0
  131. package/dist/components/cms/detail-editor.js +83 -137
  132. package/dist/components/cms/detail-editor.js.map +1 -1
  133. package/dist/components/cms/document-diff-view.d.ts +3 -1
  134. package/dist/components/cms/document-diff-view.js +12 -7
  135. package/dist/components/cms/document-diff-view.js.map +1 -1
  136. package/dist/components/cms/draft-commit.d.ts +70 -0
  137. package/dist/components/cms/draft-commit.js +82 -0
  138. package/dist/components/cms/draft-commit.js.map +1 -0
  139. package/dist/components/cms/duplicate-document.d.ts +11 -13
  140. package/dist/components/cms/duplicate-document.js +26 -34
  141. package/dist/components/cms/duplicate-document.js.map +1 -1
  142. package/dist/components/cms/field-anchors.d.ts +7 -4
  143. package/dist/components/cms/field-anchors.js +9 -7
  144. package/dist/components/cms/field-anchors.js.map +1 -1
  145. package/dist/components/cms/field-collaboration.d.ts +47 -0
  146. package/dist/components/cms/field-collaboration.js +87 -0
  147. package/dist/components/cms/field-collaboration.js.map +1 -0
  148. package/dist/components/cms/field-control-lib.d.ts +22 -1
  149. package/dist/components/cms/field-control-lib.js +33 -4
  150. package/dist/components/cms/field-control-lib.js.map +1 -1
  151. package/dist/components/cms/field-control.js +17 -14
  152. package/dist/components/cms/field-control.js.map +1 -1
  153. package/dist/components/cms/gate-validation.d.ts +21 -30
  154. package/dist/components/cms/gate-validation.js +11 -22
  155. package/dist/components/cms/gate-validation.js.map +1 -1
  156. package/dist/components/cms/object-array-field.js +4 -4
  157. package/dist/components/cms/object-array-field.js.map +1 -1
  158. package/dist/components/cms/prop-fields.d.ts +7 -2
  159. package/dist/components/cms/prop-fields.js +13 -6
  160. package/dist/components/cms/prop-fields.js.map +1 -1
  161. package/dist/components/cms/reference-confirm.js +10 -4
  162. package/dist/components/cms/reference-confirm.js.map +1 -1
  163. package/dist/components/cms/rich-text/embed-picker.d.ts +1 -1
  164. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  165. package/dist/components/cms/rich-text/insert.d.ts +20 -9
  166. package/dist/components/cms/rich-text/insert.js +25 -17
  167. package/dist/components/cms/rich-text/insert.js.map +1 -1
  168. package/dist/components/cms/use-document-draft.d.ts +96 -0
  169. package/dist/components/cms/use-document-draft.js +283 -0
  170. package/dist/components/cms/use-document-draft.js.map +1 -0
  171. package/dist/components/cms/use-document-versions.js +2 -2
  172. package/dist/components/cms/use-document-versions.js.map +1 -1
  173. package/dist/components/cms/use-live-url.d.ts +6 -12
  174. package/dist/components/cms/use-live-url.js +16 -15
  175. package/dist/components/cms/use-live-url.js.map +1 -1
  176. package/dist/components/cms/use-publish-controller.d.ts +13 -18
  177. package/dist/components/cms/use-publish-controller.js +24 -41
  178. package/dist/components/cms/use-publish-controller.js.map +1 -1
  179. package/dist/components/cms/use-published-perspective.d.ts +2 -3
  180. package/dist/components/cms/use-published-perspective.js +4 -3
  181. package/dist/components/cms/use-published-perspective.js.map +1 -1
  182. package/dist/components/cms/versions-pane.js +8 -1
  183. package/dist/components/cms/versions-pane.js.map +1 -1
  184. package/dist/components/collection-view/cms-collection-view-source.js +24 -7
  185. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  186. package/dist/components/components/transfer-dialog.js +28 -23
  187. package/dist/components/components/transfer-dialog.js.map +1 -1
  188. package/dist/components/components/transfer.d.ts +31 -21
  189. package/dist/components/components/transfer.js +49 -35
  190. package/dist/components/components/transfer.js.map +1 -1
  191. package/dist/components/project-list.js.map +1 -1
  192. package/dist/components/project-session.d.ts +6 -3
  193. package/dist/components/project-session.js +14 -8
  194. package/dist/components/project-session.js.map +1 -1
  195. package/dist/components/project-switcher.js.map +1 -1
  196. package/dist/components/rail-view-source.js +2 -1
  197. package/dist/components/rail-view-source.js.map +1 -1
  198. package/dist/components/section-nav.js +1 -1
  199. package/dist/components/section-nav.js.map +1 -1
  200. package/dist/lib/ai/apply-content.js +56 -116
  201. package/dist/lib/ai/apply-content.js.map +1 -1
  202. package/dist/lib/ai/chat-context.d.ts +8 -0
  203. package/dist/lib/ai/chat-context.js +37 -6
  204. package/dist/lib/ai/chat-context.js.map +1 -1
  205. package/dist/lib/ai/chat-state.d.ts +14 -1
  206. package/dist/lib/ai/chat-state.js.map +1 -1
  207. package/dist/lib/ai/constraints.d.ts +20 -5
  208. package/dist/lib/ai/constraints.js +125 -33
  209. package/dist/lib/ai/constraints.js.map +1 -1
  210. package/dist/lib/ai/fake-provider.d.ts +16 -3
  211. package/dist/lib/ai/fake-provider.js +22 -6
  212. package/dist/lib/ai/fake-provider.js.map +1 -1
  213. package/dist/lib/ai/index.d.ts +2 -0
  214. package/dist/lib/ai/index.js +2 -0
  215. package/dist/lib/ai/index.js.map +1 -1
  216. package/dist/lib/ai/leash.d.ts +34 -0
  217. package/dist/lib/ai/leash.js +28 -0
  218. package/dist/lib/ai/leash.js.map +1 -0
  219. package/dist/lib/ai/provenance.d.ts +1 -1
  220. package/dist/lib/ai/provenance.js.map +1 -1
  221. package/dist/lib/ai/rich-text-input.d.ts +8 -8
  222. package/dist/lib/ai/rich-text-input.js +2 -1
  223. package/dist/lib/ai/rich-text-input.js.map +1 -1
  224. package/dist/lib/ai/rich-text-translate.d.ts +27 -4
  225. package/dist/lib/ai/rich-text-translate.js +28 -10
  226. package/dist/lib/ai/rich-text-translate.js.map +1 -1
  227. package/dist/lib/ai/shape.d.ts +13 -13
  228. package/dist/lib/ai/shape.js +69 -93
  229. package/dist/lib/ai/shape.js.map +1 -1
  230. package/dist/lib/ai/tools.d.ts +36 -41
  231. package/dist/lib/ai/tools.js +85 -87
  232. package/dist/lib/ai/tools.js.map +1 -1
  233. package/dist/lib/ai/translate-document.d.ts +39 -5
  234. package/dist/lib/ai/translate-document.js +160 -51
  235. package/dist/lib/ai/translate-document.js.map +1 -1
  236. package/dist/lib/ai/types.d.ts +34 -23
  237. package/dist/lib/ai/types.js.map +1 -1
  238. package/dist/lib/ai/update-fields.d.ts +21 -12
  239. package/dist/lib/ai/update-fields.js +118 -177
  240. package/dist/lib/ai/update-fields.js.map +1 -1
  241. package/dist/lib/ai/writer-form.d.ts +116 -0
  242. package/dist/lib/ai/writer-form.js +429 -0
  243. package/dist/lib/ai/writer-form.js.map +1 -0
  244. package/dist/lib/ai-current-document.d.ts +11 -11
  245. package/dist/lib/ai-current-document.js +4 -4
  246. package/dist/lib/ai-current-document.js.map +1 -1
  247. package/dist/lib/assets/upload.d.ts +31 -2
  248. package/dist/lib/assets/upload.js +61 -6
  249. package/dist/lib/assets/upload.js.map +1 -1
  250. package/dist/lib/clipboard/os-clipboard.d.ts +10 -2
  251. package/dist/lib/clipboard/os-clipboard.js +8 -4
  252. package/dist/lib/clipboard/os-clipboard.js.map +1 -1
  253. package/dist/lib/clipboard/paste-target.d.ts +18 -9
  254. package/dist/lib/clipboard/paste-target.js +8 -7
  255. package/dist/lib/clipboard/paste-target.js.map +1 -1
  256. package/dist/lib/cms/field-patch.d.ts +15 -0
  257. package/dist/lib/cms/field-patch.js +25 -0
  258. package/dist/lib/cms/field-patch.js.map +1 -0
  259. package/dist/lib/cms/published-documents.d.ts +1 -1
  260. package/dist/lib/cms/published-documents.js +1 -1
  261. package/dist/lib/cms/published-documents.js.map +1 -1
  262. package/dist/lib/cms-views/view-model.d.ts +3 -3
  263. package/dist/lib/collection-view/__fixtures__/document-writes.d.ts +6 -0
  264. package/dist/lib/collection-view/__fixtures__/document-writes.js +23 -0
  265. package/dist/lib/collection-view/__fixtures__/document-writes.js.map +1 -0
  266. package/dist/lib/collection-view/collection-view-source.d.ts +69 -6
  267. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  268. package/dist/lib/data/collection-content.d.ts +4 -13
  269. package/dist/lib/data/collection-content.js +11 -5
  270. package/dist/lib/data/collection-content.js.map +1 -1
  271. package/dist/lib/dnd/drop-rules.d.ts +25 -31
  272. package/dist/lib/dnd/drop-rules.js +53 -59
  273. package/dist/lib/dnd/drop-rules.js.map +1 -1
  274. package/dist/lib/engine-version.d.ts +1 -1
  275. package/dist/lib/engine-version.js +1 -1
  276. package/dist/lib/engine-version.js.map +1 -1
  277. package/dist/lib/i18n/messages.de.js +72 -15
  278. package/dist/lib/i18n/messages.de.js.map +1 -1
  279. package/dist/lib/i18n/messages.en.d.ts +61 -12
  280. package/dist/lib/i18n/messages.en.js +76 -16
  281. package/dist/lib/i18n/messages.en.js.map +1 -1
  282. package/dist/lib/operations/__fixtures__/write-document.d.ts +30 -0
  283. package/dist/lib/operations/__fixtures__/write-document.js +16 -0
  284. package/dist/lib/operations/__fixtures__/write-document.js.map +1 -0
  285. package/dist/lib/operations/cms-persistence.d.ts +124 -31
  286. package/dist/lib/operations/cms-persistence.js +447 -68
  287. package/dist/lib/operations/cms-persistence.js.map +1 -1
  288. package/dist/lib/operations/config-manifest-lookup.d.ts +35 -0
  289. package/dist/lib/operations/config-manifest-lookup.js +56 -0
  290. package/dist/lib/operations/config-manifest-lookup.js.map +1 -0
  291. package/dist/lib/operations/project-runtime-config.d.ts +39 -0
  292. package/dist/lib/operations/project-runtime-config.js +54 -0
  293. package/dist/lib/operations/project-runtime-config.js.map +1 -0
  294. package/dist/lib/operations/publish-with-revision.d.ts +64 -0
  295. package/dist/lib/operations/publish-with-revision.js +66 -0
  296. package/dist/lib/operations/publish-with-revision.js.map +1 -0
  297. package/dist/lib/operations/runtime-validator.d.ts +32 -0
  298. package/dist/lib/operations/runtime-validator.js +18 -0
  299. package/dist/lib/operations/runtime-validator.js.map +1 -0
  300. package/dist/lib/operations/session-hydration.d.ts +31 -14
  301. package/dist/lib/operations/session-hydration.js +12 -16
  302. package/dist/lib/operations/session-hydration.js.map +1 -1
  303. package/dist/lib/operations/studio-operations.d.ts +59 -2
  304. package/dist/lib/operations/studio-operations.js +76 -14
  305. package/dist/lib/operations/studio-operations.js.map +1 -1
  306. package/dist/lib/persistence/convex-adapter.d.ts +13 -5
  307. package/dist/lib/persistence/convex-adapter.js +189 -74
  308. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  309. package/dist/lib/persistence/convex-client.d.ts +65 -5
  310. package/dist/lib/persistence/convex-client.js +106 -3
  311. package/dist/lib/persistence/convex-client.js.map +1 -1
  312. package/dist/lib/persistence/index.js +17 -18
  313. package/dist/lib/persistence/index.js.map +1 -1
  314. package/dist/lib/persona/labels.js +4 -1
  315. package/dist/lib/persona/labels.js.map +1 -1
  316. package/dist/lib/query/operations-query.d.ts +1 -1
  317. package/dist/lib/query/operations-query.js +1 -1
  318. package/dist/lib/query/operations-query.js.map +1 -1
  319. package/dist/lib/references/impact.d.ts +21 -15
  320. package/dist/lib/references/impact.js +13 -26
  321. package/dist/lib/references/impact.js.map +1 -1
  322. package/dist/lib/references/references-store.d.ts +9 -0
  323. package/dist/lib/references/references-store.js +24 -4
  324. package/dist/lib/references/references-store.js.map +1 -1
  325. package/dist/lib/routes/route-issues.d.ts +6 -6
  326. package/dist/lib/routes/route-issues.js +26 -80
  327. package/dist/lib/routes/route-issues.js.map +1 -1
  328. package/dist/lib/workspace/config-issues.js +2 -1
  329. package/dist/lib/workspace/config-issues.js.map +1 -1
  330. package/dist/lib/workspace/index.d.ts +31 -1
  331. package/dist/lib/workspace/index.js +64 -17
  332. package/dist/lib/workspace/index.js.map +1 -1
  333. package/dist/lib/workspace/switcher-source.d.ts +3 -3
  334. package/dist/lib/workspace/switcher-source.js.map +1 -1
  335. package/dist/lib/workspace/workspace-source.d.ts +2 -2
  336. package/dist/lib/workspace/workspace-source.js +2 -1
  337. package/dist/lib/workspace/workspace-source.js.map +1 -1
  338. package/package.json +3 -3
  339. package/dist/components/cms/cms-composition-validators.d.ts +0 -16
  340. package/dist/components/cms/cms-composition-validators.js +0 -133
  341. package/dist/components/cms/cms-composition-validators.js.map +0 -1
  342. package/dist/components/cms/publish-clean.d.ts +0 -28
  343. package/dist/components/cms/publish-clean.js +0 -38
  344. package/dist/components/cms/publish-clean.js.map +0 -1
  345. package/dist/components/cms/use-save-mode.d.ts +0 -35
  346. package/dist/components/cms/use-save-mode.js +0 -54
  347. package/dist/components/cms/use-save-mode.js.map +0 -1
  348. package/dist/lib/assets/asset-placements.d.ts +0 -28
  349. package/dist/lib/assets/asset-placements.js +0 -83
  350. package/dist/lib/assets/asset-placements.js.map +0 -1
  351. package/dist/lib/cms/document-gate.d.ts +0 -47
  352. package/dist/lib/cms/document-gate.js +0 -61
  353. package/dist/lib/cms/document-gate.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/workspace/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { frontendUrlForEnvironment } from '@elytracms/core/studio-core'\nimport type {\n BreakpointConfig,\n ElytraConfigInput,\n ResolvedWorkspaceProject,\n} from '@elytracms/core/studio-core'\nimport type { CollectionDef, LocaleConfig, RouteRecord } from '@elytracms/core/cms-core'\nimport type { SidebarNode } from '../../components/app-views/module'\nimport type { CustomFieldRenderer, StudioConfig } from '../../components/app-views/studio-config'\nimport type { GeneratedPreviewLookup } from '../../components/builder/blocks-model'\nimport { provideStudioEnv } from '../persistence/backend-mode'\nimport { resolveWorkspaceState } from './workspace-source'\nimport type { WorkspaceState } from './workspace-source'\n\nexport {\n PROJECT_CONFIG_FILE,\n PROJECT_CONFIG_ISSUE_KEYS,\n PROJECT_CONFIG_POINT,\n projectConfigIssues,\n} from './config-issues'\nexport { resolveWorkspaceState } from './workspace-source'\nexport type { WorkspaceSources, WorkspaceState } from './workspace-source'\nexport {\n isSingleProjectWorkspace,\n launcherAutoForwardTarget,\n persistedProjects,\n selectSwitcherProjects,\n soleWorkspaceProject,\n unprovisionedProjects,\n} from './switcher-source'\nexport type { SwitcherEntry, SwitcherSourceInput } from './switcher-source'\n\nlet cached: WorkspaceState | null = null\nlet studioConfig: StudioConfig | null = null\n\n/**\n * Mount the studio against a project config (EC-223 mount contract) and an\n * optional studio-only customization config (EC-236). The consumer's entry point\n * does `import config from './elytra.config'` + `import studio from\n * './studio.config'` and calls this — an explicit init, NOT an `import.meta.glob`\n * autoload that reaches into the consumer's filesystem (Oli, 2026-06-19: \"there's\n * always an entry point that can trigger a function call\").\n *\n * Two configs, deliberately: `config` (`elytra.config.ts`) is the project\n * definition, shared with the `frontend/` delivery bundle and projected to data;\n * `studio` (`studio.config.tsx` via `defineStudio`) is live builder UI code — the\n * section rail, custom field renderers, branding — that must NEVER reach delivery.\n * Keeping them separate is the customization boundary principle in the mount\n * signature. Seeds the workspace state + the studio-config holder every later\n * query (`getActiveProjectSlug`, `resolveDeployment*`) reads.\n *\n * EC-357 — `options.env`: the consumer's `import.meta.env`, captured HERE because\n * the entry module is code Vite always transforms live. The engine's own\n * `import.meta.env` reads are unreliable inside dep-optimized package code (Vite ≥8\n * omits custom `VITE_*` values there; Vite 7 froze them at optimize time), which\n * silently broke backend URL / mode / dev-login resolution in real consumers.\n * Always pass it: `createStudio(config, studioConfig, { env: import.meta.env })`.\n */\nexport interface StudioMountOptions {\n /** The consumer entry's `import.meta.env` (see `provideStudioEnv` for the contract). */\n env?: Record<string, unknown>\n}\n\nexport function createStudio(\n config: ElytraConfigInput,\n studio?: StudioConfig,\n options?: StudioMountOptions,\n): void {\n provideStudioEnv(options?.env)\n cached = resolveWorkspaceState({\n fileModule: { default: config },\n fileName: 'elytra.config.ts',\n })\n studioConfig = studio ?? null\n}\n\n/** Test seam: set/clear the studio customization config. Never used by app code. */\nexport function setStudioConfigForTests(next: StudioConfig | null): void {\n studioConfig = next\n}\n\n/**\n * The active project slug (EC-179, vision AD-8). One project per backend, so the\n * active project selects which Convex deployment the studio talks to. The root\n * layout sets this from the URL (`/<slug>`) — or, on the sign-in screen, from\n * the `redirect` target's slug — before the auth/persistence adapters are\n * selected, so the *initial* sign-in authenticates against the project the user\n * is heading for (Sanity-style: authenticate to one workspace, then the full\n * config-based switcher is available). `null` at the launcher → the default\n * (first) configured project's deployment.\n */\nlet activeProjectSlug: string | null = null\n\n/** Set the active project slug; returns whether the resolved deployment changed. */\nexport function setActiveProjectSlug(slug: string | null): boolean {\n const before = resolveDeploymentUrl('')\n activeProjectSlug = slug && slug.trim() !== '' ? slug : null\n return resolveDeploymentUrl('') !== before\n}\n\nexport function getActiveProjectSlug(): string | null {\n return activeProjectSlug\n}\n\n/** Test seam: reset the active project slug. Never used by app code. */\nexport function setActiveProjectSlugForTests(slug: string | null): void {\n activeProjectSlug = slug\n}\n\n/**\n * Reserved first-path segments that are NOT project slugs: the studio's own\n * root routes (EC-220). Project surfaces now live at `/<slug>/...` (no `/p/`\n * prefix), so the studio's root namespace is shared between project slugs and\n * these fixed routes — they must be excluded from slug extraction. Keep in\n * sync with the non-`$projectId` files in `src/routes/`; the launcher (`/`) is\n * the empty segment, excluded by the regex requiring one path char.\n */\nconst RESERVED_ROOT_SEGMENTS: ReadonlySet<string> = new Set(['sign-in', 'styleguide'])\n\n/** Extract a project slug from a studio path (`/<slug>/...`), else `null`. */\nexport function projectSlugFromPath(pathname: string): string | null {\n const match = /^\\/([^/?#]+)/.exec(pathname)\n if (!match) return null\n const segment = decodeURIComponent(match[1]!)\n return RESERVED_ROOT_SEGMENTS.has(segment) ? null : segment\n}\n\n/**\n * The project whose backend the studio should bind for a given location\n * (EC-179). A project path (`/<slug>`) binds that project; the sign-in screen\n * binds the project from its `redirect` target so the *initial* sign-in\n * authenticates against the backend the user is heading for (Sanity-style).\n * Anywhere else (the launcher) → `null`, i.e. the default/first project.\n */\nexport function activeSlugForLocation(pathname: string, redirect?: string): string | null {\n const direct = projectSlugFromPath(pathname)\n if (direct) return direct\n if (pathname === '/sign-in' && redirect) return projectSlugFromPath(redirect)\n return null\n}\n\n/** The active config project (by slug), or the first configured project. */\nfunction activeConfigProject() {\n const state = getWorkspaceState()\n if (state.kind !== 'config') return undefined\n if (activeProjectSlug) {\n const match = state.projects.find((p) => p.slug === activeProjectSlug)\n if (match) return match\n }\n return state.projects[0]\n}\n\n/**\n * The studio's resolved workspace state. Deterministic per boot: the config is\n * static input (bundled file or deployment env), so it is resolved once and\n * cached — every consumer (gate, switcher, launcher) sees the same state.\n */\nexport function getWorkspaceState(): WorkspaceState {\n // Seeded by `createStudio(config)` at the entry point. Until then — or when the\n // consumer wires no config — resolve to the empty `none` state (the single-project\n // golden path), rather than reaching for the filesystem.\n if (!cached) cached = resolveWorkspaceState({})\n return cached\n}\n\n/** Test seam: clear/replace the cached state. Never used by app code. */\nexport function setWorkspaceStateForTests(next: WorkspaceState | null): void {\n cached = next\n}\n\n/**\n * Resolve the Convex deployment URL the studio should connect to (vision AD-8:\n * the backend is wired in config). One project per backend, so the **active**\n * project (see {@link setActiveProjectSlug}) selects the deployment via its\n * `convex.deployment` (or its production environment's). At the launcher (no\n * active project) the first configured project is the default backend. Absent\n * config — or a project without a wired deployment — falls back to\n * `VITE_CONVEX_URL` (`fallbackUrl`) for local single-deployment development.\n */\nexport function resolveDeploymentUrl(fallbackUrl: string): string {\n return projectDeploymentUrl(activeConfigProject(), fallbackUrl)\n}\n\n/** The deployment URL for a specific project slug (per-backend auth checks). */\nexport function deploymentUrlForSlug(slug: string, fallbackUrl: string): string {\n const state = getWorkspaceState()\n const project = state.kind === 'config' ? state.projects.find((p) => p.slug === slug) : undefined\n return projectDeploymentUrl(project, fallbackUrl)\n}\n\n/** A project's wired deployment: per-environment ref → project ref → env fallback. */\nfunction projectDeploymentUrl(\n project: ResolvedWorkspaceProject | undefined,\n fallbackUrl: string,\n): string {\n if (!project) return fallbackUrl\n const envDeployment = project.environments.find((e) => e.convex?.deployment)?.convex?.deployment\n return envDeployment ?? project.convex?.deployment ?? fallbackUrl\n}\n\n/**\n * The slug of the project bound to the active deployment (EC-179). One project\n * per backend, so the active config project's slug identifies the deployment's\n * one project; the Convex adapter stamps it onto deployment-scoped records.\n * Absent config falls back to `fallbackId` (used only when wired by env alone).\n */\nexport function resolveDeploymentProjectId(fallbackId: string): string {\n return activeConfigProject()?.slug ?? fallbackId\n}\n\n/**\n * The CMS collections of the active config project (AD-11, EC-183). CMS\n * collection schemas are config-owned: the studio reads the model from\n * `elytra.config.ts` (served through the persistence port — the backend has no\n * `cmsSchemas` table). `[]` when the active project declares none, or when the\n * studio is wired by env alone with no config.\n */\nexport function resolveDeploymentCollections(): CollectionDef[] {\n return activeConfigProject()?.collections ?? []\n}\n\n/**\n * The canvas preview breakpoints of the active config project (EC-250).\n * Config-sourced (config-as-code, derived from the site's Tailwind `screens`);\n * `undefined` when the project declares none — the canvas then falls back to\n * Tailwind's default screens (see `resolveCanvasViewports`), never a baked-in triple.\n */\nexport function resolveDeploymentBreakpoints(): BreakpointConfig[] | undefined {\n return activeConfigProject()?.breakpoints\n}\n\n/**\n * The custom field renderers from the studio config (EC-238): control name →\n * component. Studio-only live code (from `defineStudio`, never the project schema\n * or the delivery bundle); `undefined` when none — the document editor then uses\n * its built-in control for every field. Studio-wide (not per-project): the rail and\n * renderers customize the studio, not one backend.\n */\nexport function resolveDeploymentFieldRenderers(): Record<string, CustomFieldRenderer> | undefined {\n return studioConfig?.fieldRenderers\n}\n\n/**\n * The studio-UI localization config from the studio config (EC-241): the default\n * locale + imported translation catalogs. Studio-only live code (from `defineStudio`,\n * never the project schema or the delivery bundle); `undefined` when none — the\n * studio then renders its built-in English chrome. Studio-wide, like the rail and\n * field renderers (localizes the studio, not one backend).\n */\nexport function resolveDeploymentI18n(): StudioConfig['i18n'] {\n return studioConfig?.i18n\n}\n\n/**\n * The AI platform config from the studio config (EC-331): provider, knowledge, and\n * limits. Studio-only live code (from `defineStudio`, never the delivery bundle);\n * `undefined` when no `ai` block is configured — the feature is then fully absent\n * (the AI subsystem never boots, so `@elytracms/studio/lib/ai`'s runtime — and the\n * `ai`/`@ai-sdk/*` SDK behind it — is never imported). Studio-wide, like the rail\n * and field renderers.\n */\nexport function resolveDeploymentAi(): StudioConfig['ai'] {\n return studioConfig?.ai\n}\n\n/**\n * The generated-preview lookup from the studio config (EC-439 AD-2, the seam\n * EC-440 fills): a pure `componentId + fixtureName → URL` function the blocks\n * panel threads into its tile model. Studio-only live code (from `defineStudio`,\n * never the delivery bundle); `undefined` when none — tiles then fall back from\n * a curated `manifest.previewImage` straight to the icon+name placeholder.\n * Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentGeneratedPreviews(): GeneratedPreviewLookup | undefined {\n return studioConfig?.previews?.generated\n}\n\n/**\n * The configurable section rail from the studio config (EC-237): an ordered list of\n * `nav.*` nodes (views / groups / separators). Studio-only live code (carries view\n * objects + icons); `undefined` when none — the studio then derives the default\n * rail from its built-in views. Invalid entries surface as visible issues at\n * resolution, never silent.\n */\nexport function resolveDeploymentSidebar(): SidebarNode[] | undefined {\n return studioConfig?.sidebar\n}\n\n/**\n * The canvas page-surface theme from the studio config (EC-389): body classes +\n * raw CSS for the preview iframe, so the canvas wears the SITE's chrome. Studio-only\n * (from `defineStudio`, never the delivery bundle); `undefined` when none — the\n * canvas keeps its built-in defaults (`bg-background text-foreground font-sans`\n * body). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentCanvasTheme(): StudioConfig['canvas'] {\n return studioConfig?.canvas\n}\n\n/**\n * The reusable-elements declaration from the studio config (EC-467): WHICH\n * component is the project's reuse embed. Studio-only (from `defineStudio`, never\n * the delivery bundle); `undefined` when none — the studio then INFERS the embed\n * from the manifests + collections exactly as it always has (absence means infer,\n * not off). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentReuse(): StudioConfig['reuse'] {\n return studioConfig?.reuse\n}\n\n/**\n * The resolved URL routes of the active config project (EC-207, \"routes as\n * code\"). Routes are config-sourced: the studio reads the materialized\n * `RouteRecord[]` from the active `elytra.config.ts` project — never from the\n * backend (the `routes` system collection is retired). The Routes surface and\n * the per-page URL preview both read through here. `[]` when the active project\n * declares none, or when the studio is wired by env alone with no config.\n */\nexport function resolveDeploymentRoutes(): RouteRecord[] {\n return activeConfigProject()?.routes ?? []\n}\n\n// Redirects are editorial DATA now (EC-303), managed in the studio Redirects rail\n// and served dynamically — no longer resolved from config. `resolveDeploymentRedirects`\n// is retired; static/infra redirects are the dev's job via native next.config/vercel.\n\n/**\n * The active config project's `LocaleConfig` (EC-357 finding #3). Locales are\n * structure and structure is code: when `elytra.config.ts` declares them, they\n * drive the studio's ambient locale switcher in EVERY mode — the CMS surface must\n * not fall back to the sample `en/de` fixture (playground, or a connected first\n * run with nothing persisted) when the config says otherwise. `undefined` when no\n * config project is active (env-only wiring keeps the existing kit/sample\n * fallbacks).\n *\n * EC-519: this used to REASSEMBLE the config's flat `locales` + `defaultLocale`\n * pair into a `LocaleConfig`, and had grown its own repair rule — an absent or\n * out-of-set default fell back to `locales[0]`. That is a fourth place a locale\n * could be invented, in the surface furthest from the author. The resolver now\n * hands over the same `LocaleConfig` the delivery host takes, so there is nothing\n * to reassemble and nothing to repair: a default outside the set is a config\n * ERROR (`default-locale-not-in-locales`) and the project never resolves.\n */\nexport function resolveDeploymentLocaleConfig(): LocaleConfig | undefined {\n return activeConfigProject()?.locales\n}\n\n/**\n * The active project's components reference (EC-194, AD-11): where the consuming\n * repo's OWN real components live (`{ package }` or `{ dir }`). When set, the\n * studio registry composes with the repo's components instead of the built-in\n * demo fixtures — so the page builder edits the repo's actual component library.\n * `undefined` → the project ships no code components; the studio falls back to the\n * golden-path fixtures.\n */\nexport function resolveDeploymentComponents() {\n return activeConfigProject()?.components\n}\n\n/**\n * The active project's public site URL (EC-215): the live site's base URL the\n * studio links to (a document's live URL + open-live button, EC-216). Resolved\n * in lockstep with {@link resolveDeploymentUrl} — the environment bound to the\n * active deployment (the first with a wired deployment) provides the URL, falling\n * back to the project-level `frontendUrl`. No active-environment selector exists\n * yet, so the bound environment is the active one. `undefined` when the active\n * project (or env-only wiring) configures no frontend URL.\n */\nexport function resolveDeploymentFrontendUrl(): string | undefined {\n const project = activeConfigProject()\n if (!project) return undefined\n const boundEnv = project.environments.find((e) => e.convex?.deployment)\n return frontendUrlForEnvironment(project, boundEnv?.key)\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/workspace/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEV_FRONTEND_URL_ENV } from '@elytracms/core/export-sync/deployment-ready'\nimport { projectFrontendUrl, RESERVED_STUDIO_ROOT_SEGMENTS } from '@elytracms/core/studio-core'\nimport type {\n BreakpointConfig,\n ElytraConfigInput,\n ResolvedProject,\n} from '@elytracms/core/studio-core'\nimport type { CollectionDef, LocaleConfig, RouteRecord } from '@elytracms/core/cms-core'\nimport type { SidebarNode } from '../../components/app-views/module'\nimport type { CustomFieldRenderer, StudioConfig } from '../../components/app-views/studio-config'\nimport type { GeneratedPreviewLookup } from '../../components/builder/blocks-model'\nimport { isDevRuntime, provideStudioEnv, studioEnv } from '../persistence/backend-mode'\nimport { resolveWorkspaceState } from './workspace-source'\nimport type { WorkspaceState } from './workspace-source'\n\nexport {\n PROJECT_CONFIG_FILE,\n PROJECT_CONFIG_ISSUE_KEYS,\n PROJECT_CONFIG_POINT,\n projectConfigIssues,\n} from './config-issues'\nexport { resolveWorkspaceState } from './workspace-source'\nexport type { WorkspaceSources, WorkspaceState } from './workspace-source'\nexport {\n isSingleProjectWorkspace,\n launcherAutoForwardTarget,\n persistedProjects,\n selectSwitcherProjects,\n soleWorkspaceProject,\n unprovisionedProjects,\n} from './switcher-source'\nexport type { SwitcherEntry, SwitcherSourceInput } from './switcher-source'\n\nlet cached: WorkspaceState | null = null\nlet studioConfig: StudioConfig | null = null\n\n/**\n * Mount the studio against a project config (EC-223 mount contract) and an\n * optional studio-only customization config (EC-236). The consumer's entry point\n * does `import config from './elytra.config'` + `import studio from\n * './studio.config'` and calls this — an explicit init, NOT an `import.meta.glob`\n * autoload that reaches into the consumer's filesystem (Oli, 2026-06-19: \"there's\n * always an entry point that can trigger a function call\").\n *\n * Two configs, deliberately: `config` (`elytra.config.ts`) is the project\n * definition, shared with the `frontend/` delivery bundle and projected to data;\n * `studio` (`studio.config.tsx` via `defineStudio`) is live builder UI code — the\n * section rail, custom field renderers, branding — that must NEVER reach delivery.\n * Keeping them separate is the customization boundary principle in the mount\n * signature. Seeds the workspace state + the studio-config holder every later\n * query (`getActiveProjectSlug`, `resolveDeployment*`) reads.\n *\n * EC-357 — `options.env`: the consumer's `import.meta.env`, captured HERE because\n * the entry module is code Vite always transforms live. The engine's own\n * `import.meta.env` reads are unreliable inside dep-optimized package code (Vite ≥8\n * omits custom `VITE_*` values there; Vite 7 froze them at optimize time), which\n * silently broke backend URL / mode / dev-login resolution in real consumers.\n * Always pass it: `createStudio(config, studioConfig, { env: import.meta.env })`.\n */\nexport interface StudioMountOptions {\n /** The consumer entry's `import.meta.env` (see `provideStudioEnv` for the contract). */\n env?: Record<string, unknown>\n}\n\nexport function createStudio(\n config: ElytraConfigInput,\n studio?: StudioConfig,\n options?: StudioMountOptions,\n): void {\n provideStudioEnv(options?.env)\n cached = resolveWorkspaceState({\n fileModule: { default: config },\n fileName: 'elytra.config.ts',\n })\n studioConfig = studio ?? null\n}\n\n/** Test seam: set/clear the studio customization config. Never used by app code. */\nexport function setStudioConfigForTests(next: StudioConfig | null): void {\n studioConfig = next\n}\n\n/**\n * The active project slug (EC-179, vision AD-8). One project per backend, so the\n * active project selects which Convex deployment the studio talks to. The root\n * layout sets this from the URL (`/<slug>`) — or, on the sign-in screen, from\n * the `redirect` target's slug — before the auth/persistence adapters are\n * selected, so the *initial* sign-in authenticates against the project the user\n * is heading for (Sanity-style: authenticate to one workspace, then the full\n * config-based switcher is available). `null` at the launcher → the default\n * (first) configured project's deployment.\n */\nlet activeProjectSlug: string | null = null\n\n/** Set the active project slug; returns whether the resolved deployment changed. */\nexport function setActiveProjectSlug(slug: string | null): boolean {\n const before = resolveDeploymentUrl('')\n activeProjectSlug = slug && slug.trim() !== '' ? slug : null\n return resolveDeploymentUrl('') !== before\n}\n\nexport function getActiveProjectSlug(): string | null {\n return activeProjectSlug\n}\n\n/** Test seam: reset the active project slug. Never used by app code. */\nexport function setActiveProjectSlugForTests(slug: string | null): void {\n activeProjectSlug = slug\n}\n\n/**\n * Reserved first-path segments that are NOT project slugs: the studio's own\n * root routes (EC-220). Project surfaces now live at `/<slug>/...` (no `/p/`\n * prefix), so the studio's root namespace is shared between project slugs and\n * these fixed routes — they must be excluded from slug extraction. The launcher\n * (`/`) is the empty segment, excluded by the regex requiring one path char.\n *\n * EC-583 (RESIDUE-8): declared ONCE, in core — the resolver refuses a project slug in\n * this set, so a config can no longer name a project this reader would skip.\n */\nconst RESERVED_ROOT_SEGMENTS: ReadonlySet<string> = new Set(RESERVED_STUDIO_ROOT_SEGMENTS)\n\n/** Extract a project slug from a studio path (`/<slug>/...`), else `null`. */\nexport function projectSlugFromPath(pathname: string): string | null {\n const match = /^\\/([^/?#]+)/.exec(pathname)\n if (!match) return null\n const segment = decodeURIComponent(match[1]!)\n return RESERVED_ROOT_SEGMENTS.has(segment) ? null : segment\n}\n\n/**\n * The project whose backend the studio should bind for a given location\n * (EC-179). A project path (`/<slug>`) binds that project; the sign-in screen\n * binds the project from its `redirect` target so the *initial* sign-in\n * authenticates against the backend the user is heading for (Sanity-style).\n * Anywhere else (the launcher) → `null`, i.e. the default/first project.\n */\nexport function activeSlugForLocation(pathname: string, redirect?: string): string | null {\n const direct = projectSlugFromPath(pathname)\n if (direct) return direct\n if (pathname === '/sign-in' && redirect) return projectSlugFromPath(redirect)\n return null\n}\n\n/**\n * THE reader of \"the config project with this slug\" (EC-595 A2 → EC-582: one reader —\n * the operations runtime resolves an OPERATION's project through it, whether or not it\n * is the active one; the deployment lookup and the active project read it too).\n * `undefined` for a slug no config project declares (env-only wiring, fixtures).\n */\nexport function configProjectFor(slug: string): ResolvedProject | undefined {\n const state = getWorkspaceState()\n return state.kind === 'config' ? state.projects.find((p) => p.slug === slug) : undefined\n}\n\n/** The active config project (by slug), or the first configured project. */\nfunction activeConfigProject() {\n const state = getWorkspaceState()\n if (state.kind !== 'config') return undefined\n return (activeProjectSlug ? configProjectFor(activeProjectSlug) : undefined) ?? state.projects[0]\n}\n\n/**\n * The studio's resolved workspace state. Deterministic per boot: the config is\n * static input (bundled file or deployment env), so it is resolved once and\n * cached — every consumer (gate, switcher, launcher) sees the same state.\n */\nexport function getWorkspaceState(): WorkspaceState {\n // Seeded by `createStudio(config)` at the entry point. Until then — or when the\n // consumer wires no config — resolve to the empty `none` state (the single-project\n // golden path), rather than reaching for the filesystem.\n if (!cached) cached = resolveWorkspaceState({})\n return cached\n}\n\n/** Test seam: clear/replace the cached state. Never used by app code. */\nexport function setWorkspaceStateForTests(next: WorkspaceState | null): void {\n cached = next\n}\n\n/**\n * Resolve the Convex deployment URL the studio should connect to (vision AD-8:\n * the backend is wired in config). One project per backend, so the **active**\n * project (see {@link setActiveProjectSlug}) selects the deployment via its\n * `convex.deployment` (or its production environment's). At the launcher (no\n * active project) the first configured project is the default backend. Absent\n * config — or a project without a wired deployment — falls back to\n * `VITE_CONVEX_URL` (`fallbackUrl`) for local single-deployment development.\n */\nexport function resolveDeploymentUrl(fallbackUrl: string): string {\n return projectDeploymentUrl(activeConfigProject(), fallbackUrl)\n}\n\n/** The deployment URL for a specific project slug (per-backend auth checks). */\nexport function deploymentUrlForSlug(slug: string, fallbackUrl: string): string {\n return projectDeploymentUrl(configProjectFor(slug), fallbackUrl)\n}\n\n/** A project's wired deployment: per-environment ref → project ref → env fallback. */\nfunction projectDeploymentUrl(\n project: ResolvedProject | undefined,\n fallbackUrl: string,\n): string {\n if (!project) return fallbackUrl\n const envDeployment = project.environments.find((e) => e.convex?.deployment)?.convex?.deployment\n return envDeployment ?? project.convex?.deployment ?? fallbackUrl\n}\n\n/**\n * The slug of the project bound to the active deployment (EC-179). One project\n * per backend, so the active config project's slug identifies the deployment's\n * one project; the Convex adapter stamps it onto deployment-scoped records.\n * Absent config falls back to `fallbackId` (used only when wired by env alone).\n */\nexport function resolveDeploymentProjectId(fallbackId: string): string {\n return activeConfigProject()?.slug ?? fallbackId\n}\n\n/**\n * The CMS collections of the active config project (AD-11, EC-183). CMS\n * collection schemas are config-owned: the studio reads the model from\n * `elytra.config.ts` (served through the persistence port — the backend has no\n * `cmsSchemas` table). `[]` when the active project declares none, or when the\n * studio is wired by env alone with no config.\n */\nexport function resolveDeploymentCollections(): CollectionDef[] {\n return activeConfigProject()?.collections ?? []\n}\n\n/**\n * The canvas preview breakpoints of the active config project (EC-250).\n * Config-sourced (config-as-code, derived from the site's Tailwind `screens`);\n * `undefined` when the project declares none — the canvas then falls back to\n * Tailwind's default screens (see `resolveCanvasViewports`), never a baked-in triple.\n */\nexport function resolveDeploymentBreakpoints(): BreakpointConfig[] | undefined {\n return activeConfigProject()?.breakpoints\n}\n\n/**\n * The custom field renderers from the studio config (EC-238): control name →\n * component. Studio-only live code (from `defineStudio`, never the project schema\n * or the delivery bundle); `undefined` when none — the document editor then uses\n * its built-in control for every field. Studio-wide (not per-project): the rail and\n * renderers customize the studio, not one backend.\n */\nexport function resolveDeploymentFieldRenderers(): Record<string, CustomFieldRenderer> | undefined {\n return studioConfig?.fieldRenderers\n}\n\n/**\n * The studio-UI localization config from the studio config (EC-241): the default\n * locale + imported translation catalogs. Studio-only live code (from `defineStudio`,\n * never the project schema or the delivery bundle); `undefined` when none — the\n * studio then renders its built-in English chrome. Studio-wide, like the rail and\n * field renderers (localizes the studio, not one backend).\n */\nexport function resolveDeploymentI18n(): StudioConfig['i18n'] {\n return studioConfig?.i18n\n}\n\n/**\n * The AI platform config from the studio config (EC-331): provider, knowledge, and\n * limits. Studio-only live code (from `defineStudio`, never the delivery bundle);\n * `undefined` when no `ai` block is configured — the feature is then fully absent\n * (the AI subsystem never boots, so `@elytracms/studio/lib/ai`'s runtime — and the\n * `ai`/`@ai-sdk/*` SDK behind it — is never imported). Studio-wide, like the rail\n * and field renderers.\n */\nexport function resolveDeploymentAi(): StudioConfig['ai'] {\n return studioConfig?.ai\n}\n\n/**\n * The generated-preview lookup from the studio config (EC-439 AD-2, the seam\n * EC-440 fills): a pure `componentId + fixtureName → URL` function the blocks\n * panel threads into its tile model. Studio-only live code (from `defineStudio`,\n * never the delivery bundle); `undefined` when none — tiles then fall back from\n * a curated `manifest.previewImage` straight to the icon+name placeholder.\n * Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentGeneratedPreviews(): GeneratedPreviewLookup | undefined {\n return studioConfig?.previews?.generated\n}\n\n/**\n * The configurable section rail from the studio config (EC-237): an ordered list of\n * `nav.*` nodes (views / groups / separators). Studio-only live code (carries view\n * objects + icons); `undefined` when none — the studio then derives the default\n * rail from its built-in views. Invalid entries surface as visible issues at\n * resolution, never silent.\n */\nexport function resolveDeploymentSidebar(): SidebarNode[] | undefined {\n return studioConfig?.sidebar\n}\n\n/**\n * The canvas page-surface theme from the studio config (EC-389): body classes +\n * raw CSS for the preview iframe, so the canvas wears the SITE's chrome. Studio-only\n * (from `defineStudio`, never the delivery bundle); `undefined` when none — the\n * canvas keeps its built-in defaults (`bg-background text-foreground font-sans`\n * body). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentCanvasTheme(): StudioConfig['canvas'] {\n return studioConfig?.canvas\n}\n\n/**\n * The reusable-elements declaration from the studio config (EC-467): WHICH\n * component is the project's reuse embed. Studio-only (from `defineStudio`, never\n * the delivery bundle); `undefined` when none — the studio then INFERS the embed\n * from the manifests + collections exactly as it always has (absence means infer,\n * not off). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentReuse(): StudioConfig['reuse'] {\n return studioConfig?.reuse\n}\n\n/**\n * The resolved URL routes of the active config project (EC-207, \"routes as\n * code\"). Routes are config-sourced: the studio reads the materialized\n * `RouteRecord[]` from the active `elytra.config.ts` project — never from the\n * backend (the `routes` system collection is retired). The Routes surface and\n * the per-page URL preview both read through here. `[]` when the active project\n * declares none, or when the studio is wired by env alone with no config.\n */\nexport function resolveDeploymentRoutes(): RouteRecord[] {\n return activeConfigProject()?.routes ?? []\n}\n\n/**\n * The active project's PAGE collections (EC-590; EC-565 Round 3, 3g): the collections at\n * least one route addresses, in declaration order. \"A page collection\" is derived from the\n * routes — the engine dictates no collection named `page`.\n */\nexport function resolveDeploymentPageCollections(): string[] {\n return pageCollectionsOf(resolveDeploymentRoutes())\n}\n\n/** The collections a route set addresses, deduplicated in declaration order (pure). */\nexport function pageCollectionsOf(routes: readonly RouteRecord[]): string[] {\n return [...new Set(routes.flatMap((route) => (route.document ? [route.document.collection] : [])))]\n}\n\n// Redirects are editorial DATA now (EC-303), managed in the studio Redirects rail\n// and served dynamically — no longer resolved from config. `resolveDeploymentRedirects`\n// is retired; static/infra redirects are the dev's job via native next.config/vercel.\n\n/**\n * The active config project's `LocaleConfig` (EC-357 finding #3). Locales are\n * structure and structure is code: when `elytra.config.ts` declares them, they\n * drive the studio's ambient locale switcher in EVERY mode — the CMS surface must\n * not fall back to the sample `en/de` fixture (playground, or a connected first\n * run with nothing persisted) when the config says otherwise. `undefined` when no\n * config project is active (env-only wiring keeps the existing kit/sample\n * fallbacks).\n *\n * EC-519: this used to REASSEMBLE the config's flat `locales` + `defaultLocale`\n * pair into a `LocaleConfig`, and had grown its own repair rule — an absent or\n * out-of-set default fell back to `locales[0]`. That is a fourth place a locale\n * could be invented, in the surface furthest from the author. The resolver now\n * hands over the same `LocaleConfig` the delivery host takes, so there is nothing\n * to reassemble and nothing to repair: a default outside the set is a config\n * ERROR (`default-locale-not-in-locales`) and the project never resolves.\n */\nexport function resolveDeploymentLocaleConfig(): LocaleConfig | undefined {\n return activeConfigProject()?.locales\n}\n\n/**\n * The active project's components reference (EC-194, AD-11): where the consuming\n * repo's OWN real components live (`{ package }` or `{ dir }`). When set, the\n * studio registry composes with the repo's components instead of the built-in\n * demo fixtures — so the page builder edits the repo's actual component library.\n * `undefined` → the project ships no code components; the studio falls back to the\n * golden-path fixtures.\n */\nexport function resolveDeploymentComponents() {\n return activeConfigProject()?.components\n}\n\n/**\n * The active project's public site URL (EC-215): the live site's base URL the\n * studio links to (a document's live URL + open-live button, EC-216). Resolved\n * in lockstep with {@link resolveDeploymentUrl} — the environment bound to the\n * active deployment (the first with a wired deployment) provides the URL, falling\n * back to the project-level `frontendUrl`. No active-environment selector exists\n * yet, so the bound environment is the active one. `undefined` when the active\n * project (or env-only wiring) configures no frontend URL.\n *\n * EC-583: the rule is core's `projectFrontendUrl` — the delivery host advertises the\n * same answer. In a DEV studio the URL the CLI handed over wins (Q15, see\n * {@link studioDevFrontendUrl}).\n */\nexport function resolveDeploymentFrontendUrl(): string | undefined {\n const project = activeConfigProject()\n if (!project) return undefined\n const devFrontendUrl = studioDevFrontendUrl()\n return projectFrontendUrl(project, devFrontendUrl !== undefined ? { devFrontendUrl } : {})\n}\n\nlet warnedInvalidDevFrontendUrl = false\n\n/**\n * Q15 RATIFIED (EC-565 Round 5, 5b; EC-583): the dev frontend URL comes from the process\n * that knows the port. `elytra dev` starts the frontend on `3000 + offset` and hands the\n * studio dev server `ELYTRA_DEV_FRONTEND_URL`; the studio reads it through the mount env\n * ({@link studioEnv}) — and **only in a dev runtime**, so a deployed studio never links to\n * a localhost the launcher once named. `elytra.config.ts` keeps reading no env (EC-532).\n *\n * A value that is not an http(s) URL is not used, and says so once in the console\n * rather than silently linking nowhere.\n */\nexport function studioDevFrontendUrl(): string | undefined {\n if (!isDevRuntime()) return undefined\n const raw = studioEnv()[DEV_FRONTEND_URL_ENV]?.trim()\n if (raw === undefined || raw === '') return undefined\n if (/^https?:\\/\\//.test(raw) && URL.canParse(raw)) return raw\n if (!warnedInvalidDevFrontendUrl) {\n warnedInvalidDevFrontendUrl = true\n console.error(\n `[elytra] ${DEV_FRONTEND_URL_ENV} is not an http(s) URL, so live links use the config's frontendUrl instead.`,\n )\n }\n return undefined\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { Project } from '@elytracms/core/studio-core';
2
- import type { ResolvedWorkspaceProject } from '@elytracms/core/studio-core';
2
+ import type { ResolvedProject } from '@elytracms/core/studio-core';
3
3
  import type { WorkspaceState } from './workspace-source.js';
4
4
  /**
5
5
  * Config-driven switcher source (EC-154, reframed for EC-179 per-backend
@@ -42,7 +42,7 @@ export type SwitcherEntry = {
42
42
  project: Project;
43
43
  } | {
44
44
  kind: 'unprovisioned';
45
- config: ResolvedWorkspaceProject;
45
+ config: ResolvedProject;
46
46
  };
47
47
  export interface SwitcherSourceInput {
48
48
  workspace: WorkspaceState;
@@ -80,4 +80,4 @@ export declare function launcherAutoForwardTarget(input: {
80
80
  explicitVisit: boolean;
81
81
  }): Project | null;
82
82
  /** The not-yet-provisioned config entries (rendered as explicit CLI-hint states). */
83
- export declare function unprovisionedProjects(entries: readonly SwitcherEntry[]): ResolvedWorkspaceProject[];
83
+ export declare function unprovisionedProjects(entries: readonly SwitcherEntry[]): ResolvedProject[];
@@ -1 +1 @@
1
- {"version":3,"file":"switcher-source.js","sourceRoot":"","sources":["../../../src/lib/workspace/switcher-source.ts"],"names":[],"mappings":"AAmDA,MAAM,UAAU,sBAAsB,CAAC,KAA0B;IAC/D,IAAI,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,wEAAwE;QACxE,yEAAyE;QACzE,OAAO,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IACzE,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACjF,OAAO,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC7C,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACzC,OAAO,SAAS;YACd,CAAC,CAAC,EAAE,IAAI,EAAE,SAAkB,EAAE,OAAO,EAAE,SAAS,EAAE;YAClD,CAAC,CAAC,EAAE,IAAI,EAAE,eAAwB,EAAE,MAAM,EAAE,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,iBAAiB,CAAC,OAAiC;IACjE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AACtF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAiC;IACpE,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAA;IACtB,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;AAC/E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAiC;IACxE,OAAO,oBAAoB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;AAC/C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAMzC;IACC,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,aAAa;QAAE,OAAO,IAAI,CAAA;IACzD,OAAO,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;AAC5C,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,qBAAqB,CACnC,OAAiC;IAEjC,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAC3F,CAAC","sourcesContent":["import type { Project } from '@elytracms/core/studio-core'\nimport type { ResolvedWorkspaceProject } from '@elytracms/core/studio-core'\nimport type { WorkspaceState } from './workspace-source'\n\n/**\n * Config-driven switcher source (EC-154, reframed for EC-179 per-backend\n * projects). Pure logic deciding what the project switcher and launcher list:\n *\n * - **No workspace config** → exactly today's behavior: the adapter-listed\n * projects, untouched (single-project golden path).\n * - **Config present** → every configured project, in config order, as a\n * navigable entry; one that has no persisted counterpart yet is an explicit\n * `unprovisioned` state (\"run `elytra init`\") rather than an error. Persisted\n * projects *not* in the config are not listed: the config is the source of\n * truth for what this workspace is.\n *\n * ## The `unprovisioned` branch is currently unreachable FROM THE STUDIO (EC-470)\n *\n * This function will happily produce `unprovisioned` — `switcher-source.test.ts`\n * covers it directly. But the live studio can no longer feed it inputs that do:\n * `studio-context.tsx`'s `configProjects()` derives `persisted` from the SAME\n * `workspace.projects` array this function iterates (via `configProject`, which\n * sets `slug` from the config entry), so the `bySlug` lookup below always hits.\n * Under AD-8 project identity became config-owned, and \"provisioned\" stopped\n * being a per-project row the studio could miss — it is now a property of the\n * DEPLOYMENT (see `DeploymentSetupGate` / `elytra doctor`).\n *\n * Left in place deliberately rather than removed: the entry kind is load-bearing\n * for the EC-401 single-project predicates below (`soleWorkspaceProject` treats a\n * lone unprovisioned entry as \"do not auto-forward\"), so retiring it is a\n * separate, owner-sized decision — not a docs sweep. Recorded so the next reader\n * does not mistake it for live UI.\n *\n * Visibility is **not** filtered by membership. One project per backend (vision\n * AD-8): a configured project lives on its own deployment, so the *active*\n * backend's memberships cannot speak to another project's. Access is enforced\n * where it belongs — per-backend sign-in plus the EC-150 role hook on entry —\n * not by hiding projects you'd authenticate into. (Hiding them was the\n * single-backend assumption; cross-backend it just made projects unreachable.)\n */\n\nexport type SwitcherEntry =\n | { kind: 'project'; project: Project }\n | { kind: 'unprovisioned'; config: ResolvedWorkspaceProject }\n\nexport interface SwitcherSourceInput {\n workspace: WorkspaceState\n /** The studio store's read model (config-derived under a workspace config). */\n persisted: readonly Project[]\n}\n\nexport function selectSwitcherProjects(input: SwitcherSourceInput): SwitcherEntry[] {\n if (input.workspace.kind !== 'config') {\n // No-config golden path (an invalid config never reaches the switcher —\n // the DeploymentSetupGate blocks boot): today's adapter-listed behavior.\n return input.persisted.map((project) => ({ kind: 'project', project }))\n }\n\n const bySlug = new Map(input.persisted.map((project) => [project.slug, project]))\n return input.workspace.projects.map((config) => {\n const persisted = bySlug.get(config.slug)\n return persisted\n ? { kind: 'project' as const, project: persisted }\n : { kind: 'unprovisioned' as const, config }\n })\n}\n\n/** The persisted projects among the entries (for list surfaces filtering by status). */\nexport function persistedProjects(entries: readonly SwitcherEntry[]): Project[] {\n return entries.flatMap((entry) => (entry.kind === 'project' ? [entry.project] : []))\n}\n\n/**\n * The workspace's sole navigable project, or `null` (EC-401). Non-null exactly\n * when the workspace has ONE entry total and it is a real `project` — a single\n * *unprovisioned* entry keeps this `null` (the launcher's setup gate must stay\n * visible), and a second entry of any kind does too.\n */\nexport function soleWorkspaceProject(entries: readonly SwitcherEntry[]): Project | null {\n const [only] = entries\n return entries.length === 1 && only?.kind === 'project' ? only.project : null\n}\n\n/**\n * EC-401 — the ONE single-project predicate, shared by the launcher (skip the\n * dashboard hop) and the shell header (plain project name instead of the\n * switcher dropdown) so the two surfaces can never disagree.\n */\nexport function isSingleProjectWorkspace(entries: readonly SwitcherEntry[]): boolean {\n return soleWorkspaceProject(entries) !== null\n}\n\n/**\n * The launcher's auto-forward decision (EC-401), pure: the sole project to\n * redirect to, or `null` to render the launcher. A forced `?state=` verification\n * state and an explicit \"all projects\" navigation (see `launcherVisitState` in\n * the router seam) both keep the launcher visible; otherwise a single-project\n * workspace forwards straight into its project.\n */\nexport function launcherAutoForwardTarget(input: {\n entries: readonly SwitcherEntry[]\n /** Whether the launcher was opened with a forced `?state=` verification state. */\n forcedState: boolean\n /** Whether this location is an explicit \"all projects\" navigation. */\n explicitVisit: boolean\n}): Project | null {\n if (input.forcedState || input.explicitVisit) return null\n return soleWorkspaceProject(input.entries)\n}\n\n/** The not-yet-provisioned config entries (rendered as explicit CLI-hint states). */\nexport function unprovisionedProjects(\n entries: readonly SwitcherEntry[],\n): ResolvedWorkspaceProject[] {\n return entries.flatMap((entry) => (entry.kind === 'unprovisioned' ? [entry.config] : []))\n}\n"]}
1
+ {"version":3,"file":"switcher-source.js","sourceRoot":"","sources":["../../../src/lib/workspace/switcher-source.ts"],"names":[],"mappings":"AAmDA,MAAM,UAAU,sBAAsB,CAAC,KAA0B;IAC/D,IAAI,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,wEAAwE;QACxE,yEAAyE;QACzE,OAAO,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IACzE,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACjF,OAAO,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC7C,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACzC,OAAO,SAAS;YACd,CAAC,CAAC,EAAE,IAAI,EAAE,SAAkB,EAAE,OAAO,EAAE,SAAS,EAAE;YAClD,CAAC,CAAC,EAAE,IAAI,EAAE,eAAwB,EAAE,MAAM,EAAE,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,iBAAiB,CAAC,OAAiC;IACjE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AACtF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAiC;IACpE,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAA;IACtB,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;AAC/E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAiC;IACxE,OAAO,oBAAoB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;AAC/C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAMzC;IACC,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,aAAa;QAAE,OAAO,IAAI,CAAA;IACzD,OAAO,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;AAC5C,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,qBAAqB,CACnC,OAAiC;IAEjC,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAC3F,CAAC","sourcesContent":["import type { Project } from '@elytracms/core/studio-core'\nimport type { ResolvedProject } from '@elytracms/core/studio-core'\nimport type { WorkspaceState } from './workspace-source'\n\n/**\n * Config-driven switcher source (EC-154, reframed for EC-179 per-backend\n * projects). Pure logic deciding what the project switcher and launcher list:\n *\n * - **No workspace config** → exactly today's behavior: the adapter-listed\n * projects, untouched (single-project golden path).\n * - **Config present** → every configured project, in config order, as a\n * navigable entry; one that has no persisted counterpart yet is an explicit\n * `unprovisioned` state (\"run `elytra init`\") rather than an error. Persisted\n * projects *not* in the config are not listed: the config is the source of\n * truth for what this workspace is.\n *\n * ## The `unprovisioned` branch is currently unreachable FROM THE STUDIO (EC-470)\n *\n * This function will happily produce `unprovisioned` — `switcher-source.test.ts`\n * covers it directly. But the live studio can no longer feed it inputs that do:\n * `studio-context.tsx`'s `configProjects()` derives `persisted` from the SAME\n * `workspace.projects` array this function iterates (via `configProject`, which\n * sets `slug` from the config entry), so the `bySlug` lookup below always hits.\n * Under AD-8 project identity became config-owned, and \"provisioned\" stopped\n * being a per-project row the studio could miss — it is now a property of the\n * DEPLOYMENT (see `DeploymentSetupGate` / `elytra doctor`).\n *\n * Left in place deliberately rather than removed: the entry kind is load-bearing\n * for the EC-401 single-project predicates below (`soleWorkspaceProject` treats a\n * lone unprovisioned entry as \"do not auto-forward\"), so retiring it is a\n * separate, owner-sized decision — not a docs sweep. Recorded so the next reader\n * does not mistake it for live UI.\n *\n * Visibility is **not** filtered by membership. One project per backend (vision\n * AD-8): a configured project lives on its own deployment, so the *active*\n * backend's memberships cannot speak to another project's. Access is enforced\n * where it belongs — per-backend sign-in plus the EC-150 role hook on entry —\n * not by hiding projects you'd authenticate into. (Hiding them was the\n * single-backend assumption; cross-backend it just made projects unreachable.)\n */\n\nexport type SwitcherEntry =\n | { kind: 'project'; project: Project }\n | { kind: 'unprovisioned'; config: ResolvedProject }\n\nexport interface SwitcherSourceInput {\n workspace: WorkspaceState\n /** The studio store's read model (config-derived under a workspace config). */\n persisted: readonly Project[]\n}\n\nexport function selectSwitcherProjects(input: SwitcherSourceInput): SwitcherEntry[] {\n if (input.workspace.kind !== 'config') {\n // No-config golden path (an invalid config never reaches the switcher —\n // the DeploymentSetupGate blocks boot): today's adapter-listed behavior.\n return input.persisted.map((project) => ({ kind: 'project', project }))\n }\n\n const bySlug = new Map(input.persisted.map((project) => [project.slug, project]))\n return input.workspace.projects.map((config) => {\n const persisted = bySlug.get(config.slug)\n return persisted\n ? { kind: 'project' as const, project: persisted }\n : { kind: 'unprovisioned' as const, config }\n })\n}\n\n/** The persisted projects among the entries (for list surfaces filtering by status). */\nexport function persistedProjects(entries: readonly SwitcherEntry[]): Project[] {\n return entries.flatMap((entry) => (entry.kind === 'project' ? [entry.project] : []))\n}\n\n/**\n * The workspace's sole navigable project, or `null` (EC-401). Non-null exactly\n * when the workspace has ONE entry total and it is a real `project` — a single\n * *unprovisioned* entry keeps this `null` (the launcher's setup gate must stay\n * visible), and a second entry of any kind does too.\n */\nexport function soleWorkspaceProject(entries: readonly SwitcherEntry[]): Project | null {\n const [only] = entries\n return entries.length === 1 && only?.kind === 'project' ? only.project : null\n}\n\n/**\n * EC-401 — the ONE single-project predicate, shared by the launcher (skip the\n * dashboard hop) and the shell header (plain project name instead of the\n * switcher dropdown) so the two surfaces can never disagree.\n */\nexport function isSingleProjectWorkspace(entries: readonly SwitcherEntry[]): boolean {\n return soleWorkspaceProject(entries) !== null\n}\n\n/**\n * The launcher's auto-forward decision (EC-401), pure: the sole project to\n * redirect to, or `null` to render the launcher. A forced `?state=` verification\n * state and an explicit \"all projects\" navigation (see `launcherVisitState` in\n * the router seam) both keep the launcher visible; otherwise a single-project\n * workspace forwards straight into its project.\n */\nexport function launcherAutoForwardTarget(input: {\n entries: readonly SwitcherEntry[]\n /** Whether the launcher was opened with a forced `?state=` verification state. */\n forcedState: boolean\n /** Whether this location is an explicit \"all projects\" navigation. */\n explicitVisit: boolean\n}): Project | null {\n if (input.forcedState || input.explicitVisit) return null\n return soleWorkspaceProject(input.entries)\n}\n\n/** The not-yet-provisioned config entries (rendered as explicit CLI-hint states). */\nexport function unprovisionedProjects(\n entries: readonly SwitcherEntry[],\n): ResolvedProject[] {\n return entries.flatMap((entry) => (entry.kind === 'unprovisioned' ? [entry.config] : []))\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { ResolvedWorkspaceProject, WorkspaceConfigIssue } from '@elytracms/core/studio-core';
1
+ import type { ResolvedProject, WorkspaceConfigIssue } from '@elytracms/core/studio-core';
2
2
  /**
3
3
  * Workspace-config resolution for the studio (EC-154, vision AD-8).
4
4
  *
@@ -26,7 +26,7 @@ export type WorkspaceState = {
26
26
  } | {
27
27
  kind: 'config';
28
28
  source: string;
29
- projects: ResolvedWorkspaceProject[];
29
+ projects: ResolvedProject[];
30
30
  } | {
31
31
  kind: 'invalid';
32
32
  source: string;
@@ -1,4 +1,5 @@
1
- import { extractWorkspaceConfigExport, resolveWorkspaceConfig, WORKSPACE_CONFIG_FILE, } from '@elytracms/core/studio-core';
1
+ import { WORKSPACE_CONFIG_FILE } from '@elytracms/core/config';
2
+ import { extractWorkspaceConfigExport, resolveWorkspaceConfig } from '@elytracms/core/studio-core';
2
3
  /**
3
4
  * `result.issues` is carried on the `invalid` path and **deliberately dropped on the
4
5
  * ok path** (EC-506, decided 2026-08-01) — not overlooked.
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-source.js","sourceRoot":"","sources":["../../../src/lib/workspace/workspace-source.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,6BAA6B,CAAA;AAsCpC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAS,SAAS,CAAC,KAAc,EAAE,MAAc;IAC/C,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;IAC9D,OAAO,MAAM,CAAC,EAAE;QACd,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE;QACvD,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAA;AACxD,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,qBAAqB,CAAC,OAAyB;IAC7D,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,IAAI,qBAAqB,CAAA;QACtD,MAAM,SAAS,GAAG,4BAA4B,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;QACxE,IAAI,OAAO,IAAI,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAA;QAC7F,OAAO,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACzC,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACzB,CAAC","sourcesContent":["import {\n extractWorkspaceConfigExport,\n resolveWorkspaceConfig,\n WORKSPACE_CONFIG_FILE,\n} from '@elytracms/core/studio-core'\nimport type { ResolvedWorkspaceProject, WorkspaceConfigIssue } from '@elytracms/core/studio-core'\n\n/**\n * Workspace-config resolution for the studio (EC-154, vision AD-8).\n *\n * The studio receives its code-first workspace config as the default export of\n * the consumer's `elytra.config.ts`, handed in at the mount point\n * (`createStudio(config)` in `./index.ts`) and resolved here as pure, testable\n * logic. A bundled `elytra.config.ts` at the builder app root — picked up by\n * Vite (`import.meta.glob`) — is the same channel for studio development and for\n * a self-hosted builder whose repo *is* the workspace.\n *\n * No channel → `none`: the single-project golden path, identical to today's\n * adapter-listed behavior. An invalid config is a deterministic boot-error state\n * (see `DeploymentSetupGate`) — never a silent fallback to unconfigured behavior.\n *\n * NOTE (EC-376): an earlier env-injected-JSON channel (`VITE_ELYTRA_WORKSPACE`)\n * for a hosted builder serving a *separate* host repo was plumbed here but never\n * wired end-to-end — no caller ever set it (EC-357 inventory). It was removed as\n * dead code. If hosted workspace-selection ever returns, wire it deliberately\n * through the EC-357 mount-env seam (`createStudio(config, studioConfig, { env })`\n * → `provideStudioEnv`/`studioEnv()` in `lib/persistence/backend-mode.ts`) rather\n * than resurrecting latent plumbing.\n */\n\nexport type WorkspaceState =\n | { kind: 'none' }\n | { kind: 'config'; source: string; projects: ResolvedWorkspaceProject[] }\n | { kind: 'invalid'; source: string; issues: WorkspaceConfigIssue[] }\n\nexport interface WorkspaceSources {\n /** Module namespace of the bundled `elytra.config.ts`, when one was bundled. */\n fileModule?: unknown\n /** Source label for file-channel issues (defaults to the conventional name). */\n fileName?: string\n}\n\n/**\n * `result.issues` is carried on the `invalid` path and **deliberately dropped on the\n * ok path** (EC-506, decided 2026-08-01) — not overlooked.\n *\n * `ok` means \"no error-severity issue\", so what would be dropped is warnings — and\n * the resolver has **no warning-severity producer** (measured 2026-08-01: every\n * `severity:` literal in `studio-core/workspace-config.ts` is `'error'`, and its one\n * pass-through of a foreign severity cannot carry `validateProjectSettings`'s only\n * warning; both gates are pinned by `workspace-config.test.ts`). Adding an `issues`\n * field here today would be a channel with a writer, no producer and no reader —\n * exactly the latent plumbing EC-376 deleted from this file (see the NOTE above).\n *\n * The consequence is a KNOWN, deliberate divergence from `elytra doctor`, which does\n * carry the count (`cli/src/doctor.js` `configFactsFromOutcome` sets\n * `issueCount: issues.length` unconditionally, and `deployment-ready.ts`'s\n * `config-valid` check has a `warn` branch for a valid-config-with-issues that the\n * studio can therefore never reach). It is **latent, not live**: with zero producers\n * the count on a valid config is always 0, and `deployment-ready.ts` tests it for\n * truthiness — so both tools render the same `ok` today. (Read at both sites and\n * inferred from them; not exercised end-to-end through the studio gate.)\n *\n * **Resurface trigger.** The first warning-severity `WorkspaceConfigIssue` makes the\n * divergence live and this decision expires. At that point EC-506's audience question\n * (\"does an EDITOR need to know?\") returns as a real design call, and the shape is in\n * the EC-506 task file: `issues` here, a mapper in `config-issues.ts`, a rail-foot\n * reader, and a localized catalog key. Do not add the field ahead of the producer.\n */\nfunction fromValue(value: unknown, source: string): WorkspaceState {\n const result = resolveWorkspaceConfig(value, { file: source })\n return result.ok\n ? { kind: 'config', source, projects: result.projects }\n : { kind: 'invalid', source, issues: result.issues }\n}\n\n/** Resolve the workspace state from the bundled config module. Pure and deterministic. */\nexport function resolveWorkspaceState(sources: WorkspaceSources): WorkspaceState {\n if (sources.fileModule !== undefined) {\n const file = sources.fileName ?? WORKSPACE_CONFIG_FILE\n const extracted = extractWorkspaceConfigExport(sources.fileModule, file)\n if ('issue' in extracted) return { kind: 'invalid', source: file, issues: [extracted.issue] }\n return fromValue(extracted.value, file)\n }\n\n return { kind: 'none' }\n}\n"]}
1
+ {"version":3,"file":"workspace-source.js","sourceRoot":"","sources":["../../../src/lib/workspace/workspace-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAsClG;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAS,SAAS,CAAC,KAAc,EAAE,MAAc;IAC/C,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;IAC9D,OAAO,MAAM,CAAC,EAAE;QACd,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE;QACvD,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAA;AACxD,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,qBAAqB,CAAC,OAAyB;IAC7D,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,IAAI,qBAAqB,CAAA;QACtD,MAAM,SAAS,GAAG,4BAA4B,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;QACxE,IAAI,OAAO,IAAI,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAA;QAC7F,OAAO,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACzC,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACzB,CAAC","sourcesContent":["import { WORKSPACE_CONFIG_FILE } from '@elytracms/core/config'\nimport { extractWorkspaceConfigExport, resolveWorkspaceConfig } from '@elytracms/core/studio-core'\nimport type { ResolvedProject, WorkspaceConfigIssue } from '@elytracms/core/studio-core'\n\n/**\n * Workspace-config resolution for the studio (EC-154, vision AD-8).\n *\n * The studio receives its code-first workspace config as the default export of\n * the consumer's `elytra.config.ts`, handed in at the mount point\n * (`createStudio(config)` in `./index.ts`) and resolved here as pure, testable\n * logic. A bundled `elytra.config.ts` at the builder app root — picked up by\n * Vite (`import.meta.glob`) — is the same channel for studio development and for\n * a self-hosted builder whose repo *is* the workspace.\n *\n * No channel → `none`: the single-project golden path, identical to today's\n * adapter-listed behavior. An invalid config is a deterministic boot-error state\n * (see `DeploymentSetupGate`) — never a silent fallback to unconfigured behavior.\n *\n * NOTE (EC-376): an earlier env-injected-JSON channel (`VITE_ELYTRA_WORKSPACE`)\n * for a hosted builder serving a *separate* host repo was plumbed here but never\n * wired end-to-end — no caller ever set it (EC-357 inventory). It was removed as\n * dead code. If hosted workspace-selection ever returns, wire it deliberately\n * through the EC-357 mount-env seam (`createStudio(config, studioConfig, { env })`\n * → `provideStudioEnv`/`studioEnv()` in `lib/persistence/backend-mode.ts`) rather\n * than resurrecting latent plumbing.\n */\n\nexport type WorkspaceState =\n | { kind: 'none' }\n | { kind: 'config'; source: string; projects: ResolvedProject[] }\n | { kind: 'invalid'; source: string; issues: WorkspaceConfigIssue[] }\n\nexport interface WorkspaceSources {\n /** Module namespace of the bundled `elytra.config.ts`, when one was bundled. */\n fileModule?: unknown\n /** Source label for file-channel issues (defaults to the conventional name). */\n fileName?: string\n}\n\n/**\n * `result.issues` is carried on the `invalid` path and **deliberately dropped on the\n * ok path** (EC-506, decided 2026-08-01) — not overlooked.\n *\n * `ok` means \"no error-severity issue\", so what would be dropped is warnings — and\n * the resolver has **no warning-severity producer** (measured 2026-08-01: every\n * `severity:` literal in `studio-core/workspace-config.ts` is `'error'`, and its one\n * pass-through of a foreign severity cannot carry `validateProjectSettings`'s only\n * warning; both gates are pinned by `workspace-config.test.ts`). Adding an `issues`\n * field here today would be a channel with a writer, no producer and no reader —\n * exactly the latent plumbing EC-376 deleted from this file (see the NOTE above).\n *\n * The consequence is a KNOWN, deliberate divergence from `elytra doctor`, which does\n * carry the count (`cli/src/doctor.js` `configFactsFromOutcome` sets\n * `issueCount: issues.length` unconditionally, and `deployment-ready.ts`'s\n * `config-valid` check has a `warn` branch for a valid-config-with-issues that the\n * studio can therefore never reach). It is **latent, not live**: with zero producers\n * the count on a valid config is always 0, and `deployment-ready.ts` tests it for\n * truthiness — so both tools render the same `ok` today. (Read at both sites and\n * inferred from them; not exercised end-to-end through the studio gate.)\n *\n * **Resurface trigger.** The first warning-severity `WorkspaceConfigIssue` makes the\n * divergence live and this decision expires. At that point EC-506's audience question\n * (\"does an EDITOR need to know?\") returns as a real design call, and the shape is in\n * the EC-506 task file: `issues` here, a mapper in `config-issues.ts`, a rail-foot\n * reader, and a localized catalog key. Do not add the field ahead of the producer.\n */\nfunction fromValue(value: unknown, source: string): WorkspaceState {\n const result = resolveWorkspaceConfig(value, { file: source })\n return result.ok\n ? { kind: 'config', source, projects: result.projects }\n : { kind: 'invalid', source, issues: result.issues }\n}\n\n/** Resolve the workspace state from the bundled config module. Pure and deterministic. */\nexport function resolveWorkspaceState(sources: WorkspaceSources): WorkspaceState {\n if (sources.fileModule !== undefined) {\n const file = sources.fileName ?? WORKSPACE_CONFIG_FILE\n const extracted = extractWorkspaceConfigExport(sources.fileModule, file)\n if ('issue' in extracted) return { kind: 'invalid', source: file, issues: [extracted.issue] }\n return fromValue(extracted.value, file)\n }\n\n return { kind: 'none' }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elytracms/studio",
3
3
  "author": "finaldream <hi@finaldream.de>",
4
- "version": "0.0.91",
4
+ "version": "0.0.93",
5
5
  "engines": {
6
6
  "node": ">=22.18.0"
7
7
  },
@@ -149,7 +149,7 @@
149
149
  "lucide-react": "^1.23.0",
150
150
  "tailwind-merge": "^3.6.0",
151
151
  "zod": "^4.4.3",
152
- "@elytracms/core": "^0.0.91"
152
+ "@elytracms/core": "^0.0.93"
153
153
  },
154
154
  "peerDependencies": {
155
155
  "@tanstack/react-router": "^1.170.0",
@@ -173,7 +173,7 @@
173
173
  "react-dom": "^19.2.7",
174
174
  "typescript": "^6.0.3",
175
175
  "vite": "^8.1.3",
176
- "@elytracms/components": "^0.0.91"
176
+ "@elytracms/components": "^0.0.93"
177
177
  },
178
178
  "scripts": {
179
179
  "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && node ../../scripts/fix-esm-extensions.mjs dist && node ../../scripts/check-esm-extensions.mjs dist",
@@ -1,16 +0,0 @@
1
- import type { BlocksFieldDef, CompositionIssue, RichTextFieldDef } from '@elytracms/core/cms-core';
2
- /**
3
- * EC-188: validate a `blocks` composition field value against the field's `allow`
4
- * vocabulary + the code-owned component registry (the leash), mapping graph
5
- * issues into plain CMS findings. Never throws — a malformed value degrades to
6
- * a single finding, matching the "validation is first-class, never a crash"
7
- * rule.
8
- */
9
- export declare function validateComposition(value: unknown, field: BlocksFieldDef): CompositionIssue[];
10
- /**
11
- * EC-189: validate the composition nodes inside a `richText` field value with
12
- * the SAME leash a `blocks` field uses — since EC-547 literally the same walk
13
- * over the same shape: the value's `elytra/component` nodes ARE sequence
14
- * citizens, at the top level and nested in typography content.
15
- */
16
- export declare function validateRichTextEmbeds(value: unknown, field: RichTextFieldDef): CompositionIssue[];
@@ -1,133 +0,0 @@
1
- import { legacyNodeMemberIssues, validateCompositionValue } from '@elytracms/core/project-graph';
2
- import { BLOCK_SEQUENCE_VERSION, classifyBlockNode, isBlockSequenceValueShape, parseBlockSequenceValue, } from '@elytracms/core/rich-text';
3
- import { getStudioRegistry } from '@elytracms/studio/lib/registry';
4
- /**
5
- * Composition validator adapters (EC-326, extracted from `cms-store.tsx`) —
6
- * injected into `validateDocument`'s `validateComposition`/`validateRichTextEmbeds`
7
- * options so `@elytracms/core/cms-core` stays free of `@elytracms/core/project-graph`
8
- * and `@elytracms/core/rich-text`.
9
- *
10
- * EC-547 (EC-500 D1/D4): both field types now share ONE stored shape — the v2
11
- * block sequence `{ version: 2, content: BlockNode[] }` — and therefore ONE
12
- * validation walk: unwrap the envelope, validate the sequence's component nodes
13
- * (top-level roots AND nodes nested inside typography content) against the
14
- * field's `allow` vocabulary + the code-owned component registry. "`allow`
15
- * means component ids insertable at this position" is now literally the same
16
- * check on the same shape for `blocks` and `richText`.
17
- *
18
- * Pre-convergence shapes (the v1 `{ version, doc }` envelope, bare node lists)
19
- * never reach these adapters: `validateDocument`'s shape pass reports them as
20
- * the explicit pre-convergence issue and skips the deeper validators (EC-548
21
- * removed the legacy walks). A retired `componentEmbed` node riding in v2
22
- * content is a foreign typography node — not rendered, not validated, surfaced
23
- * by the renderer's unknown-node fallback (render/validate parity, EC-497).
24
- */
25
- /**
26
- * Validate the component nodes of a v2 block sequence against the field leash.
27
- *
28
- * - Top-level component roots validate in ONE `validateCompositionValue` call,
29
- * so cross-root checks (duplicate node ids) see the whole sequence. Their
30
- * `root/<k>` issue paths are re-keyed to the node's actual sequence index
31
- * (`content/<i>/…`).
32
- * - A component nested in typography content validates per-node with the SAME
33
- * leash, its findings prefixed with the typography path
34
- * (`content/<i>/content/<j>/…`).
35
- * - A malformed value degrades to a single finding, never a crash.
36
- */
37
- function validateBlockSequence(value, allow) {
38
- // Structural gate FIRST: only the stored `{ version: 2, content }` object is a
39
- // sequence here. The parser alone would also accept a JSON-string encoding of
40
- // one — which no delivery reader accepts — and validating that clean would be
41
- // the invisible divergence EC-390 exists to prevent.
42
- if (!isBlockSequenceValueShape(value)) {
43
- return [
44
- {
45
- message: `is not a valid block sequence (version ${BLOCK_SEQUENCE_VERSION})`,
46
- severity: 'error',
47
- },
48
- ];
49
- }
50
- // EC-574: retired node members (a render `condition`, a `kind: 'binding'` prop)
51
- // are judged on the value AS STORED, before the parse below — the parser strips
52
- // a stray `condition` silently and rejects a binding prop only generically, so
53
- // this is the one place the stored shape is still visible to name precisely.
54
- const out = legacyNodeMemberIssues(value).map((issue) => ({
55
- message: issue.message,
56
- severity: issue.severity,
57
- nodePath: issue.path.join('/'),
58
- }));
59
- const parsed = parseBlockSequenceValue(value);
60
- if (!parsed.ok) {
61
- return [
62
- ...out,
63
- {
64
- message: `is not a valid block sequence (version ${BLOCK_SEQUENCE_VERSION})`,
65
- severity: 'error',
66
- },
67
- ];
68
- }
69
- const components = getStudioRegistry().registry.lookup;
70
- const opts = { ...(allow ? { allow } : {}), components };
71
- /** `root/0/<rest>` from a single-node validation call → `<base>/<rest>`. */
72
- const nestedPath = (base, path) => {
73
- const rest = path.slice(2);
74
- return rest.length > 0 ? `${base}/${rest.join('/')}` : base;
75
- };
76
- const walkTypography = (nodes, base) => {
77
- nodes?.forEach((child, i) => {
78
- const here = `${base}/content/${i}`;
79
- const kind = classifyBlockNode(child);
80
- if (kind.kind === 'component') {
81
- for (const issue of validateCompositionValue(kind.node, opts)) {
82
- out.push({ message: issue.message, severity: issue.severity, nodePath: nestedPath(here, issue.path) });
83
- }
84
- }
85
- else {
86
- walkTypography(kind.node.content, here);
87
- }
88
- });
89
- };
90
- const roots = [];
91
- parsed.value.content.forEach((node, index) => {
92
- const kind = classifyBlockNode(node);
93
- if (kind.kind === 'component')
94
- roots.push({ node: kind.node, index });
95
- else
96
- walkTypography(kind.node.content, `content/${index}`);
97
- });
98
- for (const issue of validateCompositionValue(roots.map((r) => r.node), opts)) {
99
- // path is `['root', <k>, ...rest]` — re-key <k> to the sequence index.
100
- const [, k, ...rest] = issue.path;
101
- const index = typeof k === 'number' ? (roots[k]?.index ?? k) : k;
102
- out.push({
103
- message: issue.message,
104
- severity: issue.severity,
105
- nodePath: ['content', ...(index === undefined ? [] : [index]), ...rest].join('/'),
106
- });
107
- }
108
- return out;
109
- }
110
- /**
111
- * EC-188: validate a `blocks` composition field value against the field's `allow`
112
- * vocabulary + the code-owned component registry (the leash), mapping graph
113
- * issues into plain CMS findings. Never throws — a malformed value degrades to
114
- * a single finding, matching the "validation is first-class, never a crash"
115
- * rule.
116
- */
117
- export function validateComposition(value, field) {
118
- if (value == null)
119
- return [];
120
- return validateBlockSequence(value, field.allow);
121
- }
122
- /**
123
- * EC-189: validate the composition nodes inside a `richText` field value with
124
- * the SAME leash a `blocks` field uses — since EC-547 literally the same walk
125
- * over the same shape: the value's `elytra/component` nodes ARE sequence
126
- * citizens, at the top level and nested in typography content.
127
- */
128
- export function validateRichTextEmbeds(value, field) {
129
- if (value == null)
130
- return [];
131
- return validateBlockSequence(value, field.allow);
132
- }
133
- //# sourceMappingURL=cms-composition-validators.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cms-composition-validators.js","sourceRoot":"","sources":["../../../src/components/cms/cms-composition-validators.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BlocksFieldDef, CompositionIssue, RichTextFieldDef } from '@elytracms/core/cms-core'\nimport { legacyNodeMemberIssues, validateCompositionValue } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport {\n BLOCK_SEQUENCE_VERSION,\n classifyBlockNode,\n isBlockSequenceValueShape,\n parseBlockSequenceValue,\n} from '@elytracms/core/rich-text'\nimport type { BlockNode } from '@elytracms/core/rich-text'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\n\n/**\n * Composition validator adapters (EC-326, extracted from `cms-store.tsx`) —\n * injected into `validateDocument`'s `validateComposition`/`validateRichTextEmbeds`\n * options so `@elytracms/core/cms-core` stays free of `@elytracms/core/project-graph`\n * and `@elytracms/core/rich-text`.\n *\n * EC-547 (EC-500 D1/D4): both field types now share ONE stored shape — the v2\n * block sequence `{ version: 2, content: BlockNode[] }` — and therefore ONE\n * validation walk: unwrap the envelope, validate the sequence's component nodes\n * (top-level roots AND nodes nested inside typography content) against the\n * field's `allow` vocabulary + the code-owned component registry. \"`allow`\n * means component ids insertable at this position\" is now literally the same\n * check on the same shape for `blocks` and `richText`.\n *\n * Pre-convergence shapes (the v1 `{ version, doc }` envelope, bare node lists)\n * never reach these adapters: `validateDocument`'s shape pass reports them as\n * the explicit pre-convergence issue and skips the deeper validators (EC-548\n * removed the legacy walks). A retired `componentEmbed` node riding in v2\n * content is a foreign typography node — not rendered, not validated, surfaced\n * by the renderer's unknown-node fallback (render/validate parity, EC-497).\n */\n\n/**\n * Validate the component nodes of a v2 block sequence against the field leash.\n *\n * - Top-level component roots validate in ONE `validateCompositionValue` call,\n * so cross-root checks (duplicate node ids) see the whole sequence. Their\n * `root/<k>` issue paths are re-keyed to the node's actual sequence index\n * (`content/<i>/…`).\n * - A component nested in typography content validates per-node with the SAME\n * leash, its findings prefixed with the typography path\n * (`content/<i>/content/<j>/…`).\n * - A malformed value degrades to a single finding, never a crash.\n */\nfunction validateBlockSequence(\n value: unknown,\n allow: readonly string[] | undefined,\n): CompositionIssue[] {\n // Structural gate FIRST: only the stored `{ version: 2, content }` object is a\n // sequence here. The parser alone would also accept a JSON-string encoding of\n // one — which no delivery reader accepts — and validating that clean would be\n // the invisible divergence EC-390 exists to prevent.\n if (!isBlockSequenceValueShape(value)) {\n return [\n {\n message: `is not a valid block sequence (version ${BLOCK_SEQUENCE_VERSION})`,\n severity: 'error',\n },\n ]\n }\n // EC-574: retired node members (a render `condition`, a `kind: 'binding'` prop)\n // are judged on the value AS STORED, before the parse below — the parser strips\n // a stray `condition` silently and rejects a binding prop only generically, so\n // this is the one place the stored shape is still visible to name precisely.\n const out: CompositionIssue[] = legacyNodeMemberIssues(value).map((issue) => ({\n message: issue.message,\n severity: issue.severity,\n nodePath: issue.path.join('/'),\n }))\n const parsed = parseBlockSequenceValue(value)\n if (!parsed.ok) {\n return [\n ...out,\n {\n message: `is not a valid block sequence (version ${BLOCK_SEQUENCE_VERSION})`,\n severity: 'error',\n },\n ]\n }\n const components = getStudioRegistry().registry.lookup\n const opts = { ...(allow ? { allow } : {}), components }\n\n /** `root/0/<rest>` from a single-node validation call → `<base>/<rest>`. */\n const nestedPath = (base: string, path: ReadonlyArray<string | number>): string => {\n const rest = path.slice(2)\n return rest.length > 0 ? `${base}/${rest.join('/')}` : base\n }\n\n const walkTypography = (nodes: readonly BlockNode[] | undefined, base: string): void => {\n nodes?.forEach((child, i) => {\n const here = `${base}/content/${i}`\n const kind = classifyBlockNode(child)\n if (kind.kind === 'component') {\n for (const issue of validateCompositionValue(kind.node, opts)) {\n out.push({ message: issue.message, severity: issue.severity, nodePath: nestedPath(here, issue.path) })\n }\n } else {\n walkTypography(kind.node.content, here)\n }\n })\n }\n\n const roots: { node: ComponentNode; index: number }[] = []\n parsed.value.content.forEach((node, index) => {\n const kind = classifyBlockNode(node)\n if (kind.kind === 'component') roots.push({ node: kind.node, index })\n else walkTypography(kind.node.content, `content/${index}`)\n })\n\n for (const issue of validateCompositionValue(roots.map((r) => r.node), opts)) {\n // path is `['root', <k>, ...rest]` — re-key <k> to the sequence index.\n const [, k, ...rest] = issue.path\n const index = typeof k === 'number' ? (roots[k]?.index ?? k) : k\n out.push({\n message: issue.message,\n severity: issue.severity,\n nodePath: ['content', ...(index === undefined ? [] : [index]), ...rest].join('/'),\n })\n }\n return out\n}\n\n/**\n * EC-188: validate a `blocks` composition field value against the field's `allow`\n * vocabulary + the code-owned component registry (the leash), mapping graph\n * issues into plain CMS findings. Never throws — a malformed value degrades to\n * a single finding, matching the \"validation is first-class, never a crash\"\n * rule.\n */\nexport function validateComposition(value: unknown, field: BlocksFieldDef): CompositionIssue[] {\n if (value == null) return []\n return validateBlockSequence(value, field.allow)\n}\n\n/**\n * EC-189: validate the composition nodes inside a `richText` field value with\n * the SAME leash a `blocks` field uses — since EC-547 literally the same walk\n * over the same shape: the value's `elytra/component` nodes ARE sequence\n * citizens, at the top level and nested in typography content.\n */\nexport function validateRichTextEmbeds(value: unknown, field: RichTextFieldDef): CompositionIssue[] {\n if (value == null) return []\n return validateBlockSequence(value, field.allow)\n}\n"]}
@@ -1,28 +0,0 @@
1
- import type { CmsDocument, CollectionDef, LocaleConfig } from '@elytracms/core/cms-core';
2
- import type { ManifestResolver } from '@elytracms/core/component-registry';
3
- /**
4
- * EC-425 — THE publish-time hidden-strip, extracted so single-document publish
5
- * (`doPublish`, use-publish-controller) and bulk publish (`runBulk`, cms-store)
6
- * share ONE clean step instead of the bulk bar bypassing it:
7
- *
8
- * - EC-273 `clearHiddenValues`: drop conditionally-hidden document field values
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`).
12
- * - EC-394 `clearHiddenProps`: the same strip for component props across every
13
- * `blocks`/canvas composition, so a hidden relation/asset prop's ref leaves
14
- * the EC-277 reference index (derived from the stored draft at the EC-286
15
- * write chokepoint) and stops blocking its target's delete/unpublish.
16
- *
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
25
- * fixture manifests. Identity-preserving: returns the input document when
26
- * nothing clears, so callers can save-if-changed.
27
- */
28
- export declare function cleanHiddenForPublish(collection: CollectionDef, doc: CmsDocument, resolveManifest?: ManifestResolver, localeConfig?: LocaleConfig | undefined): CmsDocument;
@@ -1,38 +0,0 @@
1
- import { clearHiddenValues } from '@elytracms/core/cms-core';
2
- import { clearHiddenProps } from '@elytracms/core/component-registry';
3
- import { getStudioRegistry } from '@elytracms/studio/lib/registry';
4
- import { getStudioLocaleConfig } from '@elytracms/studio/lib/locale';
5
- /**
6
- * EC-425 — THE publish-time hidden-strip, extracted so single-document publish
7
- * (`doPublish`, use-publish-controller) and bulk publish (`runBulk`, cms-store)
8
- * share ONE clean step instead of the bulk bar bypassing it:
9
- *
10
- * - EC-273 `clearHiddenValues`: drop conditionally-hidden document field values
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`).
14
- * - EC-394 `clearHiddenProps`: the same strip for component props across every
15
- * `blocks`/canvas composition, so a hidden relation/asset prop's ref leaves
16
- * the EC-277 reference index (derived from the stored draft at the EC-286
17
- * write chokepoint) and stops blocking its target's delete/unpublish.
18
- *
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
27
- * fixture manifests. Identity-preserving: returns the input document when
28
- * nothing clears, so callers can save-if-changed.
29
- */
30
- export function cleanHiddenForPublish(collection, doc, resolveManifest = sessionManifestResolver(), localeConfig = getStudioLocaleConfig() ?? undefined) {
31
- return clearHiddenProps(collection, clearHiddenValues(collection, doc, localeConfig), resolveManifest);
32
- }
33
- /** The session component registry as a {@link ManifestResolver}. */
34
- function sessionManifestResolver() {
35
- const { registry } = getStudioRegistry();
36
- return (id) => registry.getManifest(id);
37
- }
38
- //# sourceMappingURL=publish-clean.js.map
@@ -1 +0,0 @@
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"]}
@@ -1,35 +0,0 @@
1
- import type { CmsDocument } from '@elytracms/core/cms-core';
2
- import type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source';
3
- /**
4
- * EC-225 / EC-240 — the two persistence disciplines the detail editor supports,
5
- * as one pure decision so the autosave scheduler and the unmount flush can never
6
- * diverge on WHEN a dirty draft is persisted silently:
7
- *
8
- * - A **publishing** rail AUTOSAVES: the draft persists 1s after the last edit and
9
- * publish is the deliberate commit — so a dirty draft is flushed both on the
10
- * debounce timer and on unmount (navigating away inside the debounce window).
11
- * - A **non-publishing** rail (users, EC-240) SAVES EXPLICITLY: edits stay in the
12
- * draft until the Save button, and an `UnsavedChangesGuard` protects navigation —
13
- * so it NEVER persists silently (returns false here).
14
- */
15
- export declare function shouldPersistDraft(input: {
16
- explicitSave: boolean;
17
- dirty: boolean;
18
- }): boolean;
19
- /**
20
- * EC-225 autosave · EC-240 explicit save. Owns the debounced autosave timer and
21
- * the unmount flush for a publishing rail; a non-publishing rail persists only via
22
- * its Save button (the caller's `UnsavedChangesGuard` covers navigation). Refs let
23
- * the unmount flush read the latest values without re-subscribing.
24
- *
25
- * Returns `explicitSave` — the caller renders the Save button + guard (explicit)
26
- * or the autosave status text (autosave) from it.
27
- */
28
- export declare function useSaveMode(input: {
29
- canPublish: boolean;
30
- dirty: boolean;
31
- draft: CmsDocument;
32
- source: CollectionViewSource;
33
- }): {
34
- explicitSave: boolean;
35
- };
@@ -1,54 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
- /**
3
- * EC-225 / EC-240 — the two persistence disciplines the detail editor supports,
4
- * as one pure decision so the autosave scheduler and the unmount flush can never
5
- * diverge on WHEN a dirty draft is persisted silently:
6
- *
7
- * - A **publishing** rail AUTOSAVES: the draft persists 1s after the last edit and
8
- * publish is the deliberate commit — so a dirty draft is flushed both on the
9
- * debounce timer and on unmount (navigating away inside the debounce window).
10
- * - A **non-publishing** rail (users, EC-240) SAVES EXPLICITLY: edits stay in the
11
- * draft until the Save button, and an `UnsavedChangesGuard` protects navigation —
12
- * so it NEVER persists silently (returns false here).
13
- */
14
- export function shouldPersistDraft(input) {
15
- return !input.explicitSave && input.dirty;
16
- }
17
- /**
18
- * EC-225 autosave · EC-240 explicit save. Owns the debounced autosave timer and
19
- * the unmount flush for a publishing rail; a non-publishing rail persists only via
20
- * its Save button (the caller's `UnsavedChangesGuard` covers navigation). Refs let
21
- * the unmount flush read the latest values without re-subscribing.
22
- *
23
- * Returns `explicitSave` — the caller renders the Save button + guard (explicit)
24
- * or the autosave status text (autosave) from it.
25
- */
26
- export function useSaveMode(input) {
27
- const { canPublish, dirty, draft, source } = input;
28
- const explicitSave = !canPublish;
29
- const draftRef = useRef(draft);
30
- draftRef.current = draft;
31
- const dirtyRef = useRef(dirty);
32
- dirtyRef.current = dirty;
33
- const sourceRef = useRef(source);
34
- sourceRef.current = source;
35
- const explicitSaveRef = useRef(explicitSave);
36
- explicitSaveRef.current = explicitSave;
37
- useEffect(() => {
38
- if (explicitSave)
39
- return; // explicit rails persist only via the Save button
40
- if (!dirty)
41
- return;
42
- const timer = window.setTimeout(() => sourceRef.current.save(draftRef.current), 1000);
43
- return () => window.clearTimeout(timer);
44
- }, [dirty, draft, explicitSave]);
45
- // Flush a still-pending AUTOSAVE edit on unmount (navigating away inside the
46
- // debounce window) so nothing is lost. Explicit rails never flush silently —
47
- // their `UnsavedChangesGuard` catches the navigation instead. Unmount-only.
48
- useEffect(() => () => {
49
- if (shouldPersistDraft({ explicitSave: explicitSaveRef.current, dirty: dirtyRef.current }))
50
- sourceRef.current.save(draftRef.current);
51
- }, []);
52
- return { explicitSave };
53
- }
54
- //# sourceMappingURL=use-save-mode.js.map