@elytracms/studio 0.0.52 → 0.0.54

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 (321) hide show
  1. package/dist/components/account/account-settings.js +4 -4
  2. package/dist/components/account/credential-panel.js +1 -1
  3. package/dist/components/account/self-account-view-source.js +3 -3
  4. package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
  5. package/dist/components/ai/ai-chat-panel.js +4 -4
  6. package/dist/components/ai/ai-cms-panel.js +1 -1
  7. package/dist/components/ai/proposal-card.js +4 -4
  8. package/dist/components/ai/translate-document-action.js +1 -1
  9. package/dist/components/ai/translate-document-dialog.d.ts +1 -1
  10. package/dist/components/app-views/default-modules.d.ts +3 -3
  11. package/dist/components/app-views/default-modules.js +20 -20
  12. package/dist/components/app-views/sidebar.d.ts +1 -1
  13. package/dist/components/app-views/sidebar.js +2 -2
  14. package/dist/components/app-views/studio-config.d.ts +1 -1
  15. package/dist/components/assets/asset-detail.js +9 -9
  16. package/dist/components/assets/asset-grid.js +7 -7
  17. package/dist/components/assets/asset-thumb.d.ts +1 -1
  18. package/dist/components/assets/asset-thumb.js +2 -2
  19. package/dist/components/assets/assets-collection-view-source.js +3 -3
  20. package/dist/components/assets/assets-module.js +8 -8
  21. package/dist/components/assets/assets-store.d.ts +1 -1
  22. package/dist/components/assets/assets-store.js +1 -1
  23. package/dist/components/builder/blocks-model.js +1 -1
  24. package/dist/components/builder/blocks-panel.d.ts +1 -1
  25. package/dist/components/builder/blocks-panel.js +6 -6
  26. package/dist/components/builder/canvas.js +11 -11
  27. package/dist/components/builder/clipboard-actions.js +2 -2
  28. package/dist/components/builder/composition-field-editor-lazy.d.ts +1 -1
  29. package/dist/components/builder/composition-field-editor-lazy.js +1 -1
  30. package/dist/components/builder/composition-field-editor.d.ts +1 -1
  31. package/dist/components/builder/composition-field-editor.js +9 -9
  32. package/dist/components/builder/insert-context.d.ts +2 -2
  33. package/dist/components/builder/insert-context.js +2 -2
  34. package/dist/components/builder/insert-menu.d.ts +2 -2
  35. package/dist/components/builder/insert-menu.js +1 -1
  36. package/dist/components/builder/layout-inspector.js +1 -1
  37. package/dist/components/builder/page-tree.js +5 -5
  38. package/dist/components/builder/property-inspector.js +3 -3
  39. package/dist/components/builder/reuse-context.d.ts +1 -1
  40. package/dist/components/builder/reuse-context.js +3 -3
  41. package/dist/components/builder/spacing-handles.js +2 -2
  42. package/dist/components/cms/asset-picker.js +4 -4
  43. package/dist/components/cms/bulk-actions.js +1 -1
  44. package/dist/components/cms/bulk-bar.d.ts +1 -1
  45. package/dist/components/cms/bulk-bar.js +3 -3
  46. package/dist/components/cms/cell-edit.js +1 -1
  47. package/dist/components/cms/cell.js +2 -2
  48. package/dist/components/cms/cms-index.js +1 -1
  49. package/dist/components/cms/cms-store.d.ts +2 -2
  50. package/dist/components/cms/cms-store.js +6 -6
  51. package/dist/components/cms/collection-table.js +17 -17
  52. package/dist/components/cms/detail-editor.js +31 -31
  53. package/dist/components/cms/document-diff-view.js +4 -4
  54. package/dist/components/cms/editable-cell.d.ts +1 -1
  55. package/dist/components/cms/editable-cell.js +6 -6
  56. package/dist/components/cms/field-control.js +15 -15
  57. package/dist/components/cms/field-renderer.d.ts +2 -2
  58. package/dist/components/cms/filter-builder.js +3 -3
  59. package/dist/components/cms/import-export.js +1 -1
  60. package/dist/components/cms/issues-pane.js +1 -1
  61. package/dist/components/cms/prop-fields.js +3 -3
  62. package/dist/components/cms/reference-confirm.js +1 -1
  63. package/dist/components/cms/relation-picker.js +1 -1
  64. package/dist/components/cms/rich-text/block-commands.d.ts +1 -1
  65. package/dist/components/cms/rich-text/component-embed.js +2 -2
  66. package/dist/components/cms/rich-text/editor.js +8 -8
  67. package/dist/components/cms/rich-text/embed-inspector.d.ts +1 -1
  68. package/dist/components/cms/rich-text/embed-inspector.js +2 -2
  69. package/dist/components/cms/rich-text/embed-picker.js +1 -1
  70. package/dist/components/cms/rich-text/embed-props.js +1 -1
  71. package/dist/components/cms/rich-text/extensions.d.ts +1 -1
  72. package/dist/components/cms/rich-text/extensions.js +4 -4
  73. package/dist/components/cms/rich-text/index.d.ts +1 -1
  74. package/dist/components/cms/rich-text/index.js +1 -1
  75. package/dist/components/cms/rich-text/insert.d.ts +1 -1
  76. package/dist/components/cms/rich-text/lazy.d.ts +1 -1
  77. package/dist/components/cms/rich-text/lazy.js +1 -1
  78. package/dist/components/cms/rich-text/slash-command.js +1 -1
  79. package/dist/components/cms/rich-text/toolbar.d.ts +1 -1
  80. package/dist/components/cms/rich-text/toolbar.js +4 -4
  81. package/dist/components/cms/use-cell-edit.d.ts +1 -1
  82. package/dist/components/cms/use-cell-edit.js +2 -2
  83. package/dist/components/cms/use-compare-selection.d.ts +1 -1
  84. package/dist/components/cms/use-discard-controller.d.ts +1 -1
  85. package/dist/components/cms/use-discard-controller.js +2 -2
  86. package/dist/components/cms/use-document-versions.d.ts +1 -1
  87. package/dist/components/cms/use-document-versions.js +1 -1
  88. package/dist/components/cms/use-embed-inspector-host.d.ts +1 -1
  89. package/dist/components/cms/use-published-perspective.d.ts +2 -2
  90. package/dist/components/cms/use-published-perspective.js +2 -2
  91. package/dist/components/cms/use-restore-version.js +1 -1
  92. package/dist/components/cms/version-compare-lazy.js +1 -1
  93. package/dist/components/cms/version-compare.js +6 -6
  94. package/dist/components/cms/versions-model.d.ts +1 -1
  95. package/dist/components/cms/versions-pane.js +5 -5
  96. package/dist/components/cms/view-bar.js +2 -2
  97. package/dist/components/collab/collab-context.js +1 -1
  98. package/dist/components/collection-view/cms-collection-view-source.js +2 -2
  99. package/dist/components/components/component-preview.js +3 -3
  100. package/dist/components/components/components-index.js +2 -2
  101. package/dist/components/components/registry-browser.js +2 -2
  102. package/dist/components/components/transfer-dialog.js +5 -5
  103. package/dist/components/components/transfer.js +4 -4
  104. package/dist/components/current-user-menu.js +2 -2
  105. package/dist/components/delivery-health-banner.js +2 -2
  106. package/dist/components/operation-error.js +1 -1
  107. package/dist/components/project-list.js +5 -5
  108. package/dist/components/project-session.js +1 -1
  109. package/dist/components/project-switcher.js +3 -3
  110. package/dist/components/rail-store-status.js +1 -1
  111. package/dist/components/redirects/redirects-collection-view-source.js +3 -3
  112. package/dist/components/redirects/redirects-module.js +6 -6
  113. package/dist/components/section-nav.js +3 -3
  114. package/dist/components/section-surface.js +1 -1
  115. package/dist/components/studio-project-screen.js +7 -7
  116. package/dist/components/studio-shell.js +9 -9
  117. package/dist/components/use-hydration-phase.js +1 -1
  118. package/dist/components/users/users-collection-view-source.js +4 -4
  119. package/dist/components/users/users-module.js +9 -9
  120. package/dist/components/workspace-projects.js +1 -1
  121. package/dist/customization.d.ts +9 -9
  122. package/dist/customization.js +5 -5
  123. package/dist/lib/ai/anthropic-provider.d.ts +1 -1
  124. package/dist/lib/ai/apply-content.d.ts +1 -1
  125. package/dist/lib/ai/apply-content.js +3 -3
  126. package/dist/lib/ai/chat-state.d.ts +2 -2
  127. package/dist/lib/ai/config.d.ts +1 -1
  128. package/dist/lib/ai/convex-session-headers.d.ts +68 -0
  129. package/dist/lib/ai/convex-session-headers.js +79 -0
  130. package/dist/lib/ai/convex-session-headers.js.map +1 -0
  131. package/dist/lib/ai/convex-session.d.ts +82 -0
  132. package/dist/lib/ai/convex-session.js +92 -0
  133. package/dist/lib/ai/convex-session.js.map +1 -0
  134. package/dist/lib/ai/create-flow.d.ts +2 -2
  135. package/dist/lib/ai/create-flow.js +3 -3
  136. package/dist/lib/ai/fake-provider.d.ts +1 -1
  137. package/dist/lib/ai/index.d.ts +27 -16
  138. package/dist/lib/ai/index.js +27 -16
  139. package/dist/lib/ai/index.js.map +1 -1
  140. package/dist/lib/ai/knowledge.d.ts +1 -1
  141. package/dist/lib/ai/node-handler.d.ts +29 -0
  142. package/dist/lib/ai/node-handler.js +48 -0
  143. package/dist/lib/ai/node-handler.js.map +1 -0
  144. package/dist/lib/ai/provenance.d.ts +1 -1
  145. package/dist/lib/ai/route-provider.d.ts +1 -1
  146. package/dist/lib/ai/route.d.ts +1 -1
  147. package/dist/lib/ai/shape.d.ts +1 -1
  148. package/dist/lib/ai/translate-document.d.ts +1 -1
  149. package/dist/lib/ai/translate-document.js +3 -3
  150. package/dist/lib/ai/vite.d.ts +32 -0
  151. package/dist/lib/ai/vite.js +97 -0
  152. package/dist/lib/ai/vite.js.map +1 -0
  153. package/dist/lib/ai-panel.js +1 -1
  154. package/dist/lib/assets/index.d.ts +4 -4
  155. package/dist/lib/assets/index.js +4 -4
  156. package/dist/lib/assets/upload.d.ts +2 -2
  157. package/dist/lib/assets/upload.js +2 -2
  158. package/dist/lib/auth/convex-auth-adapter.d.ts +2 -2
  159. package/dist/lib/auth/convex-auth-adapter.js +1 -1
  160. package/dist/lib/auth/dev-auto-login-adapter.d.ts +1 -1
  161. package/dist/lib/auth/index.d.ts +9 -9
  162. package/dist/lib/auth/index.js +10 -10
  163. package/dist/lib/auth/local-auth-adapter.d.ts +1 -1
  164. package/dist/lib/auth/route-guard.d.ts +1 -1
  165. package/dist/lib/auth-errors.d.ts +1 -1
  166. package/dist/lib/clipboard/index.d.ts +3 -3
  167. package/dist/lib/clipboard/index.js +3 -3
  168. package/dist/lib/cms/published-documents.d.ts +1 -1
  169. package/dist/lib/cms-views/index.d.ts +2 -2
  170. package/dist/lib/cms-views/index.js +2 -2
  171. package/dist/lib/collection-view/collection-view-source.d.ts +4 -4
  172. package/dist/lib/data/collection-content.js +1 -1
  173. package/dist/lib/data/data-store.js +6 -6
  174. package/dist/lib/data/index.d.ts +4 -4
  175. package/dist/lib/data/index.js +4 -4
  176. package/dist/lib/data/sample-capture.js +4 -4
  177. package/dist/lib/data/sources-store.d.ts +1 -1
  178. package/dist/lib/diff-view-preference.js +1 -1
  179. package/dist/lib/dnd/gap-drag.d.ts +1 -1
  180. package/dist/lib/i18n/i18n.d.ts +1 -1
  181. package/dist/lib/i18n/i18n.js +1 -1
  182. package/dist/lib/i18n/index.d.ts +4 -4
  183. package/dist/lib/i18n/index.js +3 -3
  184. package/dist/lib/i18n/messages.en.d.ts +1 -1
  185. package/dist/lib/i18n/messages.en.js +1 -1
  186. package/dist/lib/locale/binding-fallback.d.ts +1 -1
  187. package/dist/lib/locale/binding-fallback.js +2 -2
  188. package/dist/lib/locale/index.d.ts +6 -6
  189. package/dist/lib/locale/index.js +6 -6
  190. package/dist/lib/locale/locale-switcher.js +3 -3
  191. package/dist/lib/locale/use-studio-locale.js +1 -1
  192. package/dist/lib/operations/asset-repository.d.ts +1 -1
  193. package/dist/lib/operations/cms-persistence.d.ts +1 -1
  194. package/dist/lib/operations/cms-persistence.js +1 -1
  195. package/dist/lib/operations/index.d.ts +6 -6
  196. package/dist/lib/operations/index.js +6 -6
  197. package/dist/lib/operations/session-hydration.d.ts +1 -1
  198. package/dist/lib/operations/session-hydration.js +1 -1
  199. package/dist/lib/operations/studio-operations.d.ts +1 -1
  200. package/dist/lib/operations/studio-operations.js +3 -3
  201. package/dist/lib/persistence/convex-adapter.js +2 -2
  202. package/dist/lib/persistence/convex-blob-storage.d.ts +1 -1
  203. package/dist/lib/persistence/convex-blob-storage.js +1 -1
  204. package/dist/lib/persistence/deployment-readiness.d.ts +2 -2
  205. package/dist/lib/persistence/index.d.ts +6 -6
  206. package/dist/lib/persistence/index.js +7 -7
  207. package/dist/lib/persona/labels.js +1 -1
  208. package/dist/lib/query/index.d.ts +2 -2
  209. package/dist/lib/query/index.js +1 -1
  210. package/dist/lib/rail.js +1 -1
  211. package/dist/lib/references/impact.d.ts +1 -1
  212. package/dist/lib/references/impact.js +1 -1
  213. package/dist/lib/references/index.d.ts +3 -3
  214. package/dist/lib/references/index.js +3 -3
  215. package/dist/lib/references/references-store.d.ts +2 -2
  216. package/dist/lib/references/references-store.js +3 -3
  217. package/dist/lib/registry/index.js +2 -2
  218. package/dist/lib/theme.js +1 -1
  219. package/dist/lib/webhooks/config.d.ts +1 -1
  220. package/dist/lib/webhooks/config.js +2 -2
  221. package/dist/lib/webhooks/dispatcher.js +2 -2
  222. package/dist/lib/webhooks/index.d.ts +6 -6
  223. package/dist/lib/webhooks/index.js +4 -4
  224. package/dist/lib/workspace/index.d.ts +7 -7
  225. package/dist/lib/workspace/index.js +3 -3
  226. package/dist/lib/workspace/switcher-source.d.ts +1 -1
  227. package/dist/router/index.d.ts +1 -1
  228. package/dist/router/index.js +1 -1
  229. package/dist/ui/avatar.js +1 -1
  230. package/dist/ui/badge.js +1 -1
  231. package/dist/ui/button.js +1 -1
  232. package/dist/ui/card.js +1 -1
  233. package/dist/ui/collapsible-section.js +2 -2
  234. package/dist/ui/dialog.js +1 -1
  235. package/dist/ui/dropdown-menu.js +1 -1
  236. package/dist/ui/field.js +1 -1
  237. package/dist/ui/index.d.ts +19 -19
  238. package/dist/ui/index.js +19 -19
  239. package/dist/ui/input.js +1 -1
  240. package/dist/ui/inspector.js +1 -1
  241. package/dist/ui/layout.js +1 -1
  242. package/dist/ui/panel.js +2 -2
  243. package/dist/ui/popover.js +1 -1
  244. package/dist/ui/row-item.js +2 -2
  245. package/dist/ui/segmented-control.js +1 -1
  246. package/dist/ui/separator.js +1 -1
  247. package/dist/ui/toolbar.js +2 -2
  248. package/dist/ui/typography.js +1 -1
  249. package/package.json +13 -5
  250. package/dist/components/assets/asset-library.d.ts +0 -14
  251. package/dist/components/assets/asset-library.js +0 -202
  252. package/dist/components/assets/asset-library.js.map +0 -1
  253. package/dist/components/backend-gate.d.ts +0 -19
  254. package/dist/components/backend-gate.js +0 -64
  255. package/dist/components/backend-gate.js.map +0 -1
  256. package/dist/components/builder/composition-scope.d.ts +0 -16
  257. package/dist/components/builder/composition-scope.js +0 -7
  258. package/dist/components/builder/composition-scope.js.map +0 -1
  259. package/dist/components/cms/alias-selector.d.ts +0 -20
  260. package/dist/components/cms/alias-selector.js +0 -34
  261. package/dist/components/cms/alias-selector.js.map +0 -1
  262. package/dist/components/cms/collections-sidebar.d.ts +0 -18
  263. package/dist/components/cms/collections-sidebar.js +0 -36
  264. package/dist/components/cms/collections-sidebar.js.map +0 -1
  265. package/dist/components/cms/new-page-dialog.d.ts +0 -22
  266. package/dist/components/cms/new-page-dialog.js +0 -42
  267. package/dist/components/cms/new-page-dialog.js.map +0 -1
  268. package/dist/components/cms/rich-text/embed-control.d.ts +0 -17
  269. package/dist/components/cms/rich-text/embed-control.js +0 -62
  270. package/dist/components/cms/rich-text/embed-control.js.map +0 -1
  271. package/dist/components/cms/rich-text/image-control.d.ts +0 -16
  272. package/dist/components/cms/rich-text/image-control.js +0 -36
  273. package/dist/components/cms/rich-text/image-control.js.map +0 -1
  274. package/dist/components/components/fixture-runner.d.ts +0 -10
  275. package/dist/components/components/fixture-runner.js +0 -27
  276. package/dist/components/components/fixture-runner.js.map +0 -1
  277. package/dist/components/references/builder-deep-link.d.ts +0 -12
  278. package/dist/components/references/builder-deep-link.js +0 -53
  279. package/dist/components/references/builder-deep-link.js.map +0 -1
  280. package/dist/components/routes/redirect-chain.d.ts +0 -9
  281. package/dist/components/routes/redirect-chain.js +0 -16
  282. package/dist/components/routes/redirect-chain.js.map +0 -1
  283. package/dist/components/routes/redirect-manager.d.ts +0 -13
  284. package/dist/components/routes/redirect-manager.js +0 -24
  285. package/dist/components/routes/redirect-manager.js.map +0 -1
  286. package/dist/components/routes/route-list.d.ts +0 -35
  287. package/dist/components/routes/route-list.js +0 -69
  288. package/dist/components/routes/route-list.js.map +0 -1
  289. package/dist/components/routes/routes-shell.d.ts +0 -14
  290. package/dist/components/routes/routes-shell.js +0 -68
  291. package/dist/components/routes/routes-shell.js.map +0 -1
  292. package/dist/components/sections.d.ts +0 -37
  293. package/dist/components/sections.js +0 -62
  294. package/dist/components/sections.js.map +0 -1
  295. package/dist/components/users/user-search.d.ts +0 -8
  296. package/dist/components/users/user-search.js +0 -13
  297. package/dist/components/users/user-search.js.map +0 -1
  298. package/dist/components/workspace-gate.d.ts +0 -12
  299. package/dist/components/workspace-gate.js +0 -28
  300. package/dist/components/workspace-gate.js.map +0 -1
  301. package/dist/lib/cms-views/views-store.d.ts +0 -70
  302. package/dist/lib/cms-views/views-store.js +0 -129
  303. package/dist/lib/cms-views/views-store.js.map +0 -1
  304. package/dist/lib/operations/graph-commands.d.ts +0 -136
  305. package/dist/lib/operations/graph-commands.js +0 -85
  306. package/dist/lib/operations/graph-commands.js.map +0 -1
  307. package/dist/lib/operations/graph-persistence.d.ts +0 -27
  308. package/dist/lib/operations/graph-persistence.js +0 -35
  309. package/dist/lib/operations/graph-persistence.js.map +0 -1
  310. package/dist/lib/routing/index.d.ts +0 -10
  311. package/dist/lib/routing/index.js +0 -11
  312. package/dist/lib/routing/index.js.map +0 -1
  313. package/dist/lib/routing/routing-analysis.d.ts +0 -26
  314. package/dist/lib/routing/routing-analysis.js +0 -37
  315. package/dist/lib/routing/routing-analysis.js.map +0 -1
  316. package/dist/lib/routing/routing-store.d.ts +0 -50
  317. package/dist/lib/routing/routing-store.js +0 -72
  318. package/dist/lib/routing/routing-store.js.map +0 -1
  319. package/dist/lib/working-graph.d.ts +0 -82
  320. package/dist/lib/working-graph.js +0 -113
  321. package/dist/lib/working-graph.js.map +0 -1
