@elytracms/studio 0.0.93 → 0.0.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. package/convex/analytics-cost.test.ts +817 -0
  2. package/convex/auth.ts +5 -2
  3. package/convex/authority.ts +35 -11
  4. package/convex/contract-parity.test.ts +7 -0
  5. package/convex/delivery.ts +12 -9
  6. package/convex/guard.ts +9 -7
  7. package/convex/http-routes.test.ts +18 -8
  8. package/convex/http.ts +23 -17
  9. package/convex/ingest.test.ts +326 -0
  10. package/convex/ingest.ts +281 -0
  11. package/convex/invites.ts +13 -5
  12. package/convex/members.ts +10 -4
  13. package/convex/record-functions.ts +325 -0
  14. package/convex/records.test.ts +156 -0
  15. package/convex/redirect-migration.test.ts +205 -0
  16. package/convex/redirect-revalidate.test.ts +5 -1
  17. package/convex/redirects.ts +120 -79
  18. package/convex/role-policy.test.ts +315 -0
  19. package/convex/roles.ts +83 -0
  20. package/convex/schema.ts +30 -0
  21. package/convex/triggers.ts +21 -1
  22. package/dist/addon.d.ts +71 -0
  23. package/dist/addon.js +67 -0
  24. package/dist/addon.js.map +1 -0
  25. package/dist/components/account/account-settings.js +2 -2
  26. package/dist/components/account/account-settings.js.map +1 -1
  27. package/dist/components/account/credential-panel.js +2 -2
  28. package/dist/components/account/credential-panel.js.map +1 -1
  29. package/dist/components/account/self-account-schema.d.ts +6 -15
  30. package/dist/components/account/self-account-schema.js +52 -34
  31. package/dist/components/account/self-account-schema.js.map +1 -1
  32. package/dist/components/account/self-account-view-source.d.ts +1 -1
  33. package/dist/components/account/self-account-view-source.js +6 -4
  34. package/dist/components/account/self-account-view-source.js.map +1 -1
  35. package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
  36. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  37. package/dist/components/ai/ai-chat-panel.js +6 -6
  38. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  39. package/dist/components/ai/ai-cms-panel.js +2 -2
  40. package/dist/components/ai/ai-cms-panel.js.map +1 -1
  41. package/dist/components/ai/ai-panel-toggle.js +4 -4
  42. package/dist/components/ai/ai-panel-toggle.js.map +1 -1
  43. package/dist/components/ai/chat-ui.js +2 -2
  44. package/dist/components/ai/chat-ui.js.map +1 -1
  45. package/dist/components/ai/proposal-card.d.ts +2 -2
  46. package/dist/components/ai/proposal-card.js +2 -2
  47. package/dist/components/ai/proposal-card.js.map +1 -1
  48. package/dist/components/ai/proposal-issues.js +2 -2
  49. package/dist/components/ai/proposal-issues.js.map +1 -1
  50. package/dist/components/ai/translate-document-action.js +2 -2
  51. package/dist/components/ai/translate-document-action.js.map +1 -1
  52. package/dist/components/ai/translate-document-dialog.js +6 -6
  53. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  54. package/dist/components/ai/translate-skipped.d.ts +1 -1
  55. package/dist/components/ai/translate-skipped.js +2 -2
  56. package/dist/components/ai/translate-skipped.js.map +1 -1
  57. package/dist/components/app-views/default-modules.d.ts +50 -2
  58. package/dist/components/app-views/default-modules.js +65 -20
  59. package/dist/components/app-views/default-modules.js.map +1 -1
  60. package/dist/components/app-views/module.d.ts +53 -3
  61. package/dist/components/app-views/module.js +14 -7
  62. package/dist/components/app-views/module.js.map +1 -1
  63. package/dist/components/app-views/sidebar.js +1 -1
  64. package/dist/components/app-views/sidebar.js.map +1 -1
  65. package/dist/components/app-views/studio-config.d.ts +11 -2
  66. package/dist/components/app-views/studio-config.js.map +1 -1
  67. package/dist/components/assets/asset-detail.js +2 -2
  68. package/dist/components/assets/asset-detail.js.map +1 -1
  69. package/dist/components/assets/asset-grid.js +2 -2
  70. package/dist/components/assets/asset-grid.js.map +1 -1
  71. package/dist/components/assets/asset-thumb.js +3 -3
  72. package/dist/components/assets/asset-thumb.js.map +1 -1
  73. package/dist/components/assets/assets-collection-view-source.d.ts +2 -2
  74. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  75. package/dist/components/assets/assets-module.js +1 -1
  76. package/dist/components/assets/assets-module.js.map +1 -1
  77. package/dist/components/assets/assets-store.d.ts +2 -2
  78. package/dist/components/assets/assets-store.js +6 -6
  79. package/dist/components/assets/assets-store.js.map +1 -1
  80. package/dist/components/assets/assets-view-preference.js +1 -1
  81. package/dist/components/assets/assets-view-preference.js.map +1 -1
  82. package/dist/components/assets/replace-file.js +1 -1
  83. package/dist/components/assets/replace-file.js.map +1 -1
  84. package/dist/components/auth-context.d.ts +1 -1
  85. package/dist/components/auth-context.js +6 -6
  86. package/dist/components/auth-context.js.map +1 -1
  87. package/dist/components/builder/blocks-model.d.ts +1 -1
  88. package/dist/components/builder/blocks-model.js +3 -3
  89. package/dist/components/builder/blocks-model.js.map +1 -1
  90. package/dist/components/builder/blocks-panel.d.ts +1 -1
  91. package/dist/components/builder/blocks-panel.js +4 -4
  92. package/dist/components/builder/blocks-panel.js.map +1 -1
  93. package/dist/components/builder/builder-store.js +2 -2
  94. package/dist/components/builder/builder-store.js.map +1 -1
  95. package/dist/components/builder/canvas-breakpoints.js +1 -1
  96. package/dist/components/builder/canvas-breakpoints.js.map +1 -1
  97. package/dist/components/builder/canvas-dnd.d.ts +2 -2
  98. package/dist/components/builder/canvas-dnd.js +1 -1
  99. package/dist/components/builder/canvas-dnd.js.map +1 -1
  100. package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
  101. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  102. package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
  103. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
  104. package/dist/components/builder/canvas-render-context.d.ts +1 -1
  105. package/dist/components/builder/canvas-render-context.js +4 -4
  106. package/dist/components/builder/canvas-render-context.js.map +1 -1
  107. package/dist/components/builder/canvas-theme-notice.js +2 -2
  108. package/dist/components/builder/canvas-theme-notice.js.map +1 -1
  109. package/dist/components/builder/canvas.js +3 -3
  110. package/dist/components/builder/canvas.js.map +1 -1
  111. package/dist/components/builder/clipboard-actions.d.ts +2 -2
  112. package/dist/components/builder/clipboard-actions.js +4 -4
  113. package/dist/components/builder/clipboard-actions.js.map +1 -1
  114. package/dist/components/builder/composition-field-editor-lazy.js +1 -1
  115. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
  116. package/dist/components/builder/composition-field-editor.d.ts +1 -1
  117. package/dist/components/builder/composition-field-editor.js +3 -3
  118. package/dist/components/builder/composition-field-editor.js.map +1 -1
  119. package/dist/components/builder/drag-session.d.ts +1 -1
  120. package/dist/components/builder/drag-session.js.map +1 -1
  121. package/dist/components/builder/fixture-thumbnail.js +1 -1
  122. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  123. package/dist/components/builder/insert-context.d.ts +1 -1
  124. package/dist/components/builder/insert-context.js +3 -3
  125. package/dist/components/builder/insert-context.js.map +1 -1
  126. package/dist/components/builder/layout-inspector.js +1 -1
  127. package/dist/components/builder/layout-inspector.js.map +1 -1
  128. package/dist/components/builder/node-title.js +1 -1
  129. package/dist/components/builder/node-title.js.map +1 -1
  130. package/dist/components/builder/page-tree.d.ts +1 -1
  131. package/dist/components/builder/page-tree.js +5 -5
  132. package/dist/components/builder/page-tree.js.map +1 -1
  133. package/dist/components/builder/placement-refusal.js +3 -3
  134. package/dist/components/builder/placement-refusal.js.map +1 -1
  135. package/dist/components/builder/property-inspector.js +4 -4
  136. package/dist/components/builder/property-inspector.js.map +1 -1
  137. package/dist/components/builder/reuse-context.js +4 -4
  138. package/dist/components/builder/reuse-context.js.map +1 -1
  139. package/dist/components/builder/reuse.js +1 -1
  140. package/dist/components/builder/reuse.js.map +1 -1
  141. package/dist/components/builder/rich-text-toolbar.js +1 -1
  142. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  143. package/dist/components/builder/run-inspector.js +1 -1
  144. package/dist/components/builder/run-inspector.js.map +1 -1
  145. package/dist/components/builder/selection-overlay.js +3 -3
  146. package/dist/components/builder/selection-overlay.js.map +1 -1
  147. package/dist/components/builder/spacing-handles.d.ts +2 -2
  148. package/dist/components/builder/spacing-handles.js +3 -3
  149. package/dist/components/builder/spacing-handles.js.map +1 -1
  150. package/dist/components/cms/asset-field.js +2 -2
  151. package/dist/components/cms/asset-field.js.map +1 -1
  152. package/dist/components/cms/asset-picker.js +2 -2
  153. package/dist/components/cms/asset-picker.js.map +1 -1
  154. package/dist/components/cms/bulk-actions.d.ts +1 -1
  155. package/dist/components/cms/bulk-actions.js +2 -2
  156. package/dist/components/cms/bulk-actions.js.map +1 -1
  157. package/dist/components/cms/bulk-bar.d.ts +2 -2
  158. package/dist/components/cms/bulk-bar.js +2 -2
  159. package/dist/components/cms/bulk-bar.js.map +1 -1
  160. package/dist/components/cms/cell-edit.d.ts +1 -1
  161. package/dist/components/cms/cell-edit.js +1 -1
  162. package/dist/components/cms/cell-edit.js.map +1 -1
  163. package/dist/components/cms/cell.d.ts +1 -1
  164. package/dist/components/cms/cell.js +3 -3
  165. package/dist/components/cms/cell.js.map +1 -1
  166. package/dist/components/cms/cms-index.js +2 -2
  167. package/dist/components/cms/cms-index.js.map +1 -1
  168. package/dist/components/cms/cms-labels.js +2 -2
  169. package/dist/components/cms/cms-labels.js.map +1 -1
  170. package/dist/components/cms/cms-store.d.ts +2 -2
  171. package/dist/components/cms/cms-store.js +10 -10
  172. package/dist/components/cms/cms-store.js.map +1 -1
  173. package/dist/components/cms/collection-table.js +7 -7
  174. package/dist/components/cms/collection-table.js.map +1 -1
  175. package/dist/components/cms/detail-editor.d.ts +5 -2
  176. package/dist/components/cms/detail-editor.js +20 -14
  177. package/dist/components/cms/detail-editor.js.map +1 -1
  178. package/dist/components/cms/details-pane.js +1 -1
  179. package/dist/components/cms/details-pane.js.map +1 -1
  180. package/dist/components/cms/doc-state.js +1 -1
  181. package/dist/components/cms/doc-state.js.map +1 -1
  182. package/dist/components/cms/document-diff-view.js +5 -5
  183. package/dist/components/cms/document-diff-view.js.map +1 -1
  184. package/dist/components/cms/document-editor-context.js +1 -1
  185. package/dist/components/cms/document-editor-context.js.map +1 -1
  186. package/dist/components/cms/document-staging.js +1 -1
  187. package/dist/components/cms/document-staging.js.map +1 -1
  188. package/dist/components/cms/document-url-path.js +2 -2
  189. package/dist/components/cms/document-url-path.js.map +1 -1
  190. package/dist/components/cms/duplicate-document.d.ts +1 -1
  191. package/dist/components/cms/duplicate-document.js.map +1 -1
  192. package/dist/components/cms/editable-cell.js +3 -3
  193. package/dist/components/cms/editable-cell.js.map +1 -1
  194. package/dist/components/cms/field-collaboration.d.ts +1 -1
  195. package/dist/components/cms/field-collaboration.js +1 -1
  196. package/dist/components/cms/field-collaboration.js.map +1 -1
  197. package/dist/components/cms/field-connection.d.ts +1 -1
  198. package/dist/components/cms/field-connection.js +2 -2
  199. package/dist/components/cms/field-connection.js.map +1 -1
  200. package/dist/components/cms/field-control-lib.js +1 -1
  201. package/dist/components/cms/field-control-lib.js.map +1 -1
  202. package/dist/components/cms/field-control.js +4 -4
  203. package/dist/components/cms/field-control.js.map +1 -1
  204. package/dist/components/cms/field-renderer.d.ts +1 -1
  205. package/dist/components/cms/field-renderer.js +2 -2
  206. package/dist/components/cms/field-renderer.js.map +1 -1
  207. package/dist/components/cms/filter-builder.d.ts +1 -1
  208. package/dist/components/cms/filter-builder.js +3 -3
  209. package/dist/components/cms/filter-builder.js.map +1 -1
  210. package/dist/components/cms/gate-validation.js +1 -1
  211. package/dist/components/cms/gate-validation.js.map +1 -1
  212. package/dist/components/cms/import-export.js +2 -2
  213. package/dist/components/cms/import-export.js.map +1 -1
  214. package/dist/components/cms/issue-copy.js +1 -1
  215. package/dist/components/cms/issue-copy.js.map +1 -1
  216. package/dist/components/cms/issues-pane.js +3 -3
  217. package/dist/components/cms/issues-pane.js.map +1 -1
  218. package/dist/components/cms/object-array-field.js +2 -2
  219. package/dist/components/cms/object-array-field.js.map +1 -1
  220. package/dist/components/cms/prop-fields.js +2 -2
  221. package/dist/components/cms/prop-fields.js.map +1 -1
  222. package/dist/components/cms/reference-confirm.d.ts +1 -1
  223. package/dist/components/cms/reference-confirm.js +2 -2
  224. package/dist/components/cms/reference-confirm.js.map +1 -1
  225. package/dist/components/cms/relation-picker.js +2 -2
  226. package/dist/components/cms/relation-picker.js.map +1 -1
  227. package/dist/components/cms/rich-text/block-style.js +1 -1
  228. package/dist/components/cms/rich-text/block-style.js.map +1 -1
  229. package/dist/components/cms/rich-text/component-embed.js +4 -4
  230. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  231. package/dist/components/cms/rich-text/editor.js +3 -3
  232. package/dist/components/cms/rich-text/editor.js.map +1 -1
  233. package/dist/components/cms/rich-text/embed-picker.js +3 -3
  234. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  235. package/dist/components/cms/rich-text/embed-props.js +3 -3
  236. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  237. package/dist/components/cms/rich-text/extensions.js +1 -1
  238. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  239. package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
  240. package/dist/components/cms/rich-text/font-size-control.js +2 -2
  241. package/dist/components/cms/rich-text/font-size-control.js.map +1 -1
  242. package/dist/components/cms/rich-text/lazy.js +1 -1
  243. package/dist/components/cms/rich-text/lazy.js.map +1 -1
  244. package/dist/components/cms/rich-text/link-control.js +2 -2
  245. package/dist/components/cms/rich-text/link-control.js.map +1 -1
  246. package/dist/components/cms/rich-text/slash-command.js +3 -3
  247. package/dist/components/cms/rich-text/slash-command.js.map +1 -1
  248. package/dist/components/cms/rich-text/toolbar.js +1 -1
  249. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  250. package/dist/components/cms/rich-text/unknown-node.js +2 -2
  251. package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
  252. package/dist/components/cms/rich-text-preview.js +2 -2
  253. package/dist/components/cms/rich-text-preview.js.map +1 -1
  254. package/dist/components/cms/row-actions.d.ts +1 -1
  255. package/dist/components/cms/row-actions.js.map +1 -1
  256. package/dist/components/cms/scalar-value-editor.d.ts +1 -1
  257. package/dist/components/cms/scalar-value-editor.js +2 -2
  258. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  259. package/dist/components/cms/use-discard-controller.d.ts +1 -1
  260. package/dist/components/cms/use-discard-controller.js +1 -1
  261. package/dist/components/cms/use-discard-controller.js.map +1 -1
  262. package/dist/components/cms/use-document-draft.d.ts +7 -1
  263. package/dist/components/cms/use-document-draft.js +16 -2
  264. package/dist/components/cms/use-document-draft.js.map +1 -1
  265. package/dist/components/cms/use-document-versions.js +1 -1
  266. package/dist/components/cms/use-document-versions.js.map +1 -1
  267. package/dist/components/cms/use-field-focus.d.ts +1 -1
  268. package/dist/components/cms/use-field-focus.js +2 -2
  269. package/dist/components/cms/use-field-focus.js.map +1 -1
  270. package/dist/components/cms/use-live-url.d.ts +1 -1
  271. package/dist/components/cms/use-live-url.js +3 -3
  272. package/dist/components/cms/use-live-url.js.map +1 -1
  273. package/dist/components/cms/use-publish-controller.d.ts +1 -1
  274. package/dist/components/cms/use-publish-controller.js +1 -1
  275. package/dist/components/cms/use-publish-controller.js.map +1 -1
  276. package/dist/components/cms/use-published-perspective.d.ts +1 -1
  277. package/dist/components/cms/use-published-perspective.js.map +1 -1
  278. package/dist/components/cms/use-restore-version.d.ts +1 -1
  279. package/dist/components/cms/use-restore-version.js +1 -1
  280. package/dist/components/cms/use-restore-version.js.map +1 -1
  281. package/dist/components/cms/version-compare-lazy.js +1 -1
  282. package/dist/components/cms/version-compare-lazy.js.map +1 -1
  283. package/dist/components/cms/version-compare.js +1 -1
  284. package/dist/components/cms/version-compare.js.map +1 -1
  285. package/dist/components/cms/versions-pane.js +3 -3
  286. package/dist/components/cms/versions-pane.js.map +1 -1
  287. package/dist/components/cms/view-bar.js +3 -3
  288. package/dist/components/cms/view-bar.js.map +1 -1
  289. package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
  290. package/dist/components/collection-view/cms-collection-view-source.js +2 -2
  291. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  292. package/dist/components/collection-view/collection-view-context.d.ts +1 -1
  293. package/dist/components/collection-view/collection-view-context.js +1 -1
  294. package/dist/components/collection-view/collection-view-context.js.map +1 -1
  295. package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
  296. package/dist/components/collection-view/collection-view-surface.js +18 -0
  297. package/dist/components/collection-view/collection-view-surface.js.map +1 -0
  298. package/dist/components/components/component-detail.js +10 -10
  299. package/dist/components/components/component-detail.js.map +1 -1
  300. package/dist/components/components/component-preview.d.ts +2 -2
  301. package/dist/components/components/component-preview.js +5 -5
  302. package/dist/components/components/component-preview.js.map +1 -1
  303. package/dist/components/components/component-status.d.ts +1 -1
  304. package/dist/components/components/component-status.js +1 -1
  305. package/dist/components/components/component-status.js.map +1 -1
  306. package/dist/components/components/components-index.js +2 -2
  307. package/dist/components/components/components-index.js.map +1 -1
  308. package/dist/components/components/field-inspector.js +1 -1
  309. package/dist/components/components/field-inspector.js.map +1 -1
  310. package/dist/components/components/registry-browser.js +3 -3
  311. package/dist/components/components/registry-browser.js.map +1 -1
  312. package/dist/components/components/taxonomy.d.ts +1 -1
  313. package/dist/components/components/taxonomy.js.map +1 -1
  314. package/dist/components/components/transfer-dialog.js +3 -3
  315. package/dist/components/components/transfer-dialog.js.map +1 -1
  316. package/dist/components/current-user-menu.js +1 -1
  317. package/dist/components/current-user-menu.js.map +1 -1
  318. package/dist/components/delivery-health-banner.js +2 -2
  319. package/dist/components/delivery-health-banner.js.map +1 -1
  320. package/dist/components/deployment-setup-gate.js +3 -3
  321. package/dist/components/deployment-setup-gate.js.map +1 -1
  322. package/dist/components/dev-login-badge.js +2 -2
  323. package/dist/components/dev-login-badge.js.map +1 -1
  324. package/dist/components/guards.js +1 -1
  325. package/dist/components/guards.js.map +1 -1
  326. package/dist/components/inspector/document-panes.d.ts +54 -0
  327. package/dist/components/inspector/document-panes.js +41 -0
  328. package/dist/components/inspector/document-panes.js.map +1 -0
  329. package/dist/components/inspector/inspector-sidebar.js +1 -1
  330. package/dist/components/inspector/inspector-sidebar.js.map +1 -1
  331. package/dist/components/nav-issues-registry.d.ts +53 -0
  332. package/dist/components/nav-issues-registry.js +69 -0
  333. package/dist/components/nav-issues-registry.js.map +1 -0
  334. package/dist/components/nav-issues.js +4 -4
  335. package/dist/components/nav-issues.js.map +1 -1
  336. package/dist/components/operation-error.d.ts +1 -1
  337. package/dist/components/operation-error.js +4 -4
  338. package/dist/components/operation-error.js.map +1 -1
  339. package/dist/components/playground-badge.js +2 -2
  340. package/dist/components/playground-badge.js.map +1 -1
  341. package/dist/components/project-list.js +3 -3
  342. package/dist/components/project-list.js.map +1 -1
  343. package/dist/components/project-session.d.ts +2 -2
  344. package/dist/components/project-session.js +5 -5
  345. package/dist/components/project-session.js.map +1 -1
  346. package/dist/components/project-switcher.js +2 -2
  347. package/dist/components/project-switcher.js.map +1 -1
  348. package/dist/components/rail-store-status.js +1 -1
  349. package/dist/components/rail-store-status.js.map +1 -1
  350. package/dist/components/rail-view-source.d.ts +6 -5
  351. package/dist/components/rail-view-source.js +4 -3
  352. package/dist/components/rail-view-source.js.map +1 -1
  353. package/dist/components/records/record-lens.d.ts +51 -0
  354. package/dist/components/records/record-lens.js +156 -0
  355. package/dist/components/records/record-lens.js.map +1 -0
  356. package/dist/components/records/record-rail.d.ts +86 -0
  357. package/dist/components/records/record-rail.js +241 -0
  358. package/dist/components/records/record-rail.js.map +1 -0
  359. package/dist/components/redirects/redirects-schema.js +1 -1
  360. package/dist/components/redirects/redirects-schema.js.map +1 -1
  361. package/dist/components/references/references-panel.js +2 -2
  362. package/dist/components/references/references-panel.js.map +1 -1
  363. package/dist/components/section-nav.js +27 -11
  364. package/dist/components/section-nav.js.map +1 -1
  365. package/dist/components/section-state.js +2 -2
  366. package/dist/components/section-state.js.map +1 -1
  367. package/dist/components/section-surface.js +2 -2
  368. package/dist/components/section-surface.js.map +1 -1
  369. package/dist/components/session-expired-overlay.js +3 -3
  370. package/dist/components/session-expired-overlay.js.map +1 -1
  371. package/dist/components/settings-form.js +1 -1
  372. package/dist/components/settings-form.js.map +1 -1
  373. package/dist/components/sign-in-needed-badge.js +1 -1
  374. package/dist/components/sign-in-needed-badge.js.map +1 -1
  375. package/dist/components/sign-in-view.d.ts +2 -2
  376. package/dist/components/sign-in-view.js +5 -5
  377. package/dist/components/sign-in-view.js.map +1 -1
  378. package/dist/components/studio-context.js +3 -3
  379. package/dist/components/studio-context.js.map +1 -1
  380. package/dist/components/studio-project-screen.js +30 -3
  381. package/dist/components/studio-project-screen.js.map +1 -1
  382. package/dist/components/studio-shell.js +6 -5
  383. package/dist/components/studio-shell.js.map +1 -1
  384. package/dist/components/theme-context.d.ts +1 -1
  385. package/dist/components/theme-context.js +2 -2
  386. package/dist/components/theme-context.js.map +1 -1
  387. package/dist/components/use-hydration-phase.d.ts +1 -1
  388. package/dist/components/use-hydration-phase.js +1 -1
  389. package/dist/components/use-hydration-phase.js.map +1 -1
  390. package/dist/components/users/invite-user-bar.d.ts +27 -0
  391. package/dist/components/users/invite-user-bar.js +25 -9
  392. package/dist/components/users/invite-user-bar.js.map +1 -1
  393. package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
  394. package/dist/components/users/role-policy-drift-issues.js +71 -0
  395. package/dist/components/users/role-policy-drift-issues.js.map +1 -0
  396. package/dist/components/users/users-collection-view-source.d.ts +1 -1
  397. package/dist/components/users/users-collection-view-source.js +4 -2
  398. package/dist/components/users/users-collection-view-source.js.map +1 -1
  399. package/dist/components/users/users-module.js +1 -1
  400. package/dist/components/users/users-module.js.map +1 -1
  401. package/dist/components/users/users-schema.d.ts +10 -1
  402. package/dist/components/users/users-schema.js +45 -25
  403. package/dist/components/users/users-schema.js.map +1 -1
  404. package/dist/components/users/users-store.js +3 -3
  405. package/dist/components/users/users-store.js.map +1 -1
  406. package/dist/components/workspace-projects.d.ts +1 -1
  407. package/dist/components/workspace-projects.js +1 -1
  408. package/dist/components/workspace-projects.js.map +1 -1
  409. package/dist/lib/ai/index.d.ts +1 -1
  410. package/dist/lib/ai/index.js +1 -1
  411. package/dist/lib/ai/index.js.map +1 -1
  412. package/dist/lib/ai/node-handler.d.ts +1 -1
  413. package/dist/lib/ai/node-handler.js.map +1 -1
  414. package/dist/lib/ai/translate-document.js +1 -1
  415. package/dist/lib/ai/translate-document.js.map +1 -1
  416. package/dist/lib/clipboard/paste-issues.js +1 -1
  417. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  418. package/dist/lib/clipboard/paste-target.js +2 -2
  419. package/dist/lib/clipboard/paste-target.js.map +1 -1
  420. package/dist/lib/cms/published-documents.js +1 -1
  421. package/dist/lib/cms/published-documents.js.map +1 -1
  422. package/dist/lib/cms-views/view-model.d.ts +12 -12
  423. package/dist/lib/cms-views/view-model.js +2 -2
  424. package/dist/lib/cms-views/view-model.js.map +1 -1
  425. package/dist/lib/collection-view/collection-view-source.d.ts +7 -2
  426. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  427. package/dist/lib/data/content-session-refresh.d.ts +1 -1
  428. package/dist/lib/data/content-session-refresh.js.map +1 -1
  429. package/dist/lib/data/playground-unload-guard.d.ts +1 -1
  430. package/dist/lib/data/playground-unload-guard.js +1 -1
  431. package/dist/lib/data/playground-unload-guard.js.map +1 -1
  432. package/dist/lib/engine-version.d.ts +1 -1
  433. package/dist/lib/engine-version.js +1 -1
  434. package/dist/lib/engine-version.js.map +1 -1
  435. package/dist/lib/i18n/i18n.js +1 -1
  436. package/dist/lib/i18n/i18n.js.map +1 -1
  437. package/dist/lib/i18n/messages.de.js +4 -0
  438. package/dist/lib/i18n/messages.de.js.map +1 -1
  439. package/dist/lib/i18n/messages.en.d.ts +4 -0
  440. package/dist/lib/i18n/messages.en.js +8 -0
  441. package/dist/lib/i18n/messages.en.js.map +1 -1
  442. package/dist/lib/operations/project-runtime-config.d.ts +9 -0
  443. package/dist/lib/operations/project-runtime-config.js +13 -0
  444. package/dist/lib/operations/project-runtime-config.js.map +1 -1
  445. package/dist/lib/operations/studio-operations.d.ts +9 -1
  446. package/dist/lib/operations/studio-operations.js +6 -1
  447. package/dist/lib/operations/studio-operations.js.map +1 -1
  448. package/dist/lib/persistence/convex-adapter.d.ts +16 -3
  449. package/dist/lib/persistence/convex-adapter.js +78 -47
  450. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  451. package/dist/lib/persistence/index.js +14 -1
  452. package/dist/lib/persistence/index.js.map +1 -1
  453. package/dist/lib/persona/labels.js +1 -1
  454. package/dist/lib/persona/labels.js.map +1 -1
  455. package/dist/lib/query/operations-query.d.ts +1 -1
  456. package/dist/lib/query/operations-query.js +1 -1
  457. package/dist/lib/query/operations-query.js.map +1 -1
  458. package/dist/lib/references/reference-model.js +1 -1
  459. package/dist/lib/references/reference-model.js.map +1 -1
  460. package/dist/lib/references/references-store.js +1 -1
  461. package/dist/lib/references/references-store.js.map +1 -1
  462. package/dist/lib/workspace/index.d.ts +6 -0
  463. package/dist/lib/workspace/index.js +8 -0
  464. package/dist/lib/workspace/index.js.map +1 -1
  465. package/dist/mount.d.ts +28 -0
  466. package/dist/mount.js +27 -0
  467. package/dist/mount.js.map +1 -0
  468. package/dist/router/root.js +10 -10
  469. package/dist/router/root.js.map +1 -1
  470. package/dist/ui/badge.d.ts +1 -1
  471. package/dist/ui/button.d.ts +2 -2
  472. package/package.json +15 -11
  473. package/dist/components/redirects/redirects-collection-view-source.d.ts +0 -53
  474. package/dist/components/redirects/redirects-collection-view-source.js +0 -146
  475. package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
  476. package/dist/components/redirects/redirects-module.d.ts +0 -21
  477. package/dist/components/redirects/redirects-module.js +0 -40
  478. package/dist/components/redirects/redirects-module.js.map +0 -1
  479. package/dist/components/redirects/redirects-store.d.ts +0 -68
  480. package/dist/components/redirects/redirects-store.js +0 -132
  481. package/dist/components/redirects/redirects-store.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-policy-drift-issues.js","sourceRoot":"","sources":["../../../src/components/users/role-policy-drift-issues.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAA;AAEnE,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAA;AAK9G,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6CAA6C,CAAA;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAGrD;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,MAAM,EACN,QAAQ,EACR,KAAK,GAKN;IACC,MAAM,OAAO,GAAG,sBAAsB,CAAC,EAAE,iBAAiB,EAAE,qBAAqB,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;IAC7H,OAAO,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;AACnD,CAAC;AAED,iHAAiH;AACjH,MAAM,UAAU,wBAAwB,CAAC,OAAsC;IAC7E,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAA;IACvB,OAAO;QACL;YACE,GAAG,EAAE,OAAO;YACZ,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,GAAG,SAAS,CAAC,6BAA6B,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;SAChH;KACF,CAAA;AACH,CAAC;AAED,MAAM,SAAS,GAAwB,EAAE,CAAA;AAEzC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,EAAE,SAAS,EAAyB;IACxE,MAAM,OAAO,GAAG,oBAAoB,EAAE,CAAA;IACtC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAEhG,MAAM,KAAK,GAAG,iBAAiB,CAA0B;QACvD,QAAQ,EAAE,CAAC,OAAO,EAAE,iBAAiB,EAAE,SAAS,CAAC;QACjD,MAAM,EAAE,GAAG,EAAE,CAAC,uBAAuB,CAAC,GAAI,CAAC;QAC3C,OAAO,EAAE,GAAG,KAAK,SAAS;KAC3B,CAAC,CAAA;IACF,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAA;IACxB,MAAM,MAAM,GAAG,OAAO,CACpB,GAAG,EAAE,CACH,KAAK;QACH,CAAC,CAAC,wBAAwB,CACtB,qBAAqB,CAAC;YACpB,MAAM,EAAE,oBAAoB,CAAC,SAAS,CAAC;YACvC,QAAQ,EAAE,gBAAgB,CAAC,SAAS,CAAC,EAAE,KAAK,KAAK,SAAS;YAC1D,KAAK;SACN,CAAC,CACH;QACH,CAAC,CAAC,SAAS,EACf,CAAC,KAAK,EAAE,SAAS,CAAC,CACnB,CAAA;IACD,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;IACpC,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { useMemo } from 'react'\nimport { rolePolicyFingerprint } from '@elytracms/core/persistence'\nimport type { RolePolicy } from '@elytracms/core/persistence'\nimport { probeDeployedRolePolicy, rolePolicyDriftVerdict } from '@elytracms/core/export-sync/deployment-ready'\nimport type {\n DeployedRolePolicyProbe,\n RolePolicyDriftVerdict,\n} from '@elytracms/core/export-sync/deployment-ready'\nimport { translate } from '../../lib/i18n'\nimport { projectRolePolicyFor } from '../../lib/operations/project-runtime-config'\nimport { resolveBackendConfig } from '../../lib/persistence/backend-mode'\nimport { configProjectFor, deploymentUrlForSlug } from '../../lib/workspace'\nimport { useOperationQuery } from '../../lib/query'\nimport { useNavIssues } from '../nav-issues-registry'\nimport type { NavIssue } from '../nav-issues-registry'\n\n/**\n * EC-605 — the studio's half of the role-policy drift check: which verdict, if any, the\n * signed-in user is shown. Pure. The decision itself is core's ONE verdict\n * (`rolePolicyDriftVerdict`, the same `elytra doctor` prints as its `role-policy` row);\n * the studio surfaces only the `fail`s — a deployment enforcing another policy than the\n * config declares (or an invalid one, or none it can show while the config declares\n * roles). A `warn` (functions without `roles:fingerprint`, no roles declared) is a\n * developer's upgrade step the doctor names; an `unknown` is transient. `null` → nothing\n * to show.\n */\nexport function studioRolePolicyDrift({\n policy,\n declared,\n probe,\n}: {\n readonly policy: RolePolicy\n readonly declared: boolean\n readonly probe: DeployedRolePolicyProbe\n}): RolePolicyDriftVerdict | null {\n const verdict = rolePolicyDriftVerdict({ configFingerprint: rolePolicyFingerprint(policy), configDeclared: declared, probe })\n return verdict.status === 'fail' ? verdict : null\n}\n\n/** The rail-foot finding for a verdict (pure): the localized headline, then the verdict's own detail and fix. */\nexport function rolePolicyDriftNavIssues(verdict: RolePolicyDriftVerdict | null): readonly NavIssue[] {\n if (!verdict) return []\n return [\n {\n key: 'drift',\n severity: 'error',\n message: `${translate('shell.rolePolicyDrift.title')} ${verdict.detail}${verdict.fix ? ` ${verdict.fix}` : ''}`,\n },\n ]\n}\n\nconst NO_ISSUES: readonly NavIssue[] = []\n\n/**\n * EC-605 — a visible issue, from boot, for every signed-in user when the connected\n * deployment enforces a different role policy than this project's `elytra.config.ts`\n * declares. While the two copies differ the deployment enforces ITS copy: a role only the\n * config declares is refused everywhere, and a role whose grants the config reduced keeps\n * the deployed grants — so a drift is never left silent. Published to the rail's issues\n * foot (EC-616 `useNavIssues`, on every studio surface) for as long as the shell is\n * mounted; renders nothing itself.\n *\n * It reads the deployed fingerprint through the public `roles:fingerprint` query with the\n * same probe the doctor runs (`probeDeployedRolePolicy`), cached in the one query cache\n * like every studio read (AD-17). Playground has no deployed copy (the operations layer is\n * the only reader), so it never publishes there. A read that fails publishes nothing —\n * only a CONFIRMED drift does.\n */\nexport function RolePolicyDriftIssues({ projectId }: { projectId: string }) {\n const backend = resolveBackendConfig()\n const url = backend.kind === 'convex' ? deploymentUrlForSlug(projectId, backend.url) : undefined\n\n const query = useOperationQuery<DeployedRolePolicyProbe>({\n queryKey: ['roles', 'deployed-policy', projectId],\n loader: () => probeDeployedRolePolicy(url!),\n enabled: url !== undefined,\n })\n const probe = query.data\n const issues = useMemo(\n () =>\n probe\n ? rolePolicyDriftNavIssues(\n studioRolePolicyDrift({\n policy: projectRolePolicyFor(projectId),\n declared: configProjectFor(projectId)?.roles !== undefined,\n probe,\n }),\n )\n : NO_ISSUES,\n [probe, projectId],\n )\n useNavIssues('roles:policy', issues)\n return null\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { CmsDocument, FieldDef } from '@elytracms/core/cms-core';
2
2
  import type { SaveUserInput, UserRecord } from '@elytracms/core/persistence';
3
- import type { CollectionViewCapabilities, CollectionViewSource, FieldAccess } from '@elytracms/studio/lib/collection-view/collection-view-source';
3
+ import type { CollectionViewCapabilities, CollectionViewSource, FieldAccess } from '../../lib/collection-view/collection-view-source.js';
4
4
  /**
5
5
  * The capability profile of the users rail (EC-240): admin-only, no
6
6
  * publish/versions/create/import (identities are minted by sign-up and their
@@ -2,7 +2,8 @@ import { useCallback } from 'react';
2
2
  import { studioPath } from '@elytracms/studio/router';
3
3
  import { useAuth } from '../auth-context.js';
4
4
  import { useUsers } from './users-store.js';
5
- import { usersCollectionDef } from './users-schema.js';
5
+ import { usersCollectionDef, usersCollectionDefFor } from './users-schema.js';
6
+ import { projectRolePolicyFor } from '../../lib/operations/project-runtime-config.js';
6
7
  import { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source.js';
7
8
  import { parseLocaleConfig } from '@elytracms/core/cms-core';
8
9
  /** The users rail is not localized — one implicit locale keeps the lens honest. */
@@ -113,7 +114,8 @@ export function useUsersCollectionViewSource(projectId) {
113
114
  const remove = useCallback((ref) => users.remove(ref.id), [users]);
114
115
  return useRailViewSource({
115
116
  projectId,
116
- schema: usersCollectionDef,
117
+ // EC-605: the role select lists THIS project's declared roles (stable per policy).
118
+ schema: usersCollectionDefFor(projectRolePolicyFor(projectId)),
117
119
  capabilities: USER_COLLECTION_CAPABILITIES,
118
120
  localeConfig: USERS_LOCALE_CONFIG,
119
121
  documents,
@@ -1 +1 @@
1
- {"version":3,"file":"users-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/users/users-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback } from 'react'\nimport type { CmsDocument, CmsValidationIssue, DocumentRef, FieldDef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport type { ProjectRole, SaveUserInput, UserRecord } from '@elytracms/core/persistence'\nimport { studioPath } from '@elytracms/studio/router'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n FieldAccess,\n} from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useAuth } from '../auth-context'\nimport { useUsers } from './users-store'\nimport { usersCollectionDef } from './users-schema'\nimport { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source'\nimport { parseLocaleConfig } from '@elytracms/core/cms-core'\n\n/** The users rail is not localized — one implicit locale keeps the lens honest. */\nconst USERS_LOCALE: Locale = 'en'\nconst USERS_LOCALE_CONFIG: LocaleConfig = parseLocaleConfig({ default: USERS_LOCALE, locales: [USERS_LOCALE] })\n\n/**\n * The capability profile of the users rail (EC-240): admin-only, no\n * publish/versions/create/import (identities are minted by sign-up and their\n * bytes are auth-owned), remove allowed (revoke membership). Hoisted to a\n * module-level constant (no prop/state dependency, EC-327) — mirrors\n * `ASSET_COLLECTION_CAPABILITIES`.\n */\nexport const USER_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: false, // identities are minted by sign-up, never here\n duplicate: false, // EC-442: identities aren't copyable content\n remove: true, // revoke membership (does not delete the auth identity)\n bulk: true, // EC-433: bulk delete = the same per-id revoke, honestly reported\n liveUrl: false,\n hierarchy: false,\n importExport: false, // credential records are never dumped to raw JSON\n adminOnly: true,\n}\n\n/** The built-in field names — everything else in `values` is a custom `meta` field. */\nconst IDENTITY_FIELDS = new Set(['name', 'email', 'role'])\n\n/** Project the auth-identity + membership join into the lens's `values` shape. */\nexport function userToDocument(record: UserRecord): CmsDocument {\n return {\n collection: usersCollectionDef.id,\n id: record.id,\n values: {\n // Display title falls through empties (name → email → id) so a nameless,\n // blank-email member never renders as a blank row; `email` keeps its real value.\n name: record.name || record.email || record.id,\n email: record.email,\n role: record.roles[0] ?? 'viewer',\n ...(record.meta ?? {}),\n },\n }\n}\n\n/**\n * Principal-based field access for the users rail (EC-240, AD-18). `role` is\n * authority: **read-only when you are editing YOURSELF** — you don't change your\n * own role (the server integrity floor also guards it) — and **editable otherwise**\n * (the rail is admin-only, so a non-self editor is an admin acting on someone else).\n * Identity fields (name/email) follow their schema `form.readOnly`; everything else\n * is editable. Pure, so the self/other rule is unit-testable without the auth hook.\n */\nexport function userFieldAccess(\n field: FieldDef,\n record: CmsDocument | undefined,\n currentUserId: string | undefined,\n): FieldAccess {\n if (field.name === 'role') {\n return record?.id === currentUserId ? 'read' : 'edit'\n }\n return field.form?.readOnly ? 'read' : 'edit'\n}\n\n/** Map an edited lens document back to the write contract (role + custom `meta`). */\nexport function documentToSaveInput(doc: CmsDocument): SaveUserInput {\n const values = doc.values ?? {}\n const meta = metaBagProjection(values, IDENTITY_FIELDS)\n const role = typeof values.role === 'string' ? (values.role as ProjectRole) : 'viewer'\n return { id: doc.id, roles: [role], ...(Object.keys(meta).length > 0 ? { meta } : {}) }\n}\n\n/** `validate` for the users rail: no-op — a member's identity fields aren't\n * schema-validated the way CMS documents are (240a-era; a registry-driven\n * validation may land alongside a future custom-field schema). */\nfunction noValidation(): CmsValidationIssue[] {\n return []\n}\n\n/** `create` when `capabilities.create === false` — never reached (identities\n * are minted by sign-up, not the users rail). */\nfunction createUnsupported(): DocumentRef {\n throw new Error('users are created by sign-up, not the users rail')\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **users rail** (EC-240, AD-19) —\n * the reuse proof that the portable list/edit surfaces serve a non-`cmsDocuments`\n * rail. Mirrors {@link useCmsCollectionViewSource}, over the users store instead of\n * the CMS store, with the users capability profile: admin-only, no\n * publish/versions/create/import (identities are minted by sign-up and their bytes\n * are auth-owned), remove allowed (revoke membership). Identity fields are\n * read-only; an admin edits a member's role (and future custom `meta` fields).\n *\n * Must be mounted under a `UsersProvider` (for {@link useUsers}).\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the users-store\n * reads/writes, the capability profile, and the principal-based `fieldAccess`.\n */\nexport function useUsersCollectionViewSource(projectId: string): CollectionViewSource {\n const users = useUsers()\n // The signed-in principal — so `role` locks to read-only when an admin opens\n // their OWN record (self ≠ other; the server floor enforces it regardless).\n const auth = useAuth()\n const currentUserId = auth.session?.user?.id\n\n const documents = useCallback(() => users.users.map(userToDocument), [users.users])\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = users.users.find((u) => u.id === ref.id)\n return record ? userToDocument(record) : undefined\n },\n [users.users],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => {\n const record = users.users.find((u) => u.id === ref.id)\n return record?.name || record?.email || ref.id\n },\n [users.users],\n )\n const fieldAccess = useCallback(\n (field: FieldDef, record?: CmsDocument): FieldAccess => userFieldAccess(field, record, currentUserId),\n [currentUserId],\n )\n const save = useCallback((doc: CmsDocument) => users.save(documentToSaveInput(doc)), [users])\n const remove = useCallback((ref: DocumentRef) => users.remove(ref.id), [users])\n\n return useRailViewSource({\n projectId,\n schema: usersCollectionDef,\n capabilities: USER_COLLECTION_CAPABILITIES,\n localeConfig: USERS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections` override: users carry no relation\n // fields in v1, so there are no cross-collection candidates to resolve; the\n // lens is the only collection in scope (both factory defaults apply).\n // The users rail now has a per-member detail surface (EC-240): rows link to\n // it, and the edit shell's back-crumb / post-delete nav return to the list.\n detailPath: (id: string) => studioPath.user(id),\n listPath: studioPath.users(),\n // Principal-based tri-state: `role` is read-only for self, editable for an\n // admin acting on another; identity fields follow their schema readOnly.\n fieldAccess,\n validate: noValidation,\n create: createUnsupported,\n save,\n remove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeOkResult,\n })\n}\n"]}
1
+ {"version":3,"file":"users-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/users/users-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback } from 'react'\nimport type { CmsDocument, CmsValidationIssue, DocumentRef, FieldDef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport type { ProjectRole, SaveUserInput, UserRecord } from '@elytracms/core/persistence'\nimport { studioPath } from '@elytracms/studio/router'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n FieldAccess,\n} from '../../lib/collection-view/collection-view-source'\nimport { useAuth } from '../auth-context'\nimport { useUsers } from './users-store'\nimport { usersCollectionDef, usersCollectionDefFor } from './users-schema'\nimport { projectRolePolicyFor } from '../../lib/operations/project-runtime-config'\nimport { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source'\nimport { parseLocaleConfig } from '@elytracms/core/cms-core'\n\n/** The users rail is not localized — one implicit locale keeps the lens honest. */\nconst USERS_LOCALE: Locale = 'en'\nconst USERS_LOCALE_CONFIG: LocaleConfig = parseLocaleConfig({ default: USERS_LOCALE, locales: [USERS_LOCALE] })\n\n/**\n * The capability profile of the users rail (EC-240): admin-only, no\n * publish/versions/create/import (identities are minted by sign-up and their\n * bytes are auth-owned), remove allowed (revoke membership). Hoisted to a\n * module-level constant (no prop/state dependency, EC-327) — mirrors\n * `ASSET_COLLECTION_CAPABILITIES`.\n */\nexport const USER_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: false, // identities are minted by sign-up, never here\n duplicate: false, // EC-442: identities aren't copyable content\n remove: true, // revoke membership (does not delete the auth identity)\n bulk: true, // EC-433: bulk delete = the same per-id revoke, honestly reported\n liveUrl: false,\n hierarchy: false,\n importExport: false, // credential records are never dumped to raw JSON\n adminOnly: true,\n}\n\n/** The built-in field names — everything else in `values` is a custom `meta` field. */\nconst IDENTITY_FIELDS = new Set(['name', 'email', 'role'])\n\n/** Project the auth-identity + membership join into the lens's `values` shape. */\nexport function userToDocument(record: UserRecord): CmsDocument {\n return {\n collection: usersCollectionDef.id,\n id: record.id,\n values: {\n // Display title falls through empties (name → email → id) so a nameless,\n // blank-email member never renders as a blank row; `email` keeps its real value.\n name: record.name || record.email || record.id,\n email: record.email,\n role: record.roles[0] ?? 'viewer',\n ...(record.meta ?? {}),\n },\n }\n}\n\n/**\n * Principal-based field access for the users rail (EC-240, AD-18). `role` is\n * authority: **read-only when you are editing YOURSELF** — you don't change your\n * own role (the server integrity floor also guards it) — and **editable otherwise**\n * (the rail is admin-only, so a non-self editor is an admin acting on someone else).\n * Identity fields (name/email) follow their schema `form.readOnly`; everything else\n * is editable. Pure, so the self/other rule is unit-testable without the auth hook.\n */\nexport function userFieldAccess(\n field: FieldDef,\n record: CmsDocument | undefined,\n currentUserId: string | undefined,\n): FieldAccess {\n if (field.name === 'role') {\n return record?.id === currentUserId ? 'read' : 'edit'\n }\n return field.form?.readOnly ? 'read' : 'edit'\n}\n\n/** Map an edited lens document back to the write contract (role + custom `meta`). */\nexport function documentToSaveInput(doc: CmsDocument): SaveUserInput {\n const values = doc.values ?? {}\n const meta = metaBagProjection(values, IDENTITY_FIELDS)\n const role = typeof values.role === 'string' ? (values.role as ProjectRole) : 'viewer'\n return { id: doc.id, roles: [role], ...(Object.keys(meta).length > 0 ? { meta } : {}) }\n}\n\n/** `validate` for the users rail: no-op — a member's identity fields aren't\n * schema-validated the way CMS documents are (240a-era; a registry-driven\n * validation may land alongside a future custom-field schema). */\nfunction noValidation(): CmsValidationIssue[] {\n return []\n}\n\n/** `create` when `capabilities.create === false` — never reached (identities\n * are minted by sign-up, not the users rail). */\nfunction createUnsupported(): DocumentRef {\n throw new Error('users are created by sign-up, not the users rail')\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **users rail** (EC-240, AD-19) —\n * the reuse proof that the portable list/edit surfaces serve a non-`cmsDocuments`\n * rail. Mirrors {@link useCmsCollectionViewSource}, over the users store instead of\n * the CMS store, with the users capability profile: admin-only, no\n * publish/versions/create/import (identities are minted by sign-up and their bytes\n * are auth-owned), remove allowed (revoke membership). Identity fields are\n * read-only; an admin edits a member's role (and future custom `meta` fields).\n *\n * Must be mounted under a `UsersProvider` (for {@link useUsers}).\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the users-store\n * reads/writes, the capability profile, and the principal-based `fieldAccess`.\n */\nexport function useUsersCollectionViewSource(projectId: string): CollectionViewSource {\n const users = useUsers()\n // The signed-in principal — so `role` locks to read-only when an admin opens\n // their OWN record (self ≠ other; the server floor enforces it regardless).\n const auth = useAuth()\n const currentUserId = auth.session?.user?.id\n\n const documents = useCallback(() => users.users.map(userToDocument), [users.users])\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = users.users.find((u) => u.id === ref.id)\n return record ? userToDocument(record) : undefined\n },\n [users.users],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => {\n const record = users.users.find((u) => u.id === ref.id)\n return record?.name || record?.email || ref.id\n },\n [users.users],\n )\n const fieldAccess = useCallback(\n (field: FieldDef, record?: CmsDocument): FieldAccess => userFieldAccess(field, record, currentUserId),\n [currentUserId],\n )\n const save = useCallback((doc: CmsDocument) => users.save(documentToSaveInput(doc)), [users])\n const remove = useCallback((ref: DocumentRef) => users.remove(ref.id), [users])\n\n return useRailViewSource({\n projectId,\n // EC-605: the role select lists THIS project's declared roles (stable per policy).\n schema: usersCollectionDefFor(projectRolePolicyFor(projectId)),\n capabilities: USER_COLLECTION_CAPABILITIES,\n localeConfig: USERS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections` override: users carry no relation\n // fields in v1, so there are no cross-collection candidates to resolve; the\n // lens is the only collection in scope (both factory defaults apply).\n // The users rail now has a per-member detail surface (EC-240): rows link to\n // it, and the edit shell's back-crumb / post-delete nav return to the list.\n detailPath: (id: string) => studioPath.user(id),\n listPath: studioPath.users(),\n // Principal-based tri-state: `role` is read-only for self, editable for an\n // admin acting on another; identity fields follow their schema readOnly.\n fieldAccess,\n validate: noValidation,\n create: createUnsupported,\n save,\n remove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeOkResult,\n })\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ShieldAlert } from 'lucide-react';
3
3
  import { Heading, Stack, Text } from '@elytracms/studio/ui';
4
- import { translate } from '@elytracms/studio/lib/i18n';
4
+ import { translate } from '../../lib/i18n/index.js';
5
5
  import { useAuth, useProjectRole } from '../auth-context.js';
6
6
  import { UsersProvider, useUsers } from './users-store.js';
7
7
  import { useUsersCollectionViewSource } from './users-collection-view-source.js';
@@ -1 +1 @@
1
- {"version":3,"file":"users-module.js","sourceRoot":"","sources":["../../../src/components/users/users-module.tsx"],"names":[],"mappings":"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","sourcesContent":["import { ShieldAlert } from 'lucide-react'\nimport { Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { useAuth, useProjectRole } from '../auth-context'\nimport { UsersProvider, useUsers } from './users-store'\nimport { useUsersCollectionViewSource } from './users-collection-view-source'\nimport { CollectionViewProvider } from '../collection-view/collection-view-context'\nimport { RailStoreStatus } from '../rail-store-status'\nimport { LoadedCollectionTable } from '../cms/collection-table'\nimport { PortableDetailEditor } from '../cms/detail-editor'\nimport { AccountSettings } from '../account/account-settings'\nimport { InviteUserBar } from './invite-user-bar'\n\n/**\n * The studio Users module (EC-240). An admin-only section for managing who has\n * access to this deployment and their role. AD-19: it is the **reuse proof** for\n * the portable interaction surfaces — it renders the SAME `LoadedCollectionTable`\n * the CMS uses for the roster, and the SAME `DetailEditor` body (via\n * {@link PortableDetailEditor}) for a member's detail, both over a users\n * {@link useUsersCollectionViewSource} instead of a CMS source. No bespoke roster,\n * no bespoke editor: filtering, columns, the count, and the edit form all come\n * from the shared surfaces. The users capability profile (admin-only, no publish/\n * versions/create/import) drives which regions appear.\n *\n * `userId === null` is the roster; a `userId` opens that member's detail (routed\n * via `studioPath.user`). Both render under one `UsersProvider` + one source.\n *\n * Gating is layered: the operations chokepoint AND the Convex handlers enforce\n * admin-only server-side (a non-admin `users.list` is denied). This UI gate is UX\n * politeness — a non-admin sees a clear \"restricted\" state instead of a raw\n * authorization error, and we skip firing a query we know will be denied.\n */\nexport function UsersModule({\n projectId,\n userId = null,\n}: {\n projectId: string\n userId?: string | null\n}) {\n const auth = useAuth()\n const role = useProjectRole(projectId)\n const currentUserId = auth.session?.user?.id ?? null\n\n // Self-access (EC-240 H-tail item 1): ANY member — admin or not — may open their\n // OWN record, and only that. This is the account-settings surface (self-permitted\n // read + identity/credential writes), routed here from the user menu. A non-admin\n // opening someone ELSE's `users/<id>` still falls through to the restricted state.\n if (userId !== null && currentUserId !== null && userId === currentUserId) {\n return <AccountSettings projectId={projectId} />\n }\n\n // `useProjectRole` conflates \"loading\" and \"not a member\" as null; in the studio\n // a member always reaches here, so null resolves to the real role momentarily.\n if (role === null) {\n return (\n <Stack className=\"h-full place-items-center p-8\" align=\"center\" justify=\"center\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('users.checkingAccess')}\n </Text>\n </Stack>\n )\n }\n if (role !== 'admin') return <RestrictedState />\n\n return (\n <UsersProvider projectId={projectId}>\n <UsersSurface projectId={projectId} userId={userId} />\n </UsersProvider>\n )\n}\n\n/** The non-admin gate: honest about why the surface is empty. */\nfunction RestrictedState() {\n return (\n <Stack gap=\"2\" align=\"center\" justify=\"center\" className=\"h-full p-8 text-center\">\n <ShieldAlert className=\"size-6 text-muted-foreground\" />\n <Heading level={2} size=\"h3\">\n {translate('users.restrictedTitle')}\n </Heading>\n <Text size=\"sm\" tone=\"muted\" className=\"max-w-sm\">\n {translate('users.restrictedBody')}\n </Text>\n </Stack>\n )\n}\n\n/** The loaded surface: the portable list (or a member's detail) over the users source. */\nfunction UsersSurface({ projectId, userId }: { projectId: string; userId: string | null }) {\n const store = useUsers()\n const source = useUsersCollectionViewSource(projectId)\n\n if (store.status !== 'ready') {\n return (\n <RailStoreStatus\n store={store}\n errorLabel={translate('users.loadFailed')}\n loadingLabel={translate('users.loading')}\n errorTestId=\"users-error\"\n />\n )\n }\n\n // A member's detail opens in the portable edit shell (AD-19); the list otherwise.\n // The shell mounts its own CollectionViewProvider, so only the list wraps here.\n if (userId !== null) {\n return <PortableDetailEditor source={source} projectId={projectId} documentId={userId} />\n }\n\n return (\n <Stack gap=\"none\" className=\"h-full min-h-0\">\n {/* EC-324: invite-based onboarding — the admin action that replaces open sign-up. */}\n <InviteUserBar projectId={projectId} />\n <div className=\"min-h-0 flex-1\">\n <CollectionViewProvider source={source}>\n <LoadedCollectionTable projectId={projectId} />\n </CollectionViewProvider>\n </div>\n </Stack>\n )\n}\n"]}
1
+ {"version":3,"file":"users-module.js","sourceRoot":"","sources":["../../../src/components/users/users-module.tsx"],"names":[],"mappings":"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","sourcesContent":["import { ShieldAlert } from 'lucide-react'\nimport { Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\nimport { useAuth, useProjectRole } from '../auth-context'\nimport { UsersProvider, useUsers } from './users-store'\nimport { useUsersCollectionViewSource } from './users-collection-view-source'\nimport { CollectionViewProvider } from '../collection-view/collection-view-context'\nimport { RailStoreStatus } from '../rail-store-status'\nimport { LoadedCollectionTable } from '../cms/collection-table'\nimport { PortableDetailEditor } from '../cms/detail-editor'\nimport { AccountSettings } from '../account/account-settings'\nimport { InviteUserBar } from './invite-user-bar'\n\n/**\n * The studio Users module (EC-240). An admin-only section for managing who has\n * access to this deployment and their role. AD-19: it is the **reuse proof** for\n * the portable interaction surfaces — it renders the SAME `LoadedCollectionTable`\n * the CMS uses for the roster, and the SAME `DetailEditor` body (via\n * {@link PortableDetailEditor}) for a member's detail, both over a users\n * {@link useUsersCollectionViewSource} instead of a CMS source. No bespoke roster,\n * no bespoke editor: filtering, columns, the count, and the edit form all come\n * from the shared surfaces. The users capability profile (admin-only, no publish/\n * versions/create/import) drives which regions appear.\n *\n * `userId === null` is the roster; a `userId` opens that member's detail (routed\n * via `studioPath.user`). Both render under one `UsersProvider` + one source.\n *\n * Gating is layered: the operations chokepoint AND the Convex handlers enforce\n * admin-only server-side (a non-admin `users.list` is denied). This UI gate is UX\n * politeness — a non-admin sees a clear \"restricted\" state instead of a raw\n * authorization error, and we skip firing a query we know will be denied.\n */\nexport function UsersModule({\n projectId,\n userId = null,\n}: {\n projectId: string\n userId?: string | null\n}) {\n const auth = useAuth()\n const role = useProjectRole(projectId)\n const currentUserId = auth.session?.user?.id ?? null\n\n // Self-access (EC-240 H-tail item 1): ANY member — admin or not — may open their\n // OWN record, and only that. This is the account-settings surface (self-permitted\n // read + identity/credential writes), routed here from the user menu. A non-admin\n // opening someone ELSE's `users/<id>` still falls through to the restricted state.\n if (userId !== null && currentUserId !== null && userId === currentUserId) {\n return <AccountSettings projectId={projectId} />\n }\n\n // `useProjectRole` conflates \"loading\" and \"not a member\" as null; in the studio\n // a member always reaches here, so null resolves to the real role momentarily.\n if (role === null) {\n return (\n <Stack className=\"h-full place-items-center p-8\" align=\"center\" justify=\"center\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('users.checkingAccess')}\n </Text>\n </Stack>\n )\n }\n if (role !== 'admin') return <RestrictedState />\n\n return (\n <UsersProvider projectId={projectId}>\n <UsersSurface projectId={projectId} userId={userId} />\n </UsersProvider>\n )\n}\n\n/** The non-admin gate: honest about why the surface is empty. */\nfunction RestrictedState() {\n return (\n <Stack gap=\"2\" align=\"center\" justify=\"center\" className=\"h-full p-8 text-center\">\n <ShieldAlert className=\"size-6 text-muted-foreground\" />\n <Heading level={2} size=\"h3\">\n {translate('users.restrictedTitle')}\n </Heading>\n <Text size=\"sm\" tone=\"muted\" className=\"max-w-sm\">\n {translate('users.restrictedBody')}\n </Text>\n </Stack>\n )\n}\n\n/** The loaded surface: the portable list (or a member's detail) over the users source. */\nfunction UsersSurface({ projectId, userId }: { projectId: string; userId: string | null }) {\n const store = useUsers()\n const source = useUsersCollectionViewSource(projectId)\n\n if (store.status !== 'ready') {\n return (\n <RailStoreStatus\n store={store}\n errorLabel={translate('users.loadFailed')}\n loadingLabel={translate('users.loading')}\n errorTestId=\"users-error\"\n />\n )\n }\n\n // A member's detail opens in the portable edit shell (AD-19); the list otherwise.\n // The shell mounts its own CollectionViewProvider, so only the list wraps here.\n if (userId !== null) {\n return <PortableDetailEditor source={source} projectId={projectId} documentId={userId} />\n }\n\n return (\n <Stack gap=\"none\" className=\"h-full min-h-0\">\n {/* EC-324: invite-based onboarding — the admin action that replaces open sign-up. */}\n <InviteUserBar projectId={projectId} />\n <div className=\"min-h-0 flex-1\">\n <CollectionViewProvider source={source}>\n <LoadedCollectionTable projectId={projectId} />\n </CollectionViewProvider>\n </div>\n </Stack>\n )\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { CollectionDef, DocumentTitleFn } from '@elytracms/core/cms-core';
2
+ import type { RolePolicy } from '@elytracms/core/persistence';
2
3
  /**
3
4
  * EC-396 — the display-title fallback shared by the users rail and the account lens
4
5
  * (`selfAccountCollectionDef`): a member's **name**, else their **email** — never the
@@ -23,7 +24,15 @@ export declare const userTitleFallback: DocumentTitleFn;
23
24
  * via account settings. A v1 default; a project may extend the field set later.
24
25
  */
25
26
  export declare const USER_ROLE_OPTIONS: {
26
- value: import("@elytracms/core/persistence").ProjectRole;
27
+ value: import("@elytracms/core/roles").ProjectRole;
27
28
  label: string;
28
29
  }[];
30
+ /**
31
+ * EC-605 — the users lens for a project's role policy: the `role` select lists the
32
+ * project's DECLARED roles with their labels (strongest first, `roleOptions`), never the
33
+ * built-in three when the project declares others. Built once per policy object (the
34
+ * resolved project holds one), so the lens is referentially stable across renders.
35
+ */
36
+ export declare function usersCollectionDefFor(policy: RolePolicy): CollectionDef;
37
+ /** The users lens under the DEFAULT role policy (a project declaring no `roles`). */
29
38
  export declare const usersCollectionDef: CollectionDef;
@@ -1,6 +1,6 @@
1
1
  import { collectionDefSchema } from '@elytracms/core/cms-core';
2
- import { roleOptions } from '@elytracms/core/persistence';
3
- import { msgLabel } from '@elytracms/studio/lib/i18n';
2
+ import { defaultRolePolicy, roleOptions } from '@elytracms/core/persistence';
3
+ import { msgLabel } from '../../lib/i18n/index.js';
4
4
  /**
5
5
  * EC-396 — the display-title fallback shared by the users rail and the account lens
6
6
  * (`selfAccountCollectionDef`): a member's **name**, else their **email** — never the
@@ -30,28 +30,48 @@ export const userTitleFallback = (values) => {
30
30
  * via account settings. A v1 default; a project may extend the field set later.
31
31
  */
32
32
  // Derived from the ONE role policy (EC-240c) — no hardcoded duplicate of the role
33
- // set. Reflects the shipped default today; a project's custom policy flows through.
33
+ // set. The DEFAULT policy's options; the rail lists its project's own declared roles
34
+ // through `usersCollectionDefFor` (EC-605).
34
35
  export const USER_ROLE_OPTIONS = roleOptions();
35
- export const usersCollectionDef = collectionDefSchema.parse({
36
- id: 'user',
37
- // Users aren't a CMS `kind`; 'document' just satisfies the lens schema. The
38
- // source's capabilities (adminOnly, no publish/versions/create) carry the semantics.
39
- kind: 'document',
40
- // EC-396: name → email → id (never the raw id when an email exists).
41
- titleField: userTitleFallback,
42
- // EC-241: built-in rail labels resolve through the message catalog (msgLabel) — a
43
- // consumer's translation catalog localizes them without a per-rail override.
44
- form: { label: msgLabel('users.rail.label'), labelSingular: msgLabel('users.rail.labelSingular') },
45
- fields: [
46
- { name: 'name', type: 'text', form: { label: msgLabel('users.field.name.label'), readOnly: true }, filterable: true },
47
- { name: 'email', type: 'text', form: { label: msgLabel('users.field.email.label'), readOnly: true }, filterable: true },
48
- {
49
- name: 'role',
50
- type: 'select',
51
- options: [...USER_ROLE_OPTIONS],
52
- filterable: true,
53
- form: { label: msgLabel('users.field.role.label') },
54
- },
55
- ],
56
- });
36
+ const usersDefs = new WeakMap();
37
+ /**
38
+ * EC-605 — the users lens for a project's role policy: the `role` select lists the
39
+ * project's DECLARED roles with their labels (strongest first, `roleOptions`), never the
40
+ * built-in three when the project declares others. Built once per policy object (the
41
+ * resolved project holds one), so the lens is referentially stable across renders.
42
+ */
43
+ export function usersCollectionDefFor(policy) {
44
+ let def = usersDefs.get(policy);
45
+ if (!def) {
46
+ def = buildUsersCollectionDef(roleOptions(policy));
47
+ usersDefs.set(policy, def);
48
+ }
49
+ return def;
50
+ }
51
+ function buildUsersCollectionDef(options) {
52
+ return collectionDefSchema.parse({
53
+ id: 'user',
54
+ // Users aren't a CMS `kind`; 'document' just satisfies the lens schema. The
55
+ // source's capabilities (adminOnly, no publish/versions/create) carry the semantics.
56
+ kind: 'document',
57
+ // EC-396: name → email → id (never the raw id when an email exists).
58
+ titleField: userTitleFallback,
59
+ // EC-241: built-in rail labels resolve through the message catalog (msgLabel) — a
60
+ // consumer's translation catalog localizes them without a per-rail override.
61
+ form: { label: msgLabel('users.rail.label'), labelSingular: msgLabel('users.rail.labelSingular') },
62
+ fields: [
63
+ { name: 'name', type: 'text', form: { label: msgLabel('users.field.name.label'), readOnly: true }, filterable: true },
64
+ { name: 'email', type: 'text', form: { label: msgLabel('users.field.email.label'), readOnly: true }, filterable: true },
65
+ {
66
+ name: 'role',
67
+ type: 'select',
68
+ options: [...options],
69
+ filterable: true,
70
+ form: { label: msgLabel('users.field.role.label') },
71
+ },
72
+ ],
73
+ });
74
+ }
75
+ /** The users lens under the DEFAULT role policy (a project declaring no `roles`). */
76
+ export const usersCollectionDef = usersCollectionDefFor(defaultRolePolicy);
57
77
  //# sourceMappingURL=users-schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"users-schema.js","sourceRoot":"","sources":["../../../src/components/users/users-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAErD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAoB,CAAC,MAAM,EAAE,EAAE;IAC3D,MAAM,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IACtE,IAAI,IAAI;QAAE,OAAO,IAAI,CAAA;IACrB,OAAO,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AACpE,CAAC,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,kFAAkF;AAClF,oFAAoF;AACpF,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,EAAE,CAAA;AAE9C,MAAM,CAAC,MAAM,kBAAkB,GAAkB,mBAAmB,CAAC,KAAK,CAAC;IACzE,EAAE,EAAE,MAAM;IACV,4EAA4E;IAC5E,qFAAqF;IACrF,IAAI,EAAE,UAAU;IAChB,qEAAqE;IACrE,UAAU,EAAE,iBAAiB;IAC7B,kFAAkF;IAClF,6EAA6E;IAC7E,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,0BAA0B,CAAC,EAAE;IAClG,MAAM,EAAE;QACN,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,wBAAwB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;QACrH,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,yBAAyB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;QACvH;YACE,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,CAAC,GAAG,iBAAiB,CAAC;YAC/B,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,wBAAwB,CAAC,EAAE;SACpD;KACF;CACF,CAAC,CAAA","sourcesContent":["import { collectionDefSchema } from '@elytracms/core/cms-core'\nimport type { CollectionDef, DocumentTitleFn } from '@elytracms/core/cms-core'\nimport { roleOptions } from '@elytracms/core/persistence'\nimport { msgLabel } from '@elytracms/studio/lib/i18n'\n\n/**\n * EC-396 — the display-title fallback shared by the users rail and the account lens\n * (`selfAccountCollectionDef`): a member's **name**, else their **email** — never the\n * raw internal id when an email exists. A function-pattern `titleField` (config-as-code,\n * studio-evaluated), so list rows, breadcrumbs, relation labels, and the account header\n * resolve identity the same way. Returns `''` when NEITHER is present, so\n * `resolveDocumentTitle` degrades to the document id (its `fallbackId`) — the only case a\n * raw id ever surfaces.\n */\nexport const userTitleFallback: DocumentTitleFn = (values) => {\n const name = typeof values.name === 'string' ? values.name.trim() : ''\n if (name) return name\n return typeof values.email === 'string' ? values.email.trim() : ''\n}\n\n/**\n * The built-in **users** field lens (EC-240, AD-19). Users are a separate rail —\n * NOT a `cmsDocuments` collection (`collectionKindSchema` is `document|asset`) —\n * so this `CollectionDef` exists only to drive the portable list/edit surfaces\n * over the users {@link CollectionViewSource}. The rail's *user-ness* comes from\n * the source's capabilities (admin-only, no publish/versions/create), never the\n * schema `kind`.\n *\n * Identity fields (`name`, `email`) are **auth-owned** and read-only here — an\n * admin manages a member's *role* (and, later, project-declared custom fields\n * over the `meta` bag, mirroring assets); the person edits their own name/email\n * via account settings. A v1 default; a project may extend the field set later.\n */\n// Derived from the ONE role policy (EC-240c) — no hardcoded duplicate of the role\n// set. Reflects the shipped default today; a project's custom policy flows through.\nexport const USER_ROLE_OPTIONS = roleOptions()\n\nexport const usersCollectionDef: CollectionDef = collectionDefSchema.parse({\n id: 'user',\n // Users aren't a CMS `kind`; 'document' just satisfies the lens schema. The\n // source's capabilities (adminOnly, no publish/versions/create) carry the semantics.\n kind: 'document',\n // EC-396: name → email → id (never the raw id when an email exists).\n titleField: userTitleFallback,\n // EC-241: built-in rail labels resolve through the message catalog (msgLabel) — a\n // consumer's translation catalog localizes them without a per-rail override.\n form: { label: msgLabel('users.rail.label'), labelSingular: msgLabel('users.rail.labelSingular') },\n fields: [\n { name: 'name', type: 'text', form: { label: msgLabel('users.field.name.label'), readOnly: true }, filterable: true },\n { name: 'email', type: 'text', form: { label: msgLabel('users.field.email.label'), readOnly: true }, filterable: true },\n {\n name: 'role',\n type: 'select',\n options: [...USER_ROLE_OPTIONS],\n filterable: true,\n form: { label: msgLabel('users.field.role.label') },\n },\n ],\n})\n"]}
1
+ {"version":3,"file":"users-schema.js","sourceRoot":"","sources":["../../../src/components/users/users-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAE9D,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAE5E,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAoB,CAAC,MAAM,EAAE,EAAE;IAC3D,MAAM,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IACtE,IAAI,IAAI;QAAE,OAAO,IAAI,CAAA;IACrB,OAAO,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AACpE,CAAC,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,kFAAkF;AAClF,qFAAqF;AACrF,4CAA4C;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,EAAE,CAAA;AAE9C,MAAM,SAAS,GAAG,IAAI,OAAO,EAA6B,CAAA;AAE1D;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAkB;IACtD,IAAI,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,GAAG,GAAG,uBAAuB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAA;QAClD,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,uBAAuB,CAAC,OAA2C;IAC1E,OAAO,mBAAmB,CAAC,KAAK,CAAC;QAC/B,EAAE,EAAE,MAAM;QACV,4EAA4E;QAC5E,qFAAqF;QACrF,IAAI,EAAE,UAAU;QAChB,qEAAqE;QACrE,UAAU,EAAE,iBAAiB;QAC7B,kFAAkF;QAClF,6EAA6E;QAC7E,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,0BAA0B,CAAC,EAAE;QAClG,MAAM,EAAE;YACN,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,wBAAwB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YACrH,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,yBAAyB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YACvH;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC;gBACrB,UAAU,EAAE,IAAI;gBAChB,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,wBAAwB,CAAC,EAAE;aACpD;SACF;KACF,CAAC,CAAA;AACJ,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,MAAM,kBAAkB,GAAkB,qBAAqB,CAAC,iBAAiB,CAAC,CAAA","sourcesContent":["import { collectionDefSchema } from '@elytracms/core/cms-core'\nimport type { CollectionDef, DocumentTitleFn } from '@elytracms/core/cms-core'\nimport { defaultRolePolicy, roleOptions } from '@elytracms/core/persistence'\nimport type { RolePolicy } from '@elytracms/core/persistence'\nimport { msgLabel } from '../../lib/i18n'\n\n/**\n * EC-396 — the display-title fallback shared by the users rail and the account lens\n * (`selfAccountCollectionDef`): a member's **name**, else their **email** — never the\n * raw internal id when an email exists. A function-pattern `titleField` (config-as-code,\n * studio-evaluated), so list rows, breadcrumbs, relation labels, and the account header\n * resolve identity the same way. Returns `''` when NEITHER is present, so\n * `resolveDocumentTitle` degrades to the document id (its `fallbackId`) — the only case a\n * raw id ever surfaces.\n */\nexport const userTitleFallback: DocumentTitleFn = (values) => {\n const name = typeof values.name === 'string' ? values.name.trim() : ''\n if (name) return name\n return typeof values.email === 'string' ? values.email.trim() : ''\n}\n\n/**\n * The built-in **users** field lens (EC-240, AD-19). Users are a separate rail —\n * NOT a `cmsDocuments` collection (`collectionKindSchema` is `document|asset`) —\n * so this `CollectionDef` exists only to drive the portable list/edit surfaces\n * over the users {@link CollectionViewSource}. The rail's *user-ness* comes from\n * the source's capabilities (admin-only, no publish/versions/create), never the\n * schema `kind`.\n *\n * Identity fields (`name`, `email`) are **auth-owned** and read-only here — an\n * admin manages a member's *role* (and, later, project-declared custom fields\n * over the `meta` bag, mirroring assets); the person edits their own name/email\n * via account settings. A v1 default; a project may extend the field set later.\n */\n// Derived from the ONE role policy (EC-240c) — no hardcoded duplicate of the role\n// set. The DEFAULT policy's options; the rail lists its project's own declared roles\n// through `usersCollectionDefFor` (EC-605).\nexport const USER_ROLE_OPTIONS = roleOptions()\n\nconst usersDefs = new WeakMap<RolePolicy, CollectionDef>()\n\n/**\n * EC-605 — the users lens for a project's role policy: the `role` select lists the\n * project's DECLARED roles with their labels (strongest first, `roleOptions`), never the\n * built-in three when the project declares others. Built once per policy object (the\n * resolved project holds one), so the lens is referentially stable across renders.\n */\nexport function usersCollectionDefFor(policy: RolePolicy): CollectionDef {\n let def = usersDefs.get(policy)\n if (!def) {\n def = buildUsersCollectionDef(roleOptions(policy))\n usersDefs.set(policy, def)\n }\n return def\n}\n\nfunction buildUsersCollectionDef(options: { value: string; label: string }[]): CollectionDef {\n return collectionDefSchema.parse({\n id: 'user',\n // Users aren't a CMS `kind`; 'document' just satisfies the lens schema. The\n // source's capabilities (adminOnly, no publish/versions/create) carry the semantics.\n kind: 'document',\n // EC-396: name → email → id (never the raw id when an email exists).\n titleField: userTitleFallback,\n // EC-241: built-in rail labels resolve through the message catalog (msgLabel) — a\n // consumer's translation catalog localizes them without a per-rail override.\n form: { label: msgLabel('users.rail.label'), labelSingular: msgLabel('users.rail.labelSingular') },\n fields: [\n { name: 'name', type: 'text', form: { label: msgLabel('users.field.name.label'), readOnly: true }, filterable: true },\n { name: 'email', type: 'text', form: { label: msgLabel('users.field.email.label'), readOnly: true }, filterable: true },\n {\n name: 'role',\n type: 'select',\n options: [...options],\n filterable: true,\n form: { label: msgLabel('users.field.role.label') },\n },\n ],\n })\n}\n\n/** The users lens under the DEFAULT role policy (a project declaring no `roles`). */\nexport const usersCollectionDef: CollectionDef = usersCollectionDefFor(defaultRolePolicy)\n"]}
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useMemo } from 'react';
3
- import { getStudioOperations } from '@elytracms/studio/lib/operations';
4
- import { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query';
5
- import { createRequiredContext } from '@elytracms/studio/lib/required-context';
3
+ import { getStudioOperations } from '../../lib/operations/index.js';
4
+ import { listStoreState, useOperationCache, useOperationQuery } from '../../lib/query/index.js';
5
+ import { createRequiredContext } from '../../lib/required-context.js';
6
6
  const { Context: UsersContext, useRequired: useUsers } = createRequiredContext('Users');
7
7
  const EMPTY_USERS = [];
8
8
  /** Cache key for a project's users rail (shared by the query and its patches). */
@@ -1 +1 @@
1
- {"version":3,"file":"users-store.js","sourceRoot":"","sources":["../../../src/components/users/users-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type { ReactNode } from 'react'\nimport type { ProjectRole, SaveUserInput, UserRecord } from '@elytracms/core/persistence'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport type { OperationsClient } from '@elytracms/studio/lib/operations'\nimport { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\n\n/**\n * Studio users store (EC-240, 240b). The users rail — role + custom fields\n * joined to auth identity — read + written through the shared operations client\n * (`users.*` commands: validate → authorize → mutate → event), the same seam the\n * assets store uses. Admin-only: every `users.*` operation is gated at the\n * chokepoint AND server-side, so a non-admin session sees an authorization error\n * rather than data. Mirrors `assets-store.tsx`, minus the blob/upload machinery\n * (users have no bytes).\n */\n\nexport interface UsersStore {\n /** The active project (deployment) whose members this rail manages. */\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n users: readonly UserRecord[]\n /** Grant/modify a user's role(s) + custom fields (admin-only). */\n save(input: SaveUserInput): Promise<{ ok: true; user: UserRecord } | { ok: false; message: string }>\n /** Revoke a user's studio membership (does not delete the auth identity). */\n remove(userId: string): Promise<{ ok: true } | { ok: false; message: string }>\n /** Re-list from the backend (the public escape hatch). */\n refresh(): Promise<void>\n}\n\nconst { Context: UsersContext, useRequired: useUsers } = createRequiredContext<UsersStore>('Users')\n\nconst EMPTY_USERS: readonly UserRecord[] = []\n\n/** Cache key for a project's users rail (shared by the query and its patches). */\nfunction usersQueryKey(projectId: string): readonly [string, string] {\n return ['users', projectId]\n}\n\n/** Load a project's studio users (pure over the operations client). A list\n * failure throws so the query surfaces it as an error status. */\nasync function loadUsers(operations: OperationsClient, projectId: string): Promise<UserRecord[]> {\n const result = await operations.users.list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n}\n\nexport function UsersProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n // The users list, read through the one operations-backed query cache\n // (EC-308/AD-17). This provider is the sole writer of its project's users, so\n // its own mutations patch the cache in place rather than re-listing.\n const query = useOperationQuery<UserRecord[]>({\n queryKey: usersQueryKey(projectId),\n loader: (operations) => loadUsers(operations, projectId),\n })\n const cache = useOperationCache()\n const refetch = query.refetch\n const users = query.data ?? EMPTY_USERS\n const { status, errorMessage } = listStoreState(query)\n\n const refresh = useCallback(async () => {\n await refetch()\n }, [refetch])\n\n const save = useCallback<UsersStore['save']>(\n async (input) => {\n // Writes go through the `users.save` command via the shared operations\n // client (EC-141) — validate → authorize (admin) → mutate → event.\n const { operations } = await getStudioOperations()\n const result = await operations.users.save({\n projectId,\n userId: input.id,\n // 240a bridge: the contract's `roles: string[]` is permissive (240c-ready);\n // the operation input constrains it to the current enum + re-validates at\n // runtime. The cast crosses that permissive→strict seam.\n roles: input.roles as ProjectRole[],\n ...(input.meta !== undefined ? { meta: input.meta } : {}),\n })\n if (!result.ok) return { ok: false, message: result.error.message }\n // Patch the one updated record into the cache rather than re-listing.\n const saved = result.value\n cache.patch<UserRecord[]>(usersQueryKey(projectId), (prev) => {\n const rest = (prev ?? []).filter((u) => u.id !== saved.id)\n return [...rest, saved].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n })\n return { ok: true, user: saved }\n },\n [projectId, cache],\n )\n\n const remove = useCallback<UsersStore['remove']>(\n async (userId) => {\n const { operations } = await getStudioOperations()\n const result = await operations.users.remove({ projectId, userId })\n if (!result.ok) return { ok: false, message: result.error.message }\n cache.patch<UserRecord[]>(usersQueryKey(projectId), (prev) =>\n (prev ?? []).filter((u) => u.id !== userId),\n )\n return { ok: true }\n },\n [projectId, cache],\n )\n\n const store = useMemo<UsersStore>(\n () => ({\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n users,\n save,\n remove,\n refresh,\n }),\n [projectId, status, errorMessage, users, save, remove, refresh],\n )\n\n return <UsersContext.Provider value={store}>{children}</UsersContext.Provider>\n}\n\nexport { useUsers }\n"]}
1
+ {"version":3,"file":"users-store.js","sourceRoot":"","sources":["../../../src/components/users/users-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type { ReactNode } from 'react'\nimport type { ProjectRole, SaveUserInput, UserRecord } from '@elytracms/core/persistence'\nimport { getStudioOperations } from '../../lib/operations'\nimport type { OperationsClient } from '../../lib/operations'\nimport { listStoreState, useOperationCache, useOperationQuery } from '../../lib/query'\nimport { createRequiredContext } from '../../lib/required-context'\n\n/**\n * Studio users store (EC-240, 240b). The users rail — role + custom fields\n * joined to auth identity — read + written through the shared operations client\n * (`users.*` commands: validate → authorize → mutate → event), the same seam the\n * assets store uses. Admin-only: every `users.*` operation is gated at the\n * chokepoint AND server-side, so a non-admin session sees an authorization error\n * rather than data. Mirrors `assets-store.tsx`, minus the blob/upload machinery\n * (users have no bytes).\n */\n\nexport interface UsersStore {\n /** The active project (deployment) whose members this rail manages. */\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n users: readonly UserRecord[]\n /** Grant/modify a user's role(s) + custom fields (admin-only). */\n save(input: SaveUserInput): Promise<{ ok: true; user: UserRecord } | { ok: false; message: string }>\n /** Revoke a user's studio membership (does not delete the auth identity). */\n remove(userId: string): Promise<{ ok: true } | { ok: false; message: string }>\n /** Re-list from the backend (the public escape hatch). */\n refresh(): Promise<void>\n}\n\nconst { Context: UsersContext, useRequired: useUsers } = createRequiredContext<UsersStore>('Users')\n\nconst EMPTY_USERS: readonly UserRecord[] = []\n\n/** Cache key for a project's users rail (shared by the query and its patches). */\nfunction usersQueryKey(projectId: string): readonly [string, string] {\n return ['users', projectId]\n}\n\n/** Load a project's studio users (pure over the operations client). A list\n * failure throws so the query surfaces it as an error status. */\nasync function loadUsers(operations: OperationsClient, projectId: string): Promise<UserRecord[]> {\n const result = await operations.users.list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n}\n\nexport function UsersProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n // The users list, read through the one operations-backed query cache\n // (EC-308/AD-17). This provider is the sole writer of its project's users, so\n // its own mutations patch the cache in place rather than re-listing.\n const query = useOperationQuery<UserRecord[]>({\n queryKey: usersQueryKey(projectId),\n loader: (operations) => loadUsers(operations, projectId),\n })\n const cache = useOperationCache()\n const refetch = query.refetch\n const users = query.data ?? EMPTY_USERS\n const { status, errorMessage } = listStoreState(query)\n\n const refresh = useCallback(async () => {\n await refetch()\n }, [refetch])\n\n const save = useCallback<UsersStore['save']>(\n async (input) => {\n // Writes go through the `users.save` command via the shared operations\n // client (EC-141) — validate → authorize (admin) → mutate → event.\n const { operations } = await getStudioOperations()\n const result = await operations.users.save({\n projectId,\n userId: input.id,\n // 240a bridge: the contract's `roles: string[]` is permissive (240c-ready);\n // the operation input constrains it to the current enum + re-validates at\n // runtime. The cast crosses that permissive→strict seam.\n roles: input.roles as ProjectRole[],\n ...(input.meta !== undefined ? { meta: input.meta } : {}),\n })\n if (!result.ok) return { ok: false, message: result.error.message }\n // Patch the one updated record into the cache rather than re-listing.\n const saved = result.value\n cache.patch<UserRecord[]>(usersQueryKey(projectId), (prev) => {\n const rest = (prev ?? []).filter((u) => u.id !== saved.id)\n return [...rest, saved].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n })\n return { ok: true, user: saved }\n },\n [projectId, cache],\n )\n\n const remove = useCallback<UsersStore['remove']>(\n async (userId) => {\n const { operations } = await getStudioOperations()\n const result = await operations.users.remove({ projectId, userId })\n if (!result.ok) return { ok: false, message: result.error.message }\n cache.patch<UserRecord[]>(usersQueryKey(projectId), (prev) =>\n (prev ?? []).filter((u) => u.id !== userId),\n )\n return { ok: true }\n },\n [projectId, cache],\n )\n\n const store = useMemo<UsersStore>(\n () => ({\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n users,\n save,\n remove,\n refresh,\n }),\n [projectId, status, errorMessage, users, save, remove, refresh],\n )\n\n return <UsersContext.Provider value={store}>{children}</UsersContext.Provider>\n}\n\nexport { useUsers }\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { Project } from '@elytracms/core/studio-core';
2
- import type { SwitcherEntry, WorkspaceState } from '@elytracms/studio/lib/workspace';
2
+ import type { SwitcherEntry, WorkspaceState } from '../lib/workspace/index.js';
3
3
  /**
4
4
  * Config-driven project source for the switcher + launcher (EC-154, reframed for
5
5
  * EC-179). The decision logic is the pure `selectSwitcherProjects`; this hook
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from 'react';
2
- import { getWorkspaceState, selectSwitcherProjects } from '@elytracms/studio/lib/workspace';
2
+ import { getWorkspaceState, selectSwitcherProjects } from '../lib/workspace/index.js';
3
3
  import { useStudio } from './studio-context.js';
4
4
  /**
5
5
  * Config-driven project source for the switcher + launcher (EC-154, reframed for
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-projects.js","sourceRoot":"","sources":["../../src/components/workspace-projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAA;AAE3F,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAA;IACrC,MAAM,OAAO,GAAG,OAAO,CACrB,GAAG,EAAE,CAAC,sBAAsB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,EACvE,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,CAC7B,CAAA;IACD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;AAC/B,CAAC;AAED,iEAAiE;AACjE,MAAM,qBAAqB,GAAG,CAAC,CAAA;AAE/B;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAiC,EACjC,OAA8B;IAE9B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,CAAA;IAChC,OAAO,OAAO,CACZ,GAAG,EAAE,CACH,MAAM,CAAC,OAAO,CAAC,gBAAgB;SAC5B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;SAClC,MAAM,CAAC,CAAC,CAAC,EAAgB,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC;SAC1D,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SAC7F,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,EACpC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAC3B,CAAA;AACH,CAAC","sourcesContent":["import { useMemo } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport { getWorkspaceState, selectSwitcherProjects } from '@elytracms/studio/lib/workspace'\nimport type { SwitcherEntry, WorkspaceState } from '@elytracms/studio/lib/workspace'\nimport { useStudio } from './studio-context'\n\n/**\n * Config-driven project source for the switcher + launcher (EC-154, reframed for\n * EC-179). The decision logic is the pure `selectSwitcherProjects`; this hook\n * wires it to the studio store (config-derived read model) and the workspace\n * state. Visibility is config-driven and NOT membership-filtered — one project\n * per backend, so access is enforced per-backend on entry (sign-in + role hook),\n * never by hiding a project you would authenticate into.\n */\nexport function useSwitcherEntries(): { entries: SwitcherEntry[]; workspace: WorkspaceState } {\n const studio = useStudio()\n const workspace = getWorkspaceState()\n const entries = useMemo(\n () => selectSwitcherProjects({ workspace, persisted: studio.projects }),\n [workspace, studio.projects],\n )\n return { entries, workspace }\n}\n\n/** How many recent projects the switcher/launcher rails show. */\nconst RECENT_PROJECTS_LIMIT = 4\n\n/**\n * The switcher/launcher's shared \"recent projects\" read model (EC-329):\n * `studio.context.recentProjectIds` → resolved `Project`s → filtered to\n * entries still visible in `entries` (the caller's config-driven switcher\n * entries — a recent project retired from the config must not still show)\n * → capped at {@link RECENT_PROJECTS_LIMIT}. `project-switcher.tsx` passes\n * `exclude` to additionally drop the active project (already shown as\n * current); `project-list.tsx`'s recents rail sits alongside the full grid,\n * so it doesn't exclude anything.\n */\nexport function useRecentProjects(\n entries: readonly SwitcherEntry[],\n options?: { exclude?: string },\n): Project[] {\n const studio = useStudio()\n const exclude = options?.exclude\n return useMemo(\n () =>\n studio.context.recentProjectIds\n .map((id) => studio.getProject(id))\n .filter((p): p is Project => p != null && p.id !== exclude)\n .filter((p) => entries.some((entry) => entry.kind === 'project' && entry.project.id === p.id))\n .slice(0, RECENT_PROJECTS_LIMIT),\n [studio, entries, exclude],\n )\n}\n"]}
1
+ {"version":3,"file":"workspace-projects.js","sourceRoot":"","sources":["../../src/components/workspace-projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAE5E,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAA;IACrC,MAAM,OAAO,GAAG,OAAO,CACrB,GAAG,EAAE,CAAC,sBAAsB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,EACvE,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,CAC7B,CAAA;IACD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;AAC/B,CAAC;AAED,iEAAiE;AACjE,MAAM,qBAAqB,GAAG,CAAC,CAAA;AAE/B;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAiC,EACjC,OAA8B;IAE9B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,CAAA;IAChC,OAAO,OAAO,CACZ,GAAG,EAAE,CACH,MAAM,CAAC,OAAO,CAAC,gBAAgB;SAC5B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;SAClC,MAAM,CAAC,CAAC,CAAC,EAAgB,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC;SAC1D,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SAC7F,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,EACpC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAC3B,CAAA;AACH,CAAC","sourcesContent":["import { useMemo } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport { getWorkspaceState, selectSwitcherProjects } from '../lib/workspace'\nimport type { SwitcherEntry, WorkspaceState } from '../lib/workspace'\nimport { useStudio } from './studio-context'\n\n/**\n * Config-driven project source for the switcher + launcher (EC-154, reframed for\n * EC-179). The decision logic is the pure `selectSwitcherProjects`; this hook\n * wires it to the studio store (config-derived read model) and the workspace\n * state. Visibility is config-driven and NOT membership-filtered — one project\n * per backend, so access is enforced per-backend on entry (sign-in + role hook),\n * never by hiding a project you would authenticate into.\n */\nexport function useSwitcherEntries(): { entries: SwitcherEntry[]; workspace: WorkspaceState } {\n const studio = useStudio()\n const workspace = getWorkspaceState()\n const entries = useMemo(\n () => selectSwitcherProjects({ workspace, persisted: studio.projects }),\n [workspace, studio.projects],\n )\n return { entries, workspace }\n}\n\n/** How many recent projects the switcher/launcher rails show. */\nconst RECENT_PROJECTS_LIMIT = 4\n\n/**\n * The switcher/launcher's shared \"recent projects\" read model (EC-329):\n * `studio.context.recentProjectIds` → resolved `Project`s → filtered to\n * entries still visible in `entries` (the caller's config-driven switcher\n * entries — a recent project retired from the config must not still show)\n * → capped at {@link RECENT_PROJECTS_LIMIT}. `project-switcher.tsx` passes\n * `exclude` to additionally drop the active project (already shown as\n * current); `project-list.tsx`'s recents rail sits alongside the full grid,\n * so it doesn't exclude anything.\n */\nexport function useRecentProjects(\n entries: readonly SwitcherEntry[],\n options?: { exclude?: string },\n): Project[] {\n const studio = useStudio()\n const exclude = options?.exclude\n return useMemo(\n () =>\n studio.context.recentProjectIds\n .map((id) => studio.getProject(id))\n .filter((p): p is Project => p != null && p.id !== exclude)\n .filter((p) => entries.some((entry) => entry.kind === 'project' && entry.project.id === p.id))\n .slice(0, RECENT_PROJECTS_LIMIT),\n [studio, entries, exclude],\n )\n}\n"]}
@@ -18,7 +18,7 @@
18
18
  * `@elytracms/studio/lib/ai/vite` (`createAiDevPlugin`) — because it imports `vite`,
19
19
  * a dev-only tool that must stay out of this barrel's dependency edge.
20
20
  */
21
- export declare const PACKAGE = "@elytracms/studio/lib/ai";
21
+ export declare const PACKAGE = "./index";
22
22
  export * from './types.js';
23
23
  export * from './config.js';
24
24
  export * from './provenance.js';
@@ -18,7 +18,7 @@
18
18
  * `@elytracms/studio/lib/ai/vite` (`createAiDevPlugin`) — because it imports `vite`,
19
19
  * a dev-only tool that must stay out of this barrel's dependency edge.
20
20
  */
21
- export const PACKAGE = '@elytracms/studio/lib/ai';
21
+ export const PACKAGE = './index';
22
22
  export * from './types.js';
23
23
  export * from './config.js';
24
24
  export * from './provenance.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/ai/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,0BAA0B,CAAA;AAEjD,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,SAAS,CAAA;AACvB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,0BAA0B,CAAA;AACxC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA","sourcesContent":["/**\n * `@elytracms/studio/lib/ai` — the headless AI platform seam (EC-331, phase 1).\n *\n * Public surface: the config gate, the provider adapters (Anthropic + Fake), the\n * knowledge mechanism, the target-shape derivation, the `applyContentWithShape`\n * engine, the server-route factory, and the provenance seam. No chat UI here.\n *\n * Bundle discipline: nothing exported here statically imports `ai` / `@ai-sdk/*` —\n * only `anthropic-provider` touches them, via dynamic `import()`. So importing this\n * barrel never eagerly loads the AI SDK, and a studio without an `ai` config block\n * (which only references `StudioAiConfig` as a type) reaches none of it. The same\n * discipline keeps `jose` (the session resolver's JWT library) behind a dynamic\n * `import()` in `convex-session`.\n *\n * EC-371 adds the engine-default session gate + mounting kit: the Convex JWT\n * session resolver (server), the matching client header provider, and the Node\n * `(req, res)` bridge. The Vite DEV mount lives in its own entry —\n * `@elytracms/studio/lib/ai/vite` (`createAiDevPlugin`) — because it imports `vite`,\n * a dev-only tool that must stay out of this barrel's dependency edge.\n */\nexport const PACKAGE = '@elytracms/studio/lib/ai'\n\nexport * from './types'\nexport * from './config'\nexport * from './provenance'\nexport * from './knowledge'\nexport * from './rich-text-input'\nexport * from './constraints'\nexport * from './proposal-validation'\nexport * from './leash'\nexport * from './writer-form'\nexport * from './shape'\nexport * from './apply-content'\nexport * from './update-fields'\nexport * from './anthropic-provider'\nexport * from './gateway-provider'\nexport * from './fake-provider'\nexport * from './route'\nexport * from './route-fetch'\nexport * from './route-provider'\nexport * from './convex-session'\nexport * from './convex-session-headers'\nexport * from './node-handler'\nexport * from './tools'\nexport * from './chat-context'\nexport * from './chat-loop'\nexport * from './chat-state'\nexport * from './rich-text-translate'\nexport * from './translate-document'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/ai/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,SAAS,CAAA;AAEhC,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,SAAS,CAAA;AACvB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,0BAA0B,CAAA;AACxC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA","sourcesContent":["/**\n * `@elytracms/studio/lib/ai` — the headless AI platform seam (EC-331, phase 1).\n *\n * Public surface: the config gate, the provider adapters (Anthropic + Fake), the\n * knowledge mechanism, the target-shape derivation, the `applyContentWithShape`\n * engine, the server-route factory, and the provenance seam. No chat UI here.\n *\n * Bundle discipline: nothing exported here statically imports `ai` / `@ai-sdk/*` —\n * only `anthropic-provider` touches them, via dynamic `import()`. So importing this\n * barrel never eagerly loads the AI SDK, and a studio without an `ai` config block\n * (which only references `StudioAiConfig` as a type) reaches none of it. The same\n * discipline keeps `jose` (the session resolver's JWT library) behind a dynamic\n * `import()` in `convex-session`.\n *\n * EC-371 adds the engine-default session gate + mounting kit: the Convex JWT\n * session resolver (server), the matching client header provider, and the Node\n * `(req, res)` bridge. The Vite DEV mount lives in its own entry —\n * `@elytracms/studio/lib/ai/vite` (`createAiDevPlugin`) — because it imports `vite`,\n * a dev-only tool that must stay out of this barrel's dependency edge.\n */\nexport const PACKAGE = './index'\n\nexport * from './types'\nexport * from './config'\nexport * from './provenance'\nexport * from './knowledge'\nexport * from './rich-text-input'\nexport * from './constraints'\nexport * from './proposal-validation'\nexport * from './leash'\nexport * from './writer-form'\nexport * from './shape'\nexport * from './apply-content'\nexport * from './update-fields'\nexport * from './anthropic-provider'\nexport * from './gateway-provider'\nexport * from './fake-provider'\nexport * from './route'\nexport * from './route-fetch'\nexport * from './route-provider'\nexport * from './convex-session'\nexport * from './convex-session-headers'\nexport * from './node-handler'\nexport * from './tools'\nexport * from './chat-context'\nexport * from './chat-loop'\nexport * from './chat-state'\nexport * from './rich-text-translate'\nexport * from './translate-document'\n"]}
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * ```ts
9
9
  * // api/ai.ts (Vercel filesystem routing beats the SPA catch-all rewrite)
10
- * import { toNodeHandler } from '@elytracms/studio/lib/ai'
10
+ * import { toNodeHandler } from './index'
11
11
  * import { aiRouteHandler } from '../src/server/ai-handler.js'
12
12
  * export default toNodeHandler(aiRouteHandler)
13
13
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"node-handler.js","sourceRoot":"","sources":["../../../src/lib/ai/node-handler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `toNodeHandler` — bridge the framework-agnostic Web `Request → Response`\n * {@link AiRouteHandler} to Node's `(req, res)` signature (EC-371; closes EC-369\n * finding #2, where the identical ~25-line bridge was hand-copied four times across\n * consumers). This is the PROD half of the mounting kit: a Vercel Node function (or\n * any Node server) mounts the route as\n *\n * ```ts\n * // api/ai.ts (Vercel filesystem routing beats the SPA catch-all rewrite)\n * import { toNodeHandler } from '@elytracms/studio/lib/ai'\n * import { aiRouteHandler } from '../src/server/ai-handler.js'\n * export default toNodeHandler(aiRouteHandler)\n * ```\n *\n * The DEV half (`createAiDevPlugin`, Vite middleware + `ssrLoadModule`) reuses this\n * same bridge — one implementation, both mounts. No platform dependency: the classic\n * `(IncomingMessage, ServerResponse)` signature works on every @vercel/node version\n * and on plain `node:http`.\n *\n * EC-334: the RESPONSE side STREAMS — `response.body` reader chunks are piped to\n * `res.write` as they arrive, so the route's native SSE chat protocol delivers\n * token-by-token through every mount (the previous `arrayBuffer()` buffering would\n * have erased token streaming). The REQUEST side stays buffered (the route only\n * ever reads JSON bodies). Ships to every consumer mount — streaming through\n * Vercel Node functions goes on the post-release live-verify checklist.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { AiRouteHandler } from './types'\n\n/** A Node-mountable request handler (Vercel Node function / `node:http` compatible). */\nexport type NodeAiRouteHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>\n\nconst encoder = new TextEncoder()\n\nfunction concatBytes(chunks: Uint8Array[]): Uint8Array<ArrayBuffer> {\n let total = 0\n for (const chunk of chunks) total += chunk.byteLength\n const out = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n out.set(chunk, offset)\n offset += chunk.byteLength\n }\n return out\n}\n\n/** Reconstruct a Web `Request` from a Node request (headers, method, buffered body). */\nasync function toWebRequest(req: IncomingMessage): Promise<Request> {\n const chunks: Uint8Array[] = []\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? encoder.encode(chunk) : (chunk as Uint8Array))\n }\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (typeof value === 'string') headers.set(key, value)\n else if (Array.isArray(value)) headers.set(key, value.join(', '))\n }\n const forwardedProto = req.headers['x-forwarded-proto']\n const proto =\n (typeof forwardedProto === 'string' && forwardedProto.split(',')[0]?.trim()) || 'http'\n return new Request(`${proto}://${req.headers.host ?? 'localhost'}${req.url ?? '/'}`, {\n method: req.method,\n headers,\n body: req.method === 'GET' || req.method === 'HEAD' ? undefined : concatBytes(chunks),\n })\n}\n\n/**\n * Wrap an {@link AiRouteHandler} as a Node `(req, res)` handler: buffer the request\n * body, bridge headers both directions, copy the status back, and STREAM the\n * response body — each chunk is written as it arrives (EC-334), so SSE token\n * streaming survives the bridge. A client disconnect cancels the reader so the\n * handler's stream stops being pulled.\n */\nexport function toNodeHandler(handler: AiRouteHandler): NodeAiRouteHandler {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const request = await toWebRequest(req)\n const response = await handler(request)\n res.statusCode = response.status\n response.headers.forEach((value, key) => res.setHeader(key, value))\n\n const body = response.body\n if (!body) {\n res.end()\n return\n }\n\n const reader = body.getReader()\n let finished = false\n res.on('close', () => {\n // Client gone before the stream ended — stop pulling from the handler.\n if (!finished) void reader.cancel().catch(() => undefined)\n })\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (value !== undefined) res.write(value)\n }\n } catch {\n // Stream error mid-flight (or cancelled by disconnect): terminate below.\n } finally {\n finished = true\n res.end()\n }\n }\n}\n"]}
1
+ {"version":3,"file":"node-handler.js","sourceRoot":"","sources":["../../../src/lib/ai/node-handler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `toNodeHandler` — bridge the framework-agnostic Web `Request → Response`\n * {@link AiRouteHandler} to Node's `(req, res)` signature (EC-371; closes EC-369\n * finding #2, where the identical ~25-line bridge was hand-copied four times across\n * consumers). This is the PROD half of the mounting kit: a Vercel Node function (or\n * any Node server) mounts the route as\n *\n * ```ts\n * // api/ai.ts (Vercel filesystem routing beats the SPA catch-all rewrite)\n * import { toNodeHandler } from './index'\n * import { aiRouteHandler } from '../src/server/ai-handler.js'\n * export default toNodeHandler(aiRouteHandler)\n * ```\n *\n * The DEV half (`createAiDevPlugin`, Vite middleware + `ssrLoadModule`) reuses this\n * same bridge — one implementation, both mounts. No platform dependency: the classic\n * `(IncomingMessage, ServerResponse)` signature works on every @vercel/node version\n * and on plain `node:http`.\n *\n * EC-334: the RESPONSE side STREAMS — `response.body` reader chunks are piped to\n * `res.write` as they arrive, so the route's native SSE chat protocol delivers\n * token-by-token through every mount (the previous `arrayBuffer()` buffering would\n * have erased token streaming). The REQUEST side stays buffered (the route only\n * ever reads JSON bodies). Ships to every consumer mount — streaming through\n * Vercel Node functions goes on the post-release live-verify checklist.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { AiRouteHandler } from './types'\n\n/** A Node-mountable request handler (Vercel Node function / `node:http` compatible). */\nexport type NodeAiRouteHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>\n\nconst encoder = new TextEncoder()\n\nfunction concatBytes(chunks: Uint8Array[]): Uint8Array<ArrayBuffer> {\n let total = 0\n for (const chunk of chunks) total += chunk.byteLength\n const out = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n out.set(chunk, offset)\n offset += chunk.byteLength\n }\n return out\n}\n\n/** Reconstruct a Web `Request` from a Node request (headers, method, buffered body). */\nasync function toWebRequest(req: IncomingMessage): Promise<Request> {\n const chunks: Uint8Array[] = []\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? encoder.encode(chunk) : (chunk as Uint8Array))\n }\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (typeof value === 'string') headers.set(key, value)\n else if (Array.isArray(value)) headers.set(key, value.join(', '))\n }\n const forwardedProto = req.headers['x-forwarded-proto']\n const proto =\n (typeof forwardedProto === 'string' && forwardedProto.split(',')[0]?.trim()) || 'http'\n return new Request(`${proto}://${req.headers.host ?? 'localhost'}${req.url ?? '/'}`, {\n method: req.method,\n headers,\n body: req.method === 'GET' || req.method === 'HEAD' ? undefined : concatBytes(chunks),\n })\n}\n\n/**\n * Wrap an {@link AiRouteHandler} as a Node `(req, res)` handler: buffer the request\n * body, bridge headers both directions, copy the status back, and STREAM the\n * response body — each chunk is written as it arrives (EC-334), so SSE token\n * streaming survives the bridge. A client disconnect cancels the reader so the\n * handler's stream stops being pulled.\n */\nexport function toNodeHandler(handler: AiRouteHandler): NodeAiRouteHandler {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const request = await toWebRequest(req)\n const response = await handler(request)\n res.statusCode = response.status\n response.headers.forEach((value, key) => res.setHeader(key, value))\n\n const body = response.body\n if (!body) {\n res.end()\n return\n }\n\n const reader = body.getReader()\n let finished = false\n res.on('close', () => {\n // Client gone before the stream ended — stop pulling from the handler.\n if (!finished) void reader.cancel().catch(() => undefined)\n })\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (value !== undefined) res.write(value)\n }\n } catch {\n // Stream error mid-flight (or cancelled by disconnect): terminate below.\n } finally {\n finished = true\n res.end()\n }\n }\n}\n"]}
@@ -41,7 +41,7 @@
41
41
  import { autoDeriveSlug, createCollectionRegistry, fieldValuePosition, isFieldLocalized, slugFieldOf, validateDocument, valuesInLocale, writeField, } from '@elytracms/core/cms-core';
42
42
  import { introducedErrors, warningsOf } from './proposal-validation.js';
43
43
  import { fieldWritePatch } from '../cms/field-patch.js';
44
- import { fieldConnection, hasLocaleVariant } from '@elytracms/studio/lib/locale';
44
+ import { fieldConnection, hasLocaleVariant } from '../locale/index.js';
45
45
  import { assembleKnowledgeBlock } from './knowledge.js';
46
46
  import { aiProvenance } from './provenance.js';
47
47
  import { extractRichTextUnits, rebuildRichTextReport } from './rich-text-translate.js';