@@ -1,53 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
- import { getNode } from '@elytracms/core/project-graph';
3
- import { useBuilder } from '../builder/builder-store';
4
- /**
5
- * Param-driven builder selection (EC-172). Reference-panel entries deep-link
6
- * to `/$projectId/builder?page=<pageId>&node=<nodeId>`; this headless
7
- * component applies those params to the builder store — switching the active
8
- * page, selecting the node (falling back to the page root when the node has
9
- * drifted away), and scrolling the canvas wrapper (`data-ec-node-id`) into
10
- * view. Pure selection state: nothing here mutates the graph.
11
- */
12
- export function BuilderDeepLink({ pageId, nodeId }) {
13
- const builder = useBuilder();
14
- const { dispatch } = builder;
15
- const graphRef = useRef(builder.present);
16
- graphRef.current = builder.present;
17
- useEffect(() => {
18
- if (pageId === undefined && nodeId === undefined)
19
- return;
20
- const graph = graphRef.current;
21
- const page = (pageId !== undefined ? graph.pages.find((p) => p.id === pageId) : undefined) ??
22
- (nodeId !== undefined
23
- ? graph.pages.find((p) => getNode(p.root, nodeId) !== undefined)
24
- : undefined);
25
- if (!page)
26
- return;
27
- const selectedId = nodeId !== undefined && getNode(page.root, nodeId) ? nodeId : page.root.id;
28
- dispatch({ type: 'select', id: selectedId, pageId: page.id });
29
- // The canvas marks every rendered node with `data-ec-node-id`; scroll the
30
- // selection into view once the page has rendered (bounded retries — the
31
- // canvas may need a frame or two after a page switch).
32
- let cancelled = false;
33
- let attempts = 0;
34
- const tryScroll = () => {
35
- if (cancelled)
36
- return;
37
- const element = document.querySelector(`[data-ec-node-id="${CSS.escape(selectedId)}"]`);
38
- if (element) {
39
- element.scrollIntoView({ block: 'center', behavior: 'smooth' });
40
- }
41
- else if (attempts < 10) {
42
- attempts += 1;
43
- window.setTimeout(tryScroll, 60);
44
- }
45
- };
46
- window.setTimeout(tryScroll, 60);
47
- return () => {
48
- cancelled = true;
49
- };
50
- }, [pageId, nodeId, dispatch]);
51
- return null;
52
- }
53
- //# sourceMappingURL=builder-deep-link.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"builder-deep-link.js","sourceRoot":"","sources":["../../../src/components/references/builder-deep-link.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAErD;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAwC;IACtF,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;IAElC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS;YAAE,OAAM;QACxD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAA;QAC9B,MAAM,IAAI,GACR,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC7E,CAAC,MAAM,KAAK,SAAS;gBACnB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,SAAS,CAAC;gBAChE,CAAC,CAAC,SAAS,CAAC,CAAA;QAChB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,UAAU,GAAG,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;QAC7F,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;QAE7D,0EAA0E;QAC1E,wEAAwE;QACxE,uDAAuD;QACvD,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,IAAI,QAAQ,GAAG,CAAC,CAAA;QAChB,MAAM,SAAS,GAAG,GAAG,EAAE;YACrB,IAAI,SAAS;gBAAE,OAAM;YACrB,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,qBAAqB,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YACvF,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAA;YACjE,CAAC;iBAAM,IAAI,QAAQ,GAAG,EAAE,EAAE,CAAC;gBACzB,QAAQ,IAAI,CAAC,CAAA;gBACb,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;YAClC,CAAC;QACH,CAAC,CAAA;QACD,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QAChC,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAA;QAClB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE9B,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { useEffect, useRef } from 'react'\nimport { getNode } from '@elytracms/core/project-graph'\nimport { useBuilder } from '../builder/builder-store'\n\n/**\n * Param-driven builder selection (EC-172). Reference-panel entries deep-link\n * to `/$projectId/builder?page=<pageId>&node=<nodeId>`; this headless\n * component applies those params to the builder store — switching the active\n * page, selecting the node (falling back to the page root when the node has\n * drifted away), and scrolling the canvas wrapper (`data-ec-node-id`) into\n * view. Pure selection state: nothing here mutates the graph.\n */\nexport function BuilderDeepLink({ pageId, nodeId }: { pageId?: string; nodeId?: string }) {\n const builder = useBuilder()\n const { dispatch } = builder\n const graphRef = useRef(builder.present)\n graphRef.current = builder.present\n\n useEffect(() => {\n if (pageId === undefined && nodeId === undefined) return\n const graph = graphRef.current\n const page =\n (pageId !== undefined ? graph.pages.find((p) => p.id === pageId) : undefined) ??\n (nodeId !== undefined\n ? graph.pages.find((p) => getNode(p.root, nodeId) !== undefined)\n : undefined)\n if (!page) return\n const selectedId = nodeId !== undefined && getNode(page.root, nodeId) ? nodeId : page.root.id\n dispatch({ type: 'select', id: selectedId, pageId: page.id })\n\n // The canvas marks every rendered node with `data-ec-node-id`; scroll the\n // selection into view once the page has rendered (bounded retries — the\n // canvas may need a frame or two after a page switch).\n let cancelled = false\n let attempts = 0\n const tryScroll = () => {\n if (cancelled) return\n const element = document.querySelector(`[data-ec-node-id=\"${CSS.escape(selectedId)}\"]`)\n if (element) {\n element.scrollIntoView({ block: 'center', behavior: 'smooth' })\n } else if (attempts < 10) {\n attempts += 1\n window.setTimeout(tryScroll, 60)\n }\n }\n window.setTimeout(tryScroll, 60)\n return () => {\n cancelled = true\n }\n }, [pageId, nodeId, dispatch])\n\n return null\n}\n"]}
@@ -1,9 +0,0 @@
1
- import type { RedirectResolution } from '@elytracms/core/cms-core';
2
- /**
3
- * Visualizes a redirect chain from the engine's {@link RedirectResolution}:
4
- * every hop in order, with the loop entry point highlighted when the chain
5
- * closes a loop (EC-170).
6
- */
7
- export declare function RedirectChain({ resolution }: {
8
- resolution: RedirectResolution;
9
- }): import("react").JSX.Element;
@@ -1,16 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Fragment } from 'react';
3
- import { ArrowRight, RotateCw } from 'lucide-react';
4
- import { Badge, Code, Row } from '@elytracms/studio/ui';
5
- /**
6
- * Visualizes a redirect chain from the engine's {@link RedirectResolution}:
7
- * every hop in order, with the loop entry point highlighted when the chain
8
- * closes a loop (EC-170).
9
- */
10
- export function RedirectChain({ resolution }) {
11
- return (_jsxs(Row, { gap: "1", wrap: true, "data-testid": "redirect-chain", "data-loop": resolution.loop, children: [resolution.chain.map((hop, index) => {
12
- const isLoopEntry = resolution.loop && hop === resolution.target;
13
- return (_jsxs(Fragment, { children: [index > 0 ? _jsx(ArrowRight, { className: "size-3 shrink-0 text-muted-foreground" }) : null, _jsx(Code, { className: isLoopEntry ? 'border-destructive/50 bg-destructive/10 text-destructive' : undefined, children: hop })] }, `${hop}-${index}`));
14
- }), resolution.loop ? (_jsxs(Badge, { variant: "error", "data-testid": "loop-badge", children: [_jsx(RotateCw, {}), " loop"] })) : null] }));
15
- }
16
- //# sourceMappingURL=redirect-chain.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"redirect-chain.js","sourceRoot":"","sources":["../../../src/components/routes/redirect-chain.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAEnD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAEvD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,UAAU,EAAsC;IAC9E,OAAO,CACL,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,uBAAa,gBAAgB,eAAY,UAAU,CAAC,IAAI,aACtE,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;gBACnC,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,IAAI,GAAG,KAAK,UAAU,CAAC,MAAM,CAAA;gBAChE,OAAO,CACL,MAAC,QAAQ,eACN,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAC,UAAU,IAAC,SAAS,EAAC,uCAAuC,GAAG,CAAC,CAAC,CAAC,IAAI,EACpF,KAAC,IAAI,IACH,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,0DAA0D,CAAC,CAAC,CAAC,SAAS,YAE9F,GAAG,GACC,KANM,GAAG,GAAG,IAAI,KAAK,EAAE,CAOrB,CACZ,CAAA;YACH,CAAC,CAAC,EACD,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CACjB,MAAC,KAAK,IAAC,OAAO,EAAC,OAAO,iBAAa,YAAY,aAC7C,KAAC,QAAQ,KAAG,aACN,CACT,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAA;AACH,CAAC","sourcesContent":["import { Fragment } from 'react'\nimport { ArrowRight, RotateCw } from 'lucide-react'\nimport type { RedirectResolution } from '@elytracms/core/cms-core'\nimport { Badge, Code, Row } from '@elytracms/studio/ui'\n\n/**\n * Visualizes a redirect chain from the engine's {@link RedirectResolution}:\n * every hop in order, with the loop entry point highlighted when the chain\n * closes a loop (EC-170).\n */\nexport function RedirectChain({ resolution }: { resolution: RedirectResolution }) {\n return (\n <Row gap=\"1\" wrap data-testid=\"redirect-chain\" data-loop={resolution.loop}>\n {resolution.chain.map((hop, index) => {\n const isLoopEntry = resolution.loop && hop === resolution.target\n return (\n <Fragment key={`${hop}-${index}`}>\n {index > 0 ? <ArrowRight className=\"size-3 shrink-0 text-muted-foreground\" /> : null}\n <Code\n className={isLoopEntry ? 'border-destructive/50 bg-destructive/10 text-destructive' : undefined}\n >\n {hop}\n </Code>\n </Fragment>\n )\n })}\n {resolution.loop ? (\n <Badge variant=\"error\" data-testid=\"loop-badge\">\n <RotateCw /> loop\n </Badge>\n ) : null}\n </Row>\n )\n}\n"]}
@@ -1,13 +0,0 @@
1
- import type { RedirectRecord, RedirectResolution } from '@elytracms/core/cms-core';
2
- export interface RedirectManagerProps {
3
- redirects: readonly RedirectRecord[];
4
- /** The live router's chain resolver (engine API, EC-020). */
5
- followRedirects: (from: string) => RedirectResolution;
6
- }
7
- /**
8
- * The redirect list (EC-170), read-only (EC-207 "routes as code"): every
9
- * 301/302 redirect of the project. Each row shows its resolved chain from the
10
- * live engine; loops render as explicit errors. Redirects are authored in
11
- * `elytra.config.ts`; this surface only views them.
12
- */
13
- export declare function RedirectManager({ redirects, followRedirects }: RedirectManagerProps): import("react").JSX.Element;
@@ -1,24 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ArrowRight } from 'lucide-react';
3
- import { Badge, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
4
- import { statusCodeOf } from '@elytracms/studio/lib/routing';
5
- import { RedirectChain } from './redirect-chain';
6
- /**
7
- * The redirect list (EC-170), read-only (EC-207 "routes as code"): every
8
- * 301/302 redirect of the project. Each row shows its resolved chain from the
9
- * live engine; loops render as explicit errors. Redirects are authored in
10
- * `elytra.config.ts`; this surface only views them.
11
- */
12
- export function RedirectManager({ redirects, followRedirects }) {
13
- const sorted = [...redirects].sort((a, b) => a.from.localeCompare(b.from));
14
- return (_jsxs(Stack, { gap: "none", className: "min-h-0", children: [_jsxs(Row, { gap: "2", className: "h-12 shrink-0 border-b border-border px-4", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Redirects" }), _jsx(Text, { size: "xs", tone: "subtle", mono: true, children: redirects.length })] }), _jsxs(Stack, { gap: "3", className: "p-4", children: [sorted.length === 0 ? (_jsx(Text, { size: "sm", tone: "muted", children: "No redirects defined. Add redirects in elytra.config.ts to forward old URLs to new ones." })) : null, _jsx("ul", { className: "flex flex-col gap-2", "data-testid": "redirect-list", children: sorted.map((redirect) => (_jsx(RedirectRow, { redirect: redirect, resolution: followRedirects(redirect.from) }, redirect.id))) })] })] }));
15
- }
16
- function RedirectRow({ redirect, resolution, }) {
17
- // More hops than `from → to` means this redirect chains onward (or loops).
18
- const chains = resolution.loop || resolution.chain.length > 2;
19
- return (_jsxs("li", { "data-testid": `redirect-row-${redirect.id}`, className: 'rounded-md border p-3 ' +
20
- (resolution.loop ? 'border-destructive/40 bg-destructive/5' : 'border-border bg-card/40'), children: [_jsxs(Row, { gap: "2", wrap: true, children: [_jsx(Code, { children: redirect.from }), _jsx(ArrowRight, { className: "size-3 shrink-0 text-muted-foreground" }), _jsx(Code, { children: redirect.to }), _jsx(Badge, { variant: redirect.permanent ? 'primary' : 'info', "data-testid": `redirect-status-${redirect.id}`, children: statusCodeOf(redirect) }), resolution.loop ? _jsx(Badge, { variant: "error", children: "loop" }) : null, _jsx(Spacer, {})] }), chains ? (_jsxs(Stack, { gap: "1", className: "mt-2", children: [_jsx(Text, { size: "xs", tone: "muted", children: resolution.loop
21
- ? 'This redirect is part of a loop:'
22
- : `Chains to ${resolution.target}:` }), _jsx(RedirectChain, { resolution: resolution })] })) : null] }));
23
- }
24
- //# sourceMappingURL=redirect-manager.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"redirect-manager.js","sourceRoot":"","sources":["../../../src/components/routes/redirect-manager.tsx"],"names":[],"mappings":"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","sourcesContent":["import { ArrowRight } from 'lucide-react'\nimport type { RedirectRecord, RedirectResolution } from '@elytracms/core/cms-core'\nimport { Badge, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { statusCodeOf } from '@elytracms/studio/lib/routing'\nimport { RedirectChain } from './redirect-chain'\n\nexport interface RedirectManagerProps {\n redirects: readonly RedirectRecord[]\n /** The live router's chain resolver (engine API, EC-020). */\n followRedirects: (from: string) => RedirectResolution\n}\n\n/**\n * The redirect list (EC-170), read-only (EC-207 \"routes as code\"): every\n * 301/302 redirect of the project. Each row shows its resolved chain from the\n * live engine; loops render as explicit errors. Redirects are authored in\n * `elytra.config.ts`; this surface only views them.\n */\nexport function RedirectManager({ redirects, followRedirects }: RedirectManagerProps) {\n const sorted = [...redirects].sort((a, b) => a.from.localeCompare(b.from))\n\n return (\n <Stack gap=\"none\" className=\"min-h-0\">\n <Row gap=\"2\" className=\"h-12 shrink-0 border-b border-border px-4\">\n <Text size=\"sm\" weight=\"semibold\">\n Redirects\n </Text>\n <Text size=\"xs\" tone=\"subtle\" mono>\n {redirects.length}\n </Text>\n </Row>\n\n <Stack gap=\"3\" className=\"p-4\">\n {sorted.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n No redirects defined. Add redirects in elytra.config.ts to forward old URLs to new ones.\n </Text>\n ) : null}\n\n <ul className=\"flex flex-col gap-2\" data-testid=\"redirect-list\">\n {sorted.map((redirect) => (\n <RedirectRow\n key={redirect.id}\n redirect={redirect}\n resolution={followRedirects(redirect.from)}\n />\n ))}\n </ul>\n </Stack>\n </Stack>\n )\n}\n\nfunction RedirectRow({\n redirect,\n resolution,\n}: {\n redirect: RedirectRecord\n /** Engine-resolved chain starting at this redirect's source. */\n resolution: RedirectResolution\n}) {\n // More hops than `from → to` means this redirect chains onward (or loops).\n const chains = resolution.loop || resolution.chain.length > 2\n return (\n <li\n data-testid={`redirect-row-${redirect.id}`}\n className={\n 'rounded-md border p-3 ' +\n (resolution.loop ? 'border-destructive/40 bg-destructive/5' : 'border-border bg-card/40')\n }\n >\n <Row gap=\"2\" wrap>\n <Code>{redirect.from}</Code>\n <ArrowRight className=\"size-3 shrink-0 text-muted-foreground\" />\n <Code>{redirect.to}</Code>\n <Badge\n variant={redirect.permanent ? 'primary' : 'info'}\n data-testid={`redirect-status-${redirect.id}`}\n >\n {statusCodeOf(redirect)}\n </Badge>\n {resolution.loop ? <Badge variant=\"error\">loop</Badge> : null}\n <Spacer />\n </Row>\n {chains ? (\n <Stack gap=\"1\" className=\"mt-2\">\n <Text size=\"xs\" tone=\"muted\">\n {resolution.loop\n ? 'This redirect is part of a loop:'\n : `Chains to ${resolution.target}:`}\n </Text>\n <RedirectChain resolution={resolution} />\n </Stack>\n ) : null}\n </li>\n )\n}\n"]}
@@ -1,35 +0,0 @@
1
- import type { RouteRecord } from '@elytracms/core/cms-core';
2
- import type { RouteConflict } from '@elytracms/studio/lib/routing';
3
- /** The slice of a graph page the route list needs for linking/labelling. */
4
- export interface PageSummary {
5
- id: string;
6
- name: string;
7
- }
8
- export interface RouteListProps {
9
- projectId: string;
10
- routes: readonly RouteRecord[];
11
- /** Engine-sourced `route-conflict` issues, indexed by route id (both sides). */
12
- conflicts: ReadonlyMap<string, RouteConflict[]>;
13
- pages: readonly PageSummary[];
14
- }
15
- /**
16
- * Every route of the project (EC-170), read-only (EC-207 "routes as code"):
17
- * static paths, dynamic patterns with `:param` segments rendered distinctly, and
18
- * per-locale variants — each row linking to its page/template/document. Routes
19
- * are authored in `elytra.config.ts`; this list only views them. Conflicts from
20
- * the engine's structured issues render inline with deep links to the
21
- * counterpart row.
22
- */
23
- export declare function RouteList({ projectId, routes, conflicts, pages }: RouteListProps): import("react").JSX.Element;
24
- /** A route pattern with `:param` segments rendered distinctly (EC-170). */
25
- export declare function PatternView({ pattern }: {
26
- pattern: string;
27
- }): import("react").JSX.Element;
28
- /**
29
- * Inline conflict block: the engine's `route-conflict` message plus a deep
30
- * link to the counterpart row (same-page anchor; rows highlight on `:target`).
31
- */
32
- export declare function ConflictNotice({ routeId, conflicts, }: {
33
- routeId: string;
34
- conflicts: readonly RouteConflict[];
35
- }): import("react").JSX.Element;
@@ -1,69 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router';
3
- import { CircleAlert, Globe } from 'lucide-react';
4
- import { Badge, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
5
- import { patternSegments } from '@elytracms/studio/lib/routing';
6
- /**
7
- * Every route of the project (EC-170), read-only (EC-207 "routes as code"):
8
- * static paths, dynamic patterns with `:param` segments rendered distinctly, and
9
- * per-locale variants — each row linking to its page/template/document. Routes
10
- * are authored in `elytra.config.ts`; this list only views them. Conflicts from
11
- * the engine's structured issues render inline with deep links to the
12
- * counterpart row.
13
- */
14
- export function RouteList({ projectId, routes, conflicts, pages }) {
15
- const sorted = [...routes].sort((a, b) => a.pattern === b.pattern
16
- ? (a.locale ?? '').localeCompare(b.locale ?? '')
17
- : a.pattern.localeCompare(b.pattern));
18
- return (_jsxs(Stack, { gap: "none", className: "min-h-0", children: [_jsxs(Row, { gap: "2", className: "h-12 shrink-0 border-b border-border px-4", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Routes" }), _jsx(Text, { size: "xs", tone: "subtle", mono: true, children: routes.length })] }), _jsxs(Stack, { gap: "3", className: "p-4", children: [sorted.length === 0 ? (_jsx(Text, { size: "sm", tone: "muted", children: "No routes defined. Add routes in elytra.config.ts to bind URL patterns to pages or templates." })) : null, _jsx("ul", { className: "flex flex-col gap-2", "data-testid": "route-list", children: sorted.map((route) => (_jsx(RouteRow, { projectId: projectId, route: route, conflicts: conflicts.get(route.id) ?? [], pages: pages }, route.id))) })] })] }));
19
- }
20
- function RouteRow({ projectId, route, conflicts, pages, }) {
21
- const hasConflict = conflicts.length > 0;
22
- return (_jsxs("li", { id: `route-${route.id}`, "data-testid": `route-row-${route.id}`, className: 'rounded-md border p-3 transition-colors target:ring-2 target:ring-ring/60 ' +
23
- (hasConflict ? 'border-destructive/40 bg-destructive/5' : 'border-border bg-card/40'), children: [_jsxs(Row, { gap: "2", wrap: true, children: [_jsx(PatternView, { pattern: route.pattern }), _jsx(LocaleBadge, { locale: route.locale }), _jsx(Spacer, {}), hasConflict ? (_jsx(Badge, { variant: "error", "data-testid": `route-status-${route.id}`, children: "conflict" })) : (_jsx(Badge, { variant: "success", "data-testid": `route-status-${route.id}`, children: "ok" }))] }), _jsx(Row, { gap: "2", wrap: true, className: "mt-2", children: _jsx(RouteTarget, { projectId: projectId, route: route, pages: pages }) }), hasConflict ? _jsx(ConflictNotice, { routeId: route.id, conflicts: conflicts }) : null] }));
24
- }
25
- /** A route pattern with `:param` segments rendered distinctly (EC-170). */
26
- export function PatternView({ pattern }) {
27
- const segments = patternSegments(pattern);
28
- return (_jsxs("span", { className: "inline-flex flex-wrap items-center font-mono text-sm", "data-testid": "pattern", children: [_jsx("span", { className: "text-muted-foreground", children: "/" }), segments.map((segment, index) => (_jsxs("span", { className: "inline-flex items-center", children: [segment.param ? (_jsx("span", { "data-param": segment.text, className: "rounded-sm border border-primary/30 bg-primary/10 px-1 text-primary", children: segment.text })) : (_jsx("span", { className: "text-foreground", children: segment.text })), index < segments.length - 1 ? _jsx("span", { className: "text-muted-foreground", children: "/" }) : null] }, `${segment.text}-${index}`)))] }));
29
- }
30
- function LocaleBadge({ locale }) {
31
- if (!locale) {
32
- return (_jsxs(Badge, { variant: "outline", children: [_jsx(Globe, {}), " all locales"] }));
33
- }
34
- return (_jsxs(Badge, { variant: "info", children: [_jsx(Globe, {}), " ", locale] }));
35
- }
36
- /** The page/template/document a route renders, deep-linked where a surface exists. */
37
- function RouteTarget({ projectId, route, pages, }) {
38
- // EC-187: every target is a single `document` ref. A page target is the
39
- // `page` collection; a template target is a `:param` placeholder id;
40
- // anything else is a concrete linked document.
41
- if (!route.document) {
42
- return (_jsx(Badge, { variant: "warning", "data-testid": `route-no-target-${route.id}`, children: "no target" }));
43
- }
44
- if (route.document.collection === 'page') {
45
- const pageId = route.document.id;
46
- const page = pages.find((candidate) => candidate.id === pageId);
47
- return (_jsxs(Row, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", children: "page" }), _jsx(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: studioPath.builder() }, className: "text-xs font-medium text-primary underline-offset-2 hover:underline", children: page?.name ?? pageId }), !page ? _jsx(Badge, { variant: "warning", children: "not in graph" }) : null] }));
48
- }
49
- if (route.document.id.startsWith(':')) {
50
- return (_jsxs(Row, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", children: "template" }), _jsx(Code, { children: route.document.collection })] }));
51
- }
52
- return (_jsxs(Row, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", children: "document" }), _jsxs(Link, { to: STUDIO_PROJECT_ROUTE, params: {
53
- projectId,
54
- _splat: studioPath.cmsDocument(route.document.collection, route.document.id),
55
- }, className: "text-xs font-medium text-primary underline-offset-2 hover:underline", children: [route.document.collection, "/", route.document.id] })] }));
56
- }
57
- /**
58
- * Inline conflict block: the engine's `route-conflict` message plus a deep
59
- * link to the counterpart row (same-page anchor; rows highlight on `:target`).
60
- */
61
- export function ConflictNotice({ routeId, conflicts, }) {
62
- return (_jsx(Stack, { gap: "1", className: "mt-2 rounded-md border border-destructive/30 bg-destructive/10 p-2", children: conflicts.map((conflict, index) => (_jsxs(Row, { gap: "2", align: "start", children: [_jsx(CircleAlert, { className: "mt-0.5 size-3.5 shrink-0 text-destructive" }), _jsxs(Stack, { gap: "1", className: "min-w-0", children: [_jsx(Text, { size: "xs", className: "text-destructive", "data-testid": `conflict-${routeId}`,
63
- // The engine message names both routes by their raw ids
64
- // (`r-blog-slug-en`) — kept inspectable on hover, never shown as
65
- // visible copy. The editor sees the patterns on the rows + the link
66
- // below (EC-192).
67
- title: conflict.issue.message, children: "Matches the same URLs as another route." }), conflict.otherRouteId ? (_jsx("a", { href: `#route-${conflict.otherRouteId}`, className: "text-xs font-medium text-destructive underline underline-offset-2", "data-testid": `conflict-link-${routeId}`, children: "View conflicting route" })) : null] })] }, `${conflict.otherRouteId}-${index}`))) }));
68
- }
69
- //# sourceMappingURL=route-list.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"route-list.js","sourceRoot":"","sources":["../../../src/components/routes/route-list.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { CircleAlert, Globe } from 'lucide-react'\nimport type { RouteRecord } from '@elytracms/core/cms-core'\nimport { Badge, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { patternSegments } from '@elytracms/studio/lib/routing'\nimport type { RouteConflict } from '@elytracms/studio/lib/routing'\n\n/** The slice of a graph page the route list needs for linking/labelling. */\nexport interface PageSummary {\n id: string\n name: string\n}\n\nexport interface RouteListProps {\n projectId: string\n routes: readonly RouteRecord[]\n /** Engine-sourced `route-conflict` issues, indexed by route id (both sides). */\n conflicts: ReadonlyMap<string, RouteConflict[]>\n pages: readonly PageSummary[]\n}\n\n/**\n * Every route of the project (EC-170), read-only (EC-207 \"routes as code\"):\n * static paths, dynamic patterns with `:param` segments rendered distinctly, and\n * per-locale variants — each row linking to its page/template/document. Routes\n * are authored in `elytra.config.ts`; this list only views them. Conflicts from\n * the engine's structured issues render inline with deep links to the\n * counterpart row.\n */\nexport function RouteList({ projectId, routes, conflicts, pages }: RouteListProps) {\n const sorted = [...routes].sort((a, b) =>\n a.pattern === b.pattern\n ? (a.locale ?? '').localeCompare(b.locale ?? '')\n : a.pattern.localeCompare(b.pattern),\n )\n\n return (\n <Stack gap=\"none\" className=\"min-h-0\">\n <Row gap=\"2\" className=\"h-12 shrink-0 border-b border-border px-4\">\n <Text size=\"sm\" weight=\"semibold\">\n Routes\n </Text>\n <Text size=\"xs\" tone=\"subtle\" mono>\n {routes.length}\n </Text>\n </Row>\n\n <Stack gap=\"3\" className=\"p-4\">\n {sorted.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n No routes defined. Add routes in elytra.config.ts to bind URL patterns to pages or\n templates.\n </Text>\n ) : null}\n\n <ul className=\"flex flex-col gap-2\" data-testid=\"route-list\">\n {sorted.map((route) => (\n <RouteRow\n key={route.id}\n projectId={projectId}\n route={route}\n conflicts={conflicts.get(route.id) ?? []}\n pages={pages}\n />\n ))}\n </ul>\n </Stack>\n </Stack>\n )\n}\n\nfunction RouteRow({\n projectId,\n route,\n conflicts,\n pages,\n}: {\n projectId: string\n route: RouteRecord\n conflicts: readonly RouteConflict[]\n pages: readonly PageSummary[]\n}) {\n const hasConflict = conflicts.length > 0\n return (\n <li\n id={`route-${route.id}`}\n data-testid={`route-row-${route.id}`}\n className={\n 'rounded-md border p-3 transition-colors target:ring-2 target:ring-ring/60 ' +\n (hasConflict ? 'border-destructive/40 bg-destructive/5' : 'border-border bg-card/40')\n }\n >\n <Row gap=\"2\" wrap>\n <PatternView pattern={route.pattern} />\n <LocaleBadge locale={route.locale} />\n <Spacer />\n {hasConflict ? (\n <Badge variant=\"error\" data-testid={`route-status-${route.id}`}>\n conflict\n </Badge>\n ) : (\n <Badge variant=\"success\" data-testid={`route-status-${route.id}`}>\n ok\n </Badge>\n )}\n </Row>\n <Row gap=\"2\" wrap className=\"mt-2\">\n <RouteTarget projectId={projectId} route={route} pages={pages} />\n </Row>\n {hasConflict ? <ConflictNotice routeId={route.id} conflicts={conflicts} /> : null}\n </li>\n )\n}\n\n/** A route pattern with `:param` segments rendered distinctly (EC-170). */\nexport function PatternView({ pattern }: { pattern: string }) {\n const segments = patternSegments(pattern)\n return (\n <span className=\"inline-flex flex-wrap items-center font-mono text-sm\" data-testid=\"pattern\">\n <span className=\"text-muted-foreground\">/</span>\n {segments.map((segment, index) => (\n <span key={`${segment.text}-${index}`} className=\"inline-flex items-center\">\n {segment.param ? (\n <span\n data-param={segment.text}\n className=\"rounded-sm border border-primary/30 bg-primary/10 px-1 text-primary\"\n >\n {segment.text}\n </span>\n ) : (\n <span className=\"text-foreground\">{segment.text}</span>\n )}\n {index < segments.length - 1 ? <span className=\"text-muted-foreground\">/</span> : null}\n </span>\n ))}\n </span>\n )\n}\n\nfunction LocaleBadge({ locale }: { locale: string | undefined }) {\n if (!locale) {\n return (\n <Badge variant=\"outline\">\n <Globe /> all locales\n </Badge>\n )\n }\n return (\n <Badge variant=\"info\">\n <Globe /> {locale}\n </Badge>\n )\n}\n\n/** The page/template/document a route renders, deep-linked where a surface exists. */\nfunction RouteTarget({\n projectId,\n route,\n pages,\n}: {\n projectId: string\n route: RouteRecord\n pages: readonly PageSummary[]\n}) {\n // EC-187: every target is a single `document` ref. A page target is the\n // `page` collection; a template target is a `:param` placeholder id;\n // anything else is a concrete linked document.\n if (!route.document) {\n return (\n <Badge variant=\"warning\" data-testid={`route-no-target-${route.id}`}>\n no target\n </Badge>\n )\n }\n if (route.document.collection === 'page') {\n const pageId = route.document.id\n const page = pages.find((candidate) => candidate.id === pageId)\n return (\n <Row gap=\"1\">\n <Text size=\"xs\" tone=\"muted\">\n page\n </Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.builder() }}\n className=\"text-xs font-medium text-primary underline-offset-2 hover:underline\"\n >\n {page?.name ?? pageId}\n </Link>\n {!page ? <Badge variant=\"warning\">not in graph</Badge> : null}\n </Row>\n )\n }\n if (route.document.id.startsWith(':')) {\n return (\n <Row gap=\"1\">\n <Text size=\"xs\" tone=\"muted\">\n template\n </Text>\n <Code>{route.document.collection}</Code>\n </Row>\n )\n }\n return (\n <Row gap=\"1\">\n <Text size=\"xs\" tone=\"muted\">\n document\n </Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{\n projectId,\n _splat: studioPath.cmsDocument(route.document.collection, route.document.id),\n }}\n className=\"text-xs font-medium text-primary underline-offset-2 hover:underline\"\n >\n {route.document.collection}/{route.document.id}\n </Link>\n </Row>\n )\n}\n\n/**\n * Inline conflict block: the engine's `route-conflict` message plus a deep\n * link to the counterpart row (same-page anchor; rows highlight on `:target`).\n */\nexport function ConflictNotice({\n routeId,\n conflicts,\n}: {\n routeId: string\n conflicts: readonly RouteConflict[]\n}) {\n return (\n <Stack gap=\"1\" className=\"mt-2 rounded-md border border-destructive/30 bg-destructive/10 p-2\">\n {conflicts.map((conflict, index) => (\n <Row gap=\"2\" align=\"start\" key={`${conflict.otherRouteId}-${index}`}>\n <CircleAlert className=\"mt-0.5 size-3.5 shrink-0 text-destructive\" />\n <Stack gap=\"1\" className=\"min-w-0\">\n <Text\n size=\"xs\"\n className=\"text-destructive\"\n data-testid={`conflict-${routeId}`}\n // The engine message names both routes by their raw ids\n // (`r-blog-slug-en`) — kept inspectable on hover, never shown as\n // visible copy. The editor sees the patterns on the rows + the link\n // below (EC-192).\n title={conflict.issue.message}\n >\n Matches the same URLs as another route.\n </Text>\n {conflict.otherRouteId ? (\n <a\n href={`#route-${conflict.otherRouteId}`}\n className=\"text-xs font-medium text-destructive underline underline-offset-2\"\n data-testid={`conflict-link-${routeId}`}\n >\n View conflicting route\n </a>\n ) : null}\n </Stack>\n </Row>\n ))}\n </Stack>\n )\n}\n"]}
@@ -1,14 +0,0 @@
1
- import type { Project } from '@elytracms/core/studio-core';
2
- /**
3
- * The Routes surface (EC-170, made read-only by EC-207 "routes as code"):
4
- * every route and redirect of the project, with the engine's structured issues
5
- * (route conflicts, redirect loops) rendered inline. Routes and redirects are
6
- * authored in `elytra.config.ts` and read from the resolved config project
7
- * (`resolveDeploymentRoutes`/`resolveDeploymentRedirects`) — NEVER from the
8
- * backend. This surface only views them; conflict/loop detection comes from the
9
- * canonical `@elytracms/core/cms-core` router, never from parallel UI-side logic. The
10
- * page list (CMS `page` collection) is read only to label route targets.
11
- */
12
- export declare function RoutesShell({ project }: {
13
- project: Project;
14
- }): import("react").JSX.Element;
@@ -1,68 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo } from 'react';
3
- import { createRouter } from '@elytracms/core/cms-core';
4
- import { Badge, Heading, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
5
- import { useOperationQuery } from '@elytracms/studio/lib/query';
6
- import { conflictsByRoute } from '@elytracms/studio/lib/routing';
7
- import { resolveDeploymentRedirects, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace';
8
- import { OperationErrorBanner } from '../operation-error';
9
- import { RouteList } from './route-list';
10
- import { RedirectManager } from './redirect-manager';
11
- /** The body-is-a-canvas page collection (EC-187): routable page documents. */
12
- const PAGE_COLLECTION = 'page';
13
- /** Page-label freshness: re-read the labels when any document command lands. */
14
- const PAGE_LABEL_RELOAD_OPS = ['documents'];
15
- const EMPTY_PAGES = [];
16
- /**
17
- * Routable pages come from the `page` CMS collection (EC-192) — the documents an
18
- * editor actually creates — not the builder's single synthetic working page.
19
- * `documents.list` returns persisted records wrapping each document; map each to
20
- * a `{ id, name }` summary for the route target labels.
21
- */
22
- function pageSummaries(records) {
23
- return records.map(({ document }) => {
24
- const name = document.values?.['name'];
25
- return { id: document.id, name: typeof name === 'string' && name.length > 0 ? name : document.id };
26
- });
27
- }
28
- async function loadPageLabels(operations, projectId) {
29
- const result = await operations.documents.list({ projectId, collection: PAGE_COLLECTION });
30
- if (result.ok)
31
- return { pages: pageSummaries(result.value), error: null };
32
- return { pages: [], error: result.error };
33
- }
34
- /**
35
- * The Routes surface (EC-170, made read-only by EC-207 "routes as code"):
36
- * every route and redirect of the project, with the engine's structured issues
37
- * (route conflicts, redirect loops) rendered inline. Routes and redirects are
38
- * authored in `elytra.config.ts` and read from the resolved config project
39
- * (`resolveDeploymentRoutes`/`resolveDeploymentRedirects`) — NEVER from the
40
- * backend. This surface only views them; conflict/loop detection comes from the
41
- * canonical `@elytracms/core/cms-core` router, never from parallel UI-side logic. The
42
- * page list (CMS `page` collection) is read only to label route targets.
43
- */
44
- export function RoutesShell({ project }) {
45
- const projectId = project.id;
46
- // Routes & redirects are config-sourced (EC-207): resolved synchronously from
47
- // the active `elytra.config.ts` project, never fetched from the backend.
48
- const routes = useMemo(() => resolveDeploymentRoutes(), []);
49
- const redirects = useMemo(() => resolveDeploymentRedirects(), []);
50
- // Page documents feed the route target labels (read-only display), read through
51
- // the one operations-backed query cache (EC-308/AD-17) and kept fresh as
52
- // documents change. A failure is non-fatal — see `loadPageLabels`.
53
- const pagesQuery = useOperationQuery({
54
- queryKey: ['routePageLabels', projectId],
55
- loader: (operations) => loadPageLabels(operations, projectId),
56
- invalidateOn: PAGE_LABEL_RELOAD_OPS,
57
- });
58
- const pages = pagesQuery.data?.pages ?? EMPTY_PAGES;
59
- const loadError = pagesQuery.data?.error ?? null;
60
- // The engine: one router over the config-resolved records. Its structured
61
- // issues are the single source of conflict/loop truth for this surface.
62
- const router = useMemo(() => createRouter(routes, redirects, {
63
- defaultLocale: project.settings.defaultLocale,
64
- }), [routes, redirects, project.settings.defaultLocale]);
65
- const conflicts = useMemo(() => conflictsByRoute(router.issues), [router]);
66
- return (_jsxs(Stack, { gap: "none", className: "min-h-full", "data-testid": "routes-shell", children: [_jsxs(Row, { gap: "3", className: "shrink-0 border-b border-border px-4 py-3 sm:px-6", children: [_jsxs(Stack, { gap: "1", children: [_jsx(Heading, { level: 2, size: "h3", children: "Routes & redirects" }), _jsxs(Text, { size: "xs", tone: "muted", children: ["Routes & redirects are defined in ", _jsx("code", { children: "elytra.config.ts" }), ". Conflicts and loops are detected by the canonical route engine \u2014 never silently."] })] }), _jsx(Spacer, {}), loadError ? (_jsx(OperationErrorBanner, { error: loadError, onDismiss: () => void pagesQuery.refetch() })) : null, router.issues.length > 0 ? (_jsxs(Badge, { variant: "error", "data-testid": "routing-issue-count", children: [router.issues.length, " issue", router.issues.length === 1 ? '' : 's'] })) : (_jsx(Badge, { variant: "success", children: "no issues" }))] }), _jsxs("div", { className: "grid min-h-0 flex-1 grid-cols-1 xl:grid-cols-[minmax(0,5fr)_minmax(0,4fr)]", children: [_jsx("section", { "aria-label": "Routes", className: "min-w-0 border-b border-border xl:border-b-0 xl:border-r", children: _jsx(RouteList, { projectId: projectId, routes: routes, conflicts: conflicts, pages: pages }) }), _jsx("section", { "aria-label": "Redirects", className: "min-w-0", children: _jsx(RedirectManager, { redirects: redirects, followRedirects: router.followRedirects }) })] })] }));
67
- }
68
- //# sourceMappingURL=routes-shell.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"routes-shell.js","sourceRoot":"","sources":["../../../src/components/routes/routes-shell.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport { createRouter } from '@elytracms/core/cms-core'\nimport type { CmsDocument } from '@elytracms/core/cms-core'\nimport type { Project } from '@elytracms/core/studio-core'\nimport { Badge, Heading, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport type { OperationError, OperationsClient } from '@elytracms/studio/lib/operations'\nimport { useOperationQuery } from '@elytracms/studio/lib/query'\nimport { conflictsByRoute } from '@elytracms/studio/lib/routing'\nimport { resolveDeploymentRedirects, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport { OperationErrorBanner } from '../operation-error'\nimport { RouteList } from './route-list'\nimport type { PageSummary } from './route-list'\nimport { RedirectManager } from './redirect-manager'\n\n/** The body-is-a-canvas page collection (EC-187): routable page documents. */\nconst PAGE_COLLECTION = 'page'\n\n/** Page-label freshness: re-read the labels when any document command lands. */\nconst PAGE_LABEL_RELOAD_OPS = ['documents'] as const\nconst EMPTY_PAGES: readonly PageSummary[] = []\n\n/**\n * Routable pages come from the `page` CMS collection (EC-192) — the documents an\n * editor actually creates — not the builder's single synthetic working page.\n * `documents.list` returns persisted records wrapping each document; map each to\n * a `{ id, name }` summary for the route target labels.\n */\nfunction pageSummaries(records: ReadonlyArray<{ document: CmsDocument }>): PageSummary[] {\n return records.map(({ document }) => {\n const name = document.values?.['name']\n return { id: document.id, name: typeof name === 'string' && name.length > 0 ? name : document.id }\n })\n}\n\n/** The page-label load result. A read failure is non-fatal (EC-207): the routes\n * still render; the labels fall back to ids and the structured error surfaces in\n * a dismissible banner. So the failure rides IN the data, not as a thrown query\n * error that would collapse the surface. */\ninterface PageLabelData {\n pages: PageSummary[]\n error: OperationError | null\n}\n\nasync function loadPageLabels(\n operations: OperationsClient,\n projectId: string,\n): Promise<PageLabelData> {\n const result = await operations.documents.list({ projectId, collection: PAGE_COLLECTION })\n if (result.ok) return { pages: pageSummaries(result.value), error: null }\n return { pages: [], error: result.error }\n}\n\n/**\n * The Routes surface (EC-170, made read-only by EC-207 \"routes as code\"):\n * every route and redirect of the project, with the engine's structured issues\n * (route conflicts, redirect loops) rendered inline. Routes and redirects are\n * authored in `elytra.config.ts` and read from the resolved config project\n * (`resolveDeploymentRoutes`/`resolveDeploymentRedirects`) — NEVER from the\n * backend. This surface only views them; conflict/loop detection comes from the\n * canonical `@elytracms/core/cms-core` router, never from parallel UI-side logic. The\n * page list (CMS `page` collection) is read only to label route targets.\n */\nexport function RoutesShell({ project }: { project: Project }) {\n const projectId = project.id\n\n // Routes & redirects are config-sourced (EC-207): resolved synchronously from\n // the active `elytra.config.ts` project, never fetched from the backend.\n const routes = useMemo(() => resolveDeploymentRoutes(), [])\n const redirects = useMemo(() => resolveDeploymentRedirects(), [])\n\n // Page documents feed the route target labels (read-only display), read through\n // the one operations-backed query cache (EC-308/AD-17) and kept fresh as\n // documents change. A failure is non-fatal — see `loadPageLabels`.\n const pagesQuery = useOperationQuery<PageLabelData>({\n queryKey: ['routePageLabels', projectId],\n loader: (operations) => loadPageLabels(operations, projectId),\n invalidateOn: PAGE_LABEL_RELOAD_OPS,\n })\n const pages = pagesQuery.data?.pages ?? EMPTY_PAGES\n const loadError = pagesQuery.data?.error ?? null\n\n // The engine: one router over the config-resolved records. Its structured\n // issues are the single source of conflict/loop truth for this surface.\n const router = useMemo(\n () =>\n createRouter(routes, redirects, {\n defaultLocale: project.settings.defaultLocale,\n }),\n [routes, redirects, project.settings.defaultLocale],\n )\n const conflicts = useMemo(() => conflictsByRoute(router.issues), [router])\n\n return (\n <Stack gap=\"none\" className=\"min-h-full\" data-testid=\"routes-shell\">\n <Row gap=\"3\" className=\"shrink-0 border-b border-border px-4 py-3 sm:px-6\">\n <Stack gap=\"1\">\n <Heading level={2} size=\"h3\">\n Routes &amp; redirects\n </Heading>\n <Text size=\"xs\" tone=\"muted\">\n Routes &amp; redirects are defined in <code>elytra.config.ts</code>. Conflicts and loops\n are detected by the canonical route engine — never silently.\n </Text>\n </Stack>\n <Spacer />\n {loadError ? (\n <OperationErrorBanner error={loadError} onDismiss={() => void pagesQuery.refetch()} />\n ) : null}\n {router.issues.length > 0 ? (\n <Badge variant=\"error\" data-testid=\"routing-issue-count\">\n {router.issues.length} issue{router.issues.length === 1 ? '' : 's'}\n </Badge>\n ) : (\n <Badge variant=\"success\">no issues</Badge>\n )}\n </Row>\n\n <div className=\"grid min-h-0 flex-1 grid-cols-1 xl:grid-cols-[minmax(0,5fr)_minmax(0,4fr)]\">\n <section\n aria-label=\"Routes\"\n className=\"min-w-0 border-b border-border xl:border-b-0 xl:border-r\"\n >\n <RouteList projectId={projectId} routes={routes} conflicts={conflicts} pages={pages} />\n </section>\n <section aria-label=\"Redirects\" className=\"min-w-0\">\n <RedirectManager redirects={redirects} followRedirects={router.followRedirects} />\n </section>\n </div>\n </Stack>\n )\n}\n"]}
@@ -1,37 +0,0 @@
1
- import type { LucideIcon } from 'lucide-react';
2
- import type { Project, SectionAvailability, SectionAvailabilityMap, SectionId } from '@elytracms/core/studio-core';
3
- /**
4
- * App-level navigation ids: the studio-core sections plus surfaces registered
5
- * by the app itself. `routes` (EC-170) is the first app-registered surface —
6
- * it lives next to the CMS in the rail and rides on the CMS availability,
7
- * since routes/redirects are CMS routing concerns (`@elytracms/core/cms-core`).
8
- */
9
- export type StudioNavId = SectionId | 'routes';
10
- export interface SectionNavItem {
11
- id: StudioNavId;
12
- label: string;
13
- description: string;
14
- icon: LucideIcon;
15
- }
16
- /** Navigation items in canonical order (brief §8.1 + EC-170). */
17
- export declare const SECTION_NAV_ITEMS: readonly SectionNavItem[];
18
- export declare function sectionNavItem(id: StudioNavId): SectionNavItem;
19
- /**
20
- * Availability for a nav id. App-registered surfaces map onto the section
21
- * whose data they manage: Routes follows the CMS availability.
22
- */
23
- export declare function navAvailabilityOf(map: SectionAvailabilityMap, id: StudioNavId): SectionAvailability;
24
- /**
25
- * The section a project should open to: the first non-`unavailable` section in
26
- * canonical order. Settings is always available, so this never returns null.
27
- */
28
- export declare function firstAvailableSection(project: Project): SectionId;
29
- /**
30
- * Parse the active nav id out of a studio pathname `/$projectId/<section>[/...]`
31
- * (incl. the app-registered Routes surface, EC-170). Returns null for the bare
32
- * project URL or an unknown segment. The section is the SECOND path segment —
33
- * directly after the projectId; there is no `/p` prefix (EC-220). The single
34
- * source of truth for active-section detection: the shell breadcrumb and the
35
- * nav rail both read it, so the segment index lives in exactly one place.
36
- */
37
- export declare function activeSectionFromPath(pathname: string): StudioNavId | null;
@@ -1,62 +0,0 @@
1
- import { Blocks, Database, Image, Puzzle, Settings, Signpost } from 'lucide-react';
2
- import { SECTIONS, SECTION_IDS, availabilityOf, sectionIdSchema } from '@elytracms/core/studio-core';
3
- const ICONS = {
4
- components: Blocks,
5
- cms: Database,
6
- routes: Signpost,
7
- assets: Image,
8
- extensions: Puzzle,
9
- settings: Settings,
10
- };
11
- /** Descriptor for the app-registered Routes surface (EC-170). */
12
- const ROUTES_DESCRIPTOR = {
13
- id: 'routes',
14
- label: 'Routes',
15
- description: 'View the resolved URL routes & redirects (defined in elytra.config.ts).',
16
- };
17
- /** Canonical nav order: studio-core order with Routes slotted after CMS. */
18
- const NAV_IDS = SECTION_IDS.flatMap((id) => id === 'cms' ? [id, 'routes'] : [id]);
19
- function descriptorOf(id) {
20
- return id === 'routes' ? ROUTES_DESCRIPTOR : SECTIONS[id];
21
- }
22
- /** Navigation items in canonical order (brief §8.1 + EC-170). */
23
- export const SECTION_NAV_ITEMS = NAV_IDS.map((id) => ({
24
- id,
25
- label: descriptorOf(id).label,
26
- description: descriptorOf(id).description,
27
- icon: ICONS[id],
28
- }));
29
- export function sectionNavItem(id) {
30
- return SECTION_NAV_ITEMS.find((item) => item.id === id);
31
- }
32
- /**
33
- * Availability for a nav id. App-registered surfaces map onto the section
34
- * whose data they manage: Routes follows the CMS availability.
35
- */
36
- export function navAvailabilityOf(map, id) {
37
- return availabilityOf(map, id === 'routes' ? 'cms' : id);
38
- }
39
- /**
40
- * The section a project should open to: the first non-`unavailable` section in
41
- * canonical order. Settings is always available, so this never returns null.
42
- */
43
- export function firstAvailableSection(project) {
44
- const found = SECTION_IDS.find((id) => availabilityOf(project.settings.sectionAvailability, id) !== 'unavailable');
45
- return found ?? 'settings';
46
- }
47
- /**
48
- * Parse the active nav id out of a studio pathname `/$projectId/<section>[/...]`
49
- * (incl. the app-registered Routes surface, EC-170). Returns null for the bare
50
- * project URL or an unknown segment. The section is the SECOND path segment —
51
- * directly after the projectId; there is no `/p` prefix (EC-220). The single
52
- * source of truth for active-section detection: the shell breadcrumb and the
53
- * nav rail both read it, so the segment index lives in exactly one place.
54
- */
55
- export function activeSectionFromPath(pathname) {
56
- const segment = pathname.split('/')[2] ?? '';
57
- if (segment === 'routes')
58
- return 'routes';
59
- const parsed = sectionIdSchema.safeParse(segment);
60
- return parsed.success ? parsed.data : null;
61
- }
62
- //# sourceMappingURL=sections.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/components/sections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAElF,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAuBpG,MAAM,KAAK,GAAoC;IAC7C,UAAU,EAAE,MAAM;IAClB,GAAG,EAAE,QAAQ;IACb,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,KAAK;IACb,UAAU,EAAE,MAAM;IAClB,QAAQ,EAAE,QAAQ;CACnB,CAAA;AAED,iEAAiE;AACjE,MAAM,iBAAiB,GAAG;IACxB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,QAAQ;IACf,WAAW,EAAE,yEAAyE;CAC9E,CAAA;AAEV,4EAA4E;AAC5E,MAAM,OAAO,GAA2B,WAAW,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CACjE,EAAE,KAAK,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,EAAE,QAAQ,CAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAChD,CAAA;AAED,SAAS,YAAY,CAAC,EAAe;IACnC,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AAC3D,CAAC;AAED,iEAAiE;AACjE,MAAM,CAAC,MAAM,iBAAiB,GAA8B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/E,EAAE;IACF,KAAK,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,KAAK;IAC7B,WAAW,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,WAAW;IACzC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;CAChB,CAAC,CAAC,CAAA;AAEH,MAAM,UAAU,cAAc,CAAC,EAAe;IAC5C,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAE,CAAA;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAA2B,EAC3B,EAAe;IAEf,OAAO,cAAc,CAAC,GAAG,EAAE,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAC5B,CAAC,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,EAAE,EAAE,CAAC,KAAK,aAAa,CACnF,CAAA;IACD,OAAO,KAAK,IAAI,UAAU,CAAA;AAC5B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAC5C,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAA;IACzC,MAAM,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACjD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5C,CAAC","sourcesContent":["import { Blocks, Database, Image, Puzzle, Settings, Signpost } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { SECTIONS, SECTION_IDS, availabilityOf, sectionIdSchema } from '@elytracms/core/studio-core'\nimport type {\n Project,\n SectionAvailability,\n SectionAvailabilityMap,\n SectionId,\n} from '@elytracms/core/studio-core'\n\n/**\n * App-level navigation ids: the studio-core sections plus surfaces registered\n * by the app itself. `routes` (EC-170) is the first app-registered surface —\n * it lives next to the CMS in the rail and rides on the CMS availability,\n * since routes/redirects are CMS routing concerns (`@elytracms/core/cms-core`).\n */\nexport type StudioNavId = SectionId | 'routes'\n\nexport interface SectionNavItem {\n id: StudioNavId\n label: string\n description: string\n icon: LucideIcon\n}\n\nconst ICONS: Record<StudioNavId, LucideIcon> = {\n components: Blocks,\n cms: Database,\n routes: Signpost,\n assets: Image,\n extensions: Puzzle,\n settings: Settings,\n}\n\n/** Descriptor for the app-registered Routes surface (EC-170). */\nconst ROUTES_DESCRIPTOR = {\n id: 'routes',\n label: 'Routes',\n description: 'View the resolved URL routes & redirects (defined in elytra.config.ts).',\n} as const\n\n/** Canonical nav order: studio-core order with Routes slotted after CMS. */\nconst NAV_IDS: readonly StudioNavId[] = SECTION_IDS.flatMap((id) =>\n id === 'cms' ? ([id, 'routes'] as const) : [id],\n)\n\nfunction descriptorOf(id: StudioNavId): { label: string; description: string } {\n return id === 'routes' ? ROUTES_DESCRIPTOR : SECTIONS[id]\n}\n\n/** Navigation items in canonical order (brief §8.1 + EC-170). */\nexport const SECTION_NAV_ITEMS: readonly SectionNavItem[] = NAV_IDS.map((id) => ({\n id,\n label: descriptorOf(id).label,\n description: descriptorOf(id).description,\n icon: ICONS[id],\n}))\n\nexport function sectionNavItem(id: StudioNavId): SectionNavItem {\n return SECTION_NAV_ITEMS.find((item) => item.id === id)!\n}\n\n/**\n * Availability for a nav id. App-registered surfaces map onto the section\n * whose data they manage: Routes follows the CMS availability.\n */\nexport function navAvailabilityOf(\n map: SectionAvailabilityMap,\n id: StudioNavId,\n): SectionAvailability {\n return availabilityOf(map, id === 'routes' ? 'cms' : id)\n}\n\n/**\n * The section a project should open to: the first non-`unavailable` section in\n * canonical order. Settings is always available, so this never returns null.\n */\nexport function firstAvailableSection(project: Project): SectionId {\n const found = SECTION_IDS.find(\n (id) => availabilityOf(project.settings.sectionAvailability, id) !== 'unavailable',\n )\n return found ?? 'settings'\n}\n\n/**\n * Parse the active nav id out of a studio pathname `/$projectId/<section>[/...]`\n * (incl. the app-registered Routes surface, EC-170). Returns null for the bare\n * project URL or an unknown segment. The section is the SECOND path segment —\n * directly after the projectId; there is no `/p` prefix (EC-220). The single\n * source of truth for active-section detection: the shell breadcrumb and the\n * nav rail both read it, so the segment index lives in exactly one place.\n */\nexport function activeSectionFromPath(pathname: string): StudioNavId | null {\n const segment = pathname.split('/')[2] ?? ''\n if (segment === 'routes') return 'routes'\n const parsed = sectionIdSchema.safeParse(segment)\n return parsed.success ? parsed.data : null\n}\n"]}
@@ -1,8 +0,0 @@
1
- import type { UserRecord } from '@elytracms/core/persistence';
2
- /**
3
- * Free-text filter over the studio users roster (EC-240, 240b) — matches a query
4
- * against a user's name, email, id, or any of their roles, case-insensitively.
5
- * Pure + deterministic (mirrors `asset-search.ts`), so the roster's search state
6
- * is reproducible from fixtures.
7
- */
8
- export declare function filterUsers(users: readonly UserRecord[], query: string): readonly UserRecord[];
@@ -1,13 +0,0 @@
1
- /**
2
- * Free-text filter over the studio users roster (EC-240, 240b) — matches a query
3
- * against a user's name, email, id, or any of their roles, case-insensitively.
4
- * Pure + deterministic (mirrors `asset-search.ts`), so the roster's search state
5
- * is reproducible from fixtures.
6
- */
7
- export function filterUsers(users, query) {
8
- const q = query.trim().toLowerCase();
9
- if (!q)
10
- return users;
11
- return users.filter((user) => [user.name ?? '', user.email, user.id, ...user.roles].some((field) => field.toLowerCase().includes(q)));
12
- }
13
- //# sourceMappingURL=user-search.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"user-search.js","sourceRoot":"","sources":["../../../src/components/users/user-search.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAA4B,EAAE,KAAa;IACrE,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACpC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IACpB,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAC3B,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACnE,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAChC,CACF,CAAA;AACH,CAAC","sourcesContent":["import type { UserRecord } from '@elytracms/core/persistence'\n\n/**\n * Free-text filter over the studio users roster (EC-240, 240b) — matches a query\n * against a user's name, email, id, or any of their roles, case-insensitively.\n * Pure + deterministic (mirrors `asset-search.ts`), so the roster's search state\n * is reproducible from fixtures.\n */\nexport function filterUsers(users: readonly UserRecord[], query: string): readonly UserRecord[] {\n const q = query.trim().toLowerCase()\n if (!q) return users\n return users.filter((user) =>\n [user.name ?? '', user.email, user.id, ...user.roles].some((field) =>\n field.toLowerCase().includes(q),\n ),\n )\n}\n"]}
@@ -1,12 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- /**
3
- * Workspace-config boot gate (EC-154) — sibling of the EC-153 `BackendGate`.
4
- * The workspace config is static boot input (bundled `elytra.config.ts` or
5
- * env-injected JSON), so an *invalid* config is a deterministic boot-error
6
- * state rendered before auth and the studio store: structured, file-located
7
- * issues — never a silent fall-back to unconfigured behavior. No config and a
8
- * valid config render children untouched.
9
- */
10
- export declare function WorkspaceGate({ children }: {
11
- children: ReactNode;
12
- }): import("react").JSX.Element;
@@ -1,28 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Card, CardContent, CardHeader, CardTitle, Code, Stack, Text } from '@elytracms/studio/ui';
3
- import { getWorkspaceState } from '@elytracms/studio/lib/workspace';
4
- import { translate } from '@elytracms/studio/lib/i18n';
5
- /**
6
- * Workspace-config boot gate (EC-154) — sibling of the EC-153 `BackendGate`.
7
- * The workspace config is static boot input (bundled `elytra.config.ts` or
8
- * env-injected JSON), so an *invalid* config is a deterministic boot-error
9
- * state rendered before auth and the studio store: structured, file-located
10
- * issues — never a silent fall-back to unconfigured behavior. No config and a
11
- * valid config render children untouched.
12
- */
13
- export function WorkspaceGate({ children }) {
14
- const workspace = getWorkspaceState();
15
- if (workspace.kind === 'invalid')
16
- return _jsx(WorkspaceConfigErrorScreen, { workspace: workspace });
17
- return _jsx(_Fragment, { children: children });
18
- }
19
- function WorkspaceConfigErrorScreen({ workspace, }) {
20
- return (_jsx("div", { className: "grid min-h-dvh place-items-center bg-background p-6", children: _jsxs(Card, { className: "max-w-2xl border-destructive/40", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: translate('shell.workspaceGate.title') }) }), _jsx(CardContent, { children: _jsxs(Stack, { gap: "3", children: [_jsxs(Text, { size: "sm", tone: "muted", children: ["The studio loaded a workspace config from ", _jsx(Code, { children: workspace.source }), ", but it failed validation. Fix the issues below and reload \u2014 the studio never falls back to unconfigured behavior while a config is present."] }), _jsx(Stack, { gap: "2", role: "list", "aria-label": translate('shell.workspaceGate.issuesLabel'), children: workspace.issues.map((issue, index) => (_jsx(WorkspaceIssueLine, { code: issue.code, message: issue.message, location: formatIssuePath(issue.path) }, `${issue.code}-${index}`))) }), _jsxs(Text, { size: "xs", tone: "muted", children: ["Validate locally with ", _jsx(Code, { children: "builder init --project <slug>" }), " \u2014 the CLI runs the same schema and reports the same issues."] })] }) })] }) }));
21
- }
22
- function WorkspaceIssueLine({ code, message, location, }) {
23
- return (_jsxs("div", { role: "listitem", className: "rounded-md border border-destructive/30 bg-destructive/5 p-2", children: [_jsxs(Text, { size: "xs", mono: true, className: "text-destructive", children: ["[", code, "]", location ? ` at ${location}` : ''] }), _jsx(Text, { size: "sm", children: message })] }));
24
- }
25
- function formatIssuePath(path) {
26
- return path.reduce((acc, part) => (typeof part === 'number' ? `${acc}[${part}]` : acc ? `${acc}.${part}` : part), '');
27
- }
28
- //# sourceMappingURL=workspace-gate.js.map