@elytracms/studio 0.0.14 → 0.0.16

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 (143) hide show
  1. package/convex/cms.ts +8 -31
  2. package/convex/content.ts +3 -44
  3. package/convex/delivery.ts +8 -42
  4. package/convex/functions.ts +17 -2
  5. package/convex/members.ts +2 -2
  6. package/convex/referenceMaintenance.ts +4 -73
  7. package/convex/references.ts +4 -2
  8. package/convex/schema.ts +17 -37
  9. package/convex/sync.ts +10 -42
  10. package/convex/triggers.ts +57 -0
  11. package/convex/webhooks.ts +3 -2
  12. package/dist/components/app-views/default-modules.d.ts +41 -0
  13. package/dist/components/app-views/default-modules.js +201 -0
  14. package/dist/components/app-views/default-modules.js.map +1 -0
  15. package/dist/components/app-views/module.d.ts +93 -0
  16. package/dist/components/app-views/module.js +75 -0
  17. package/dist/components/app-views/module.js.map +1 -0
  18. package/dist/components/app-views/sidebar.d.ts +95 -0
  19. package/dist/components/app-views/sidebar.js +153 -0
  20. package/dist/components/app-views/sidebar.js.map +1 -0
  21. package/dist/components/builder/builder-store.d.ts +92 -40
  22. package/dist/components/builder/builder-store.js +30 -126
  23. package/dist/components/builder/builder-store.js.map +1 -1
  24. package/dist/components/builder/canvas-breakpoints.d.ts +53 -0
  25. package/dist/components/builder/canvas-breakpoints.js +55 -0
  26. package/dist/components/builder/canvas-breakpoints.js.map +1 -0
  27. package/dist/components/builder/canvas.js +35 -61
  28. package/dist/components/builder/canvas.js.map +1 -1
  29. package/dist/components/builder/clipboard-actions.js +3 -3
  30. package/dist/components/builder/clipboard-actions.js.map +1 -1
  31. package/dist/components/builder/composition-field-editor.js +31 -40
  32. package/dist/components/builder/composition-field-editor.js.map +1 -1
  33. package/dist/components/builder/insert-context.js +1 -1
  34. package/dist/components/builder/insert-context.js.map +1 -1
  35. package/dist/components/builder/page-tree.js +3 -4
  36. package/dist/components/builder/page-tree.js.map +1 -1
  37. package/dist/components/builder/property-inspector.js +4 -64
  38. package/dist/components/builder/property-inspector.js.map +1 -1
  39. package/dist/components/cms/bulk-actions.d.ts +23 -5
  40. package/dist/components/cms/bulk-actions.js +7 -3
  41. package/dist/components/cms/bulk-actions.js.map +1 -1
  42. package/dist/components/cms/bulk-bar.d.ts +22 -6
  43. package/dist/components/cms/bulk-bar.js +46 -9
  44. package/dist/components/cms/bulk-bar.js.map +1 -1
  45. package/dist/components/cms/cell.d.ts +0 -4
  46. package/dist/components/cms/cell.js +1 -5
  47. package/dist/components/cms/cell.js.map +1 -1
  48. package/dist/components/cms/cms-index.d.ts +4 -2
  49. package/dist/components/cms/cms-index.js +6 -4
  50. package/dist/components/cms/cms-index.js.map +1 -1
  51. package/dist/components/cms/cms-store.d.ts +7 -6
  52. package/dist/components/cms/cms-store.js +55 -18
  53. package/dist/components/cms/cms-store.js.map +1 -1
  54. package/dist/components/cms/collection-table.js +8 -4
  55. package/dist/components/cms/collection-table.js.map +1 -1
  56. package/dist/components/cms/collections-sidebar.d.ts +10 -1
  57. package/dist/components/cms/collections-sidebar.js +13 -2
  58. package/dist/components/cms/collections-sidebar.js.map +1 -1
  59. package/dist/components/cms/detail-editor.js +2 -2
  60. package/dist/components/cms/detail-editor.js.map +1 -1
  61. package/dist/components/cms/doc-state.d.ts +0 -5
  62. package/dist/components/cms/doc-state.js +1 -7
  63. package/dist/components/cms/doc-state.js.map +1 -1
  64. package/dist/components/cms/editable-cell.js +1 -1
  65. package/dist/components/cms/editable-cell.js.map +1 -1
  66. package/dist/components/cms/field-control.d.ts +11 -2
  67. package/dist/components/cms/field-control.js +22 -1
  68. package/dist/components/cms/field-control.js.map +1 -1
  69. package/dist/components/cms/field-renderer.d.ts +71 -0
  70. package/dist/components/cms/field-renderer.js +67 -0
  71. package/dist/components/cms/field-renderer.js.map +1 -0
  72. package/dist/components/cms/filter-builder.js +6 -28
  73. package/dist/components/cms/filter-builder.js.map +1 -1
  74. package/dist/components/cms/scalar-value-editor.d.ts +35 -0
  75. package/dist/components/cms/scalar-value-editor.js +43 -0
  76. package/dist/components/cms/scalar-value-editor.js.map +1 -0
  77. package/dist/components/collab/collab-context.d.ts +6 -14
  78. package/dist/components/collab/collab-context.js +3 -12
  79. package/dist/components/collab/collab-context.js.map +1 -1
  80. package/dist/components/collab/collab-seed.d.ts +7 -19
  81. package/dist/components/collab/collab-seed.js +9 -31
  82. package/dist/components/collab/collab-seed.js.map +1 -1
  83. package/dist/components/components/registry-browser.js +6 -12
  84. package/dist/components/components/registry-browser.js.map +1 -1
  85. package/dist/components/guards.d.ts +0 -9
  86. package/dist/components/guards.js +0 -12
  87. package/dist/components/guards.js.map +1 -1
  88. package/dist/components/project-session.js +8 -3
  89. package/dist/components/project-session.js.map +1 -1
  90. package/dist/components/section-nav.d.ts +10 -10
  91. package/dist/components/section-nav.js +48 -25
  92. package/dist/components/section-nav.js.map +1 -1
  93. package/dist/components/section-state.d.ts +0 -2
  94. package/dist/components/section-state.js +0 -9
  95. package/dist/components/section-state.js.map +1 -1
  96. package/dist/components/section-surface.d.ts +3 -7
  97. package/dist/components/section-surface.js +6 -14
  98. package/dist/components/section-surface.js.map +1 -1
  99. package/dist/components/sections.d.ts +0 -1
  100. package/dist/components/sections.js +0 -10
  101. package/dist/components/sections.js.map +1 -1
  102. package/dist/components/studio-project-screen.d.ts +3 -4
  103. package/dist/components/studio-project-screen.js +41 -160
  104. package/dist/components/studio-project-screen.js.map +1 -1
  105. package/dist/components/studio-shell.d.ts +0 -4
  106. package/dist/components/studio-shell.js +14 -14
  107. package/dist/components/studio-shell.js.map +1 -1
  108. package/dist/lib/data/collection-content.d.ts +19 -1
  109. package/dist/lib/data/collection-content.js +16 -0
  110. package/dist/lib/data/collection-content.js.map +1 -1
  111. package/dist/lib/operations/graph-commands.d.ts +2 -6
  112. package/dist/lib/operations/graph-commands.js +1 -2
  113. package/dist/lib/operations/graph-commands.js.map +1 -1
  114. package/dist/lib/operations/index.d.ts +0 -2
  115. package/dist/lib/operations/index.js +0 -2
  116. package/dist/lib/operations/index.js.map +1 -1
  117. package/dist/lib/operations/session-hydration.d.ts +14 -24
  118. package/dist/lib/operations/session-hydration.js +15 -34
  119. package/dist/lib/operations/session-hydration.js.map +1 -1
  120. package/dist/lib/persistence/convex-adapter.d.ts +1 -3
  121. package/dist/lib/persistence/convex-adapter.js +1 -154
  122. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  123. package/dist/lib/persona/labels.js +0 -2
  124. package/dist/lib/persona/labels.js.map +1 -1
  125. package/dist/lib/references/reference-model.d.ts +0 -16
  126. package/dist/lib/references/reference-model.js +2 -54
  127. package/dist/lib/references/reference-model.js.map +1 -1
  128. package/dist/lib/references/references-store.js +3 -4
  129. package/dist/lib/references/references-store.js.map +1 -1
  130. package/dist/lib/registry/index.d.ts +0 -16
  131. package/dist/lib/registry/index.js +0 -15
  132. package/dist/lib/registry/index.js.map +1 -1
  133. package/dist/lib/webhooks/tags.js +2 -16
  134. package/dist/lib/webhooks/tags.js.map +1 -1
  135. package/dist/lib/workspace/index.d.ts +27 -1
  136. package/dist/lib/workspace/index.js +34 -0
  137. package/dist/lib/workspace/index.js.map +1 -1
  138. package/dist/router/match.d.ts +22 -34
  139. package/dist/router/match.js +22 -40
  140. package/dist/router/match.js.map +1 -1
  141. package/package.json +3 -2
  142. package/convex/graphs.ts +0 -55
  143. package/convex/publishing.ts +0 -76
@@ -1,26 +1,18 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useMemo, useReducer, useRef } from 'react';
3
3
  import { Collaboration, createSequentialClock, createSequentialIds } from '@elytracms/core/collaboration';
4
- import { serializeProjectGraph } from '@elytracms/core/project-graph';
5
4
  import { seedCollabSession } from './collab-seed';
6
5
  const CollabContext = createContext(null);
7
6
  const ME = { id: 'u-you', name: 'You' };
8
- const COLLABORATOR = { id: 'u-grace', name: 'Grace Hopper' };
9
- export function CollabProvider({ projectId, graph, publish, children, }) {
7
+ export function CollabProvider({ projectId, children, }) {
10
8
  const [tick, bump] = useReducer((n) => n + 1, 0);
11
9
  const [previewMode, setPreviewModeState] = useReducer((_, next) => next, 'draft');
12
10
  const ref = useRef(null);
13
11
  if (ref.current === null) {
14
12
  const collab = new Collaboration({ clock: createSequentialClock(), ids: createSequentialIds() });
15
- // Seed: an initial version from the current graph (+ the hydrated persisted
16
- // publish in Convex mode) and presence for two participants (EC-122/126).
17
- seedCollabSession(collab, projectId, {
18
- snapshot: serializeProjectGraph(graph),
19
- author: ME.name,
20
- ...(publish !== undefined ? { publish } : {}),
21
- });
13
+ // Seed one empty Initial version + presence for the current user (EC-122/126).
14
+ seedCollabSession(collab, projectId, { author: ME.name });
22
15
  collab.presence.join(projectId, ME);
23
- collab.presence.join(projectId, COLLABORATOR);
24
16
  ref.current = collab;
25
17
  }
26
18
  const store = useMemo(() => {
@@ -48,5 +40,4 @@ export function useCollab() {
48
40
  throw new Error('useCollab must be used within a <CollabProvider>.');
49
41
  return store;
50
42
  }
51
- export { COLLABORATOR };
52
43
  //# sourceMappingURL=collab-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"collab-context.js","sourceRoot":"","sources":["../../../src/components/collab/collab-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAE9E,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAEzG,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AAGrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAsBjD,MAAM,aAAa,GAAG,aAAa,CAAqB,IAAI,CAAC,CAAA;AAE7D,MAAM,EAAE,GAAU,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AAC9C,MAAM,YAAY,GAAU,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,CAAA;AAEnE,MAAM,UAAU,cAAc,CAAC,EAC7B,SAAS,EACT,KAAK,EACL,OAAO,EACP,QAAQ,GAOT;IACC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IACxD,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC,GAAG,UAAU,CACnD,CAAC,CAAc,EAAE,IAAiB,EAAE,EAAE,CAAC,IAAI,EAC3C,OAAsB,CACvB,CAAA;IAED,MAAM,GAAG,GAAG,MAAM,CAAuB,IAAI,CAAC,CAAA;IAC9C,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,EAAE,mBAAmB,EAAE,EAAE,CAAC,CAAA;QAChG,4EAA4E;QAC5E,0EAA0E;QAC1E,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE;YACnC,QAAQ,EAAE,qBAAqB,CAAC,KAAK,CAAC;YACtC,MAAM,EAAE,EAAE,CAAC,IAAI;YACf,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9C,CAAC,CAAA;QACF,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QACnC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;QAC7C,GAAG,CAAC,OAAO,GAAG,MAAM,CAAA;IACtB,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAc,GAAG,EAAE;QACtC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAQ,CAAA;QAC3B,OAAO;YACL,MAAM;YACN,SAAS;YACT,EAAE,EAAE,EAAE;YACN,WAAW;YACX,cAAc,EAAE,mBAAmB;YACnC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE;gBACb,MAAM,MAAM,GAAG,EAAE,EAAE,CAAA;gBACnB,IAAI,EAAE,CAAA;gBACN,OAAO,MAAM,CAAA;YACf,CAAC;SACF,CAAA;QACD,yEAAyE;QACzE,oEAAoE;IACtE,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC,CAAA;IAElC,OAAO,KAAC,aAAa,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA0B,CAAA;AAClF,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,MAAM,KAAK,GAAG,UAAU,CAAC,aAAa,CAAC,CAAA;IACvC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IAChF,OAAO,KAAK,CAAA;AACd,CAAC;AAED,OAAO,EAAE,YAAY,EAAE,CAAA","sourcesContent":["import { createContext, useContext, useMemo, useReducer, useRef } from 'react'\nimport type { ReactNode } from 'react'\nimport { Collaboration, createSequentialClock, createSequentialIds } from '@elytracms/core/collaboration'\nimport type { Actor, PreviewMode } from '@elytracms/core/collaboration'\nimport { serializeProjectGraph } from '@elytracms/core/project-graph'\nimport type { ProjectGraph } from '@elytracms/core/project-graph'\nimport type { HydratedPublish } from '@elytracms/studio/lib/operations'\nimport { seedCollabSession } from './collab-seed'\n\n/**\n * Per-project collaboration session (Phase 10). Holds one {@link Collaboration} store seeded with\n * an initial version + demo participants, and exposes a `mutate` helper that re-renders consumers\n * after an imperative store change. Non-persistent (session), but in Convex mode the seed REFLECTS\n * the persisted publishing state (EC-177 gap 1): the hydrated `publish` becomes a version + publish\n * event, so the session shows what is actually live instead of \"No publishes yet\". A Convex backend\n * would supply live, multi-client transport behind the same store shape. Single-environment\n * (EC-179): a deployment IS one environment, so there is no environment selector.\n */\nexport interface CollabStore {\n collab: Collaboration\n projectId: string\n me: Actor\n /** Draft vs published perspective the canvas/preview renders (EC-123). */\n previewMode: PreviewMode\n setPreviewMode: (mode: PreviewMode) => void\n /** Run an imperative store mutation, then re-render consumers. */\n mutate: <T>(fn: () => T) => T\n}\n\nconst CollabContext = createContext<CollabStore | null>(null)\n\nconst ME: Actor = { id: 'u-you', name: 'You' }\nconst COLLABORATOR: Actor = { id: 'u-grace', name: 'Grace Hopper' }\n\nexport function CollabProvider({\n projectId,\n graph,\n publish,\n children,\n}: {\n projectId: string\n graph: ProjectGraph\n /** The persisted publish to reflect in the session seed (Convex mode, EC-177 gap 1). */\n publish?: HydratedPublish | null\n children: ReactNode\n}) {\n const [tick, bump] = useReducer((n: number) => n + 1, 0)\n const [previewMode, setPreviewModeState] = useReducer(\n (_: PreviewMode, next: PreviewMode) => next,\n 'draft' as PreviewMode,\n )\n\n const ref = useRef<Collaboration | null>(null)\n if (ref.current === null) {\n const collab = new Collaboration({ clock: createSequentialClock(), ids: createSequentialIds() })\n // Seed: an initial version from the current graph (+ the hydrated persisted\n // publish in Convex mode) and presence for two participants (EC-122/126).\n seedCollabSession(collab, projectId, {\n snapshot: serializeProjectGraph(graph),\n author: ME.name,\n ...(publish !== undefined ? { publish } : {}),\n })\n collab.presence.join(projectId, ME)\n collab.presence.join(projectId, COLLABORATOR)\n ref.current = collab\n }\n\n const store = useMemo<CollabStore>(() => {\n const collab = ref.current!\n return {\n collab,\n projectId,\n me: ME,\n previewMode,\n setPreviewMode: setPreviewModeState,\n mutate: (fn) => {\n const result = fn()\n bump()\n return result\n },\n }\n // `tick` (bumped by `mutate`) and `previewMode` are in deps so consumers\n // re-render after an imperative store mutation or a preview change.\n }, [projectId, previewMode, tick])\n\n return <CollabContext.Provider value={store}>{children}</CollabContext.Provider>\n}\n\nexport function useCollab(): CollabStore {\n const store = useContext(CollabContext)\n if (!store) throw new Error('useCollab must be used within a <CollabProvider>.')\n return store\n}\n\nexport { COLLABORATOR }\n"]}
1
+ {"version":3,"file":"collab-context.js","sourceRoot":"","sources":["../../../src/components/collab/collab-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAE9E,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAEzG,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAqBjD,MAAM,aAAa,GAAG,aAAa,CAAqB,IAAI,CAAC,CAAA;AAE7D,MAAM,EAAE,GAAU,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AAE9C,MAAM,UAAU,cAAc,CAAC,EAC7B,SAAS,EACT,QAAQ,GAIT;IACC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IACxD,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC,GAAG,UAAU,CACnD,CAAC,CAAc,EAAE,IAAiB,EAAE,EAAE,CAAC,IAAI,EAC3C,OAAsB,CACvB,CAAA;IAED,MAAM,GAAG,GAAG,MAAM,CAAuB,IAAI,CAAC,CAAA;IAC9C,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,EAAE,mBAAmB,EAAE,EAAE,CAAC,CAAA;QAChG,+EAA+E;QAC/E,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;QACzD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QACnC,GAAG,CAAC,OAAO,GAAG,MAAM,CAAA;IACtB,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAc,GAAG,EAAE;QACtC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAQ,CAAA;QAC3B,OAAO;YACL,MAAM;YACN,SAAS;YACT,EAAE,EAAE,EAAE;YACN,WAAW;YACX,cAAc,EAAE,mBAAmB;YACnC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE;gBACb,MAAM,MAAM,GAAG,EAAE,EAAE,CAAA;gBACnB,IAAI,EAAE,CAAA;gBACN,OAAO,MAAM,CAAA;YACf,CAAC;SACF,CAAA;QACD,yEAAyE;QACzE,oEAAoE;IACtE,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC,CAAA;IAElC,OAAO,KAAC,aAAa,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA0B,CAAA;AAClF,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,MAAM,KAAK,GAAG,UAAU,CAAC,aAAa,CAAC,CAAA;IACvC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IAChF,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["import { createContext, useContext, useMemo, useReducer, useRef } from 'react'\nimport type { ReactNode } from 'react'\nimport { Collaboration, createSequentialClock, createSequentialIds } from '@elytracms/core/collaboration'\nimport type { Actor, PreviewMode } from '@elytracms/core/collaboration'\nimport { seedCollabSession } from './collab-seed'\n\n/**\n * Per-project collaboration session (Phase 10). Holds one {@link Collaboration} store seeded with\n * an initial version + presence for the current user, and exposes a `mutate` helper that re-renders\n * consumers after an imperative store change. Non-persistent (session): it is the builder canvas's\n * session-scoped presence/perspective store. EC-287 retired the project graph and its publish path,\n * so the seed no longer reflects a pinned graph revision; content publishing is per document\n * (EC-224), surfaced by the CMS document Versions pane.\n */\nexport interface CollabStore {\n collab: Collaboration\n projectId: string\n me: Actor\n /** Draft vs published perspective the canvas/preview renders (EC-123). */\n previewMode: PreviewMode\n setPreviewMode: (mode: PreviewMode) => void\n /** Run an imperative store mutation, then re-render consumers. */\n mutate: <T>(fn: () => T) => T\n}\n\nconst CollabContext = createContext<CollabStore | null>(null)\n\nconst ME: Actor = { id: 'u-you', name: 'You' }\n\nexport function CollabProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const [tick, bump] = useReducer((n: number) => n + 1, 0)\n const [previewMode, setPreviewModeState] = useReducer(\n (_: PreviewMode, next: PreviewMode) => next,\n 'draft' as PreviewMode,\n )\n\n const ref = useRef<Collaboration | null>(null)\n if (ref.current === null) {\n const collab = new Collaboration({ clock: createSequentialClock(), ids: createSequentialIds() })\n // Seed one empty Initial version + presence for the current user (EC-122/126).\n seedCollabSession(collab, projectId, { author: ME.name })\n collab.presence.join(projectId, ME)\n ref.current = collab\n }\n\n const store = useMemo<CollabStore>(() => {\n const collab = ref.current!\n return {\n collab,\n projectId,\n me: ME,\n previewMode,\n setPreviewMode: setPreviewModeState,\n mutate: (fn) => {\n const result = fn()\n bump()\n return result\n },\n }\n // `tick` (bumped by `mutate`) and `previewMode` are in deps so consumers\n // re-render after an imperative store mutation or a preview change.\n }, [projectId, previewMode, tick])\n\n return <CollabContext.Provider value={store}>{children}</CollabContext.Provider>\n}\n\nexport function useCollab(): CollabStore {\n const store = useContext(CollabContext)\n if (!store) throw new Error('useCollab must be used within a <CollabProvider>.')\n return store\n}\n"]}
@@ -1,27 +1,15 @@
1
1
  import type { Collaboration } from '@elytracms/core/collaboration';
2
- import type { HydratedPublish } from '@elytracms/studio/lib/operations';
3
2
  /**
4
- * Seed a fresh per-project {@link Collaboration} session (EC-177 gap 1).
3
+ * Seed a fresh per-project {@link Collaboration} session.
5
4
  *
6
- * The collaboration store is session state over an in-memory engine, but in
7
- * Convex mode its starting point must REFLECT the persisted publishing state:
8
- * when a revision is pinned live the session shows as published (with a
9
- * matching version + history event), never as "No publishes yet".
10
- *
11
- * Order matters: the published-revision snapshot becomes a version FIRST, the
12
- * working draft ("Initial") is created LAST — `versions.latest()` (what
13
- * "Publish" pins) must stay the session's working graph. A publish whose
14
- * pinned snapshot equals the working graph reuses the Initial version, so
15
- * "live vN" and "current" agree when the latest revision is the one live.
16
- *
17
- * Without `publish` (playground mode, kit sessions) this seeds exactly the
18
- * pre-hydration session: one Initial version, nothing published.
5
+ * EC-287: the project graph and its publish path are retired (the DB holds
6
+ * content only), so a session no longer reflects a pinned graph revision. The
7
+ * collaboration store stays the builder's session-scoped presence/perspective
8
+ * store; it seeds one empty "Initial" version so `versions.latest()` is defined,
9
+ * with no graph snapshot and nothing published. Content publishing is per
10
+ * document (EC-224), surfaced by the CMS document Versions pane — not here.
19
11
  */
20
12
  export declare function seedCollabSession(collab: Collaboration, projectId: string, options: {
21
- /** Serialized working graph (the session's draft snapshot). */
22
- snapshot: string;
23
13
  /** Author recorded on the Initial version. */
24
14
  author: string;
25
- /** The persisted publish to reflect (hydrated; absent in playground). */
26
- publish?: HydratedPublish | null;
27
15
  }): void;
@@ -1,42 +1,20 @@
1
1
  /**
2
- * Seed a fresh per-project {@link Collaboration} session (EC-177 gap 1).
2
+ * Seed a fresh per-project {@link Collaboration} session.
3
3
  *
4
- * The collaboration store is session state over an in-memory engine, but in
5
- * Convex mode its starting point must REFLECT the persisted publishing state:
6
- * when a revision is pinned live the session shows as published (with a
7
- * matching version + history event), never as "No publishes yet".
8
- *
9
- * Order matters: the published-revision snapshot becomes a version FIRST, the
10
- * working draft ("Initial") is created LAST — `versions.latest()` (what
11
- * "Publish" pins) must stay the session's working graph. A publish whose
12
- * pinned snapshot equals the working graph reuses the Initial version, so
13
- * "live vN" and "current" agree when the latest revision is the one live.
14
- *
15
- * Without `publish` (playground mode, kit sessions) this seeds exactly the
16
- * pre-hydration session: one Initial version, nothing published.
4
+ * EC-287: the project graph and its publish path are retired (the DB holds
5
+ * content only), so a session no longer reflects a pinned graph revision. The
6
+ * collaboration store stays the builder's session-scoped presence/perspective
7
+ * store; it seeds one empty "Initial" version so `versions.latest()` is defined,
8
+ * with no graph snapshot and nothing published. Content publishing is per
9
+ * document (EC-224), surfaced by the CMS document Versions pane — not here.
17
10
  */
18
11
  export function seedCollabSession(collab, projectId, options) {
19
- const publish = options.publish ?? null;
20
- // A version for the published snapshot when it differs from the draft.
21
- let publishedVersionId;
22
- if (publish && publish.serializedGraph !== options.snapshot) {
23
- const version = collab.versions.create(projectId, {
24
- snapshot: publish.serializedGraph,
25
- label: `Revision ${publish.revision}`,
26
- message: `Published revision ${publish.revision} (loaded from the backend).`,
27
- origin: 'import',
28
- });
29
- publishedVersionId = version.id;
30
- }
31
- const initial = collab.versions.create(projectId, {
32
- snapshot: options.snapshot,
12
+ collab.versions.create(projectId, {
13
+ snapshot: '',
33
14
  label: 'Initial',
34
15
  message: 'Project opened.',
35
16
  author: options.author,
36
17
  origin: 'import',
37
18
  });
38
- if (publish) {
39
- collab.publishing.publish(projectId, publishedVersionId ?? initial.id);
40
- }
41
19
  }
42
20
  //# sourceMappingURL=collab-seed.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"collab-seed.js","sourceRoot":"","sources":["../../../src/components/collab/collab-seed.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAqB,EACrB,SAAiB,EACjB,OAOC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAA;IAEvC,uEAAuE;IACvE,IAAI,kBAAsC,CAAA;IAC1C,IAAI,OAAO,IAAI,OAAO,CAAC,eAAe,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC5D,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE;YAChD,QAAQ,EAAE,OAAO,CAAC,eAAe;YACjC,KAAK,EAAE,YAAY,OAAO,CAAC,QAAQ,EAAE;YACrC,OAAO,EAAE,sBAAsB,OAAO,CAAC,QAAQ,6BAA6B;YAC5E,MAAM,EAAE,QAAQ;SACjB,CAAC,CAAA;QACF,kBAAkB,GAAG,OAAO,CAAC,EAAE,CAAA;IACjC,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE;QAChD,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,iBAAiB;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,QAAQ;KACjB,CAAC,CAAA;IAEF,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,kBAAkB,IAAI,OAAO,CAAC,EAAE,CAAC,CAAA;IACxE,CAAC;AACH,CAAC","sourcesContent":["import type { Collaboration } from '@elytracms/core/collaboration'\nimport type { HydratedPublish } from '@elytracms/studio/lib/operations'\n\n/**\n * Seed a fresh per-project {@link Collaboration} session (EC-177 gap 1).\n *\n * The collaboration store is session state over an in-memory engine, but in\n * Convex mode its starting point must REFLECT the persisted publishing state:\n * when a revision is pinned live the session shows as published (with a\n * matching version + history event), never as \"No publishes yet\".\n *\n * Order matters: the published-revision snapshot becomes a version FIRST, the\n * working draft (\"Initial\") is created LAST — `versions.latest()` (what\n * \"Publish\" pins) must stay the session's working graph. A publish whose\n * pinned snapshot equals the working graph reuses the Initial version, so\n * \"live vN\" and \"current\" agree when the latest revision is the one live.\n *\n * Without `publish` (playground mode, kit sessions) this seeds exactly the\n * pre-hydration session: one Initial version, nothing published.\n */\nexport function seedCollabSession(\n collab: Collaboration,\n projectId: string,\n options: {\n /** Serialized working graph (the session's draft snapshot). */\n snapshot: string\n /** Author recorded on the Initial version. */\n author: string\n /** The persisted publish to reflect (hydrated; absent in playground). */\n publish?: HydratedPublish | null\n },\n): void {\n const publish = options.publish ?? null\n\n // A version for the published snapshot when it differs from the draft.\n let publishedVersionId: string | undefined\n if (publish && publish.serializedGraph !== options.snapshot) {\n const version = collab.versions.create(projectId, {\n snapshot: publish.serializedGraph,\n label: `Revision ${publish.revision}`,\n message: `Published revision ${publish.revision} (loaded from the backend).`,\n origin: 'import',\n })\n publishedVersionId = version.id\n }\n\n const initial = collab.versions.create(projectId, {\n snapshot: options.snapshot,\n label: 'Initial',\n message: 'Project opened.',\n author: options.author,\n origin: 'import',\n })\n\n if (publish) {\n collab.publishing.publish(projectId, publishedVersionId ?? initial.id)\n }\n}\n"]}
1
+ {"version":3,"file":"collab-seed.js","sourceRoot":"","sources":["../../../src/components/collab/collab-seed.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAqB,EACrB,SAAiB,EACjB,OAGC;IAED,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE;QAChC,QAAQ,EAAE,EAAE;QACZ,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,iBAAiB;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,QAAQ;KACjB,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { Collaboration } from '@elytracms/core/collaboration'\n\n/**\n * Seed a fresh per-project {@link Collaboration} session.\n *\n * EC-287: the project graph and its publish path are retired (the DB holds\n * content only), so a session no longer reflects a pinned graph revision. The\n * collaboration store stays the builder's session-scoped presence/perspective\n * store; it seeds one empty \"Initial\" version so `versions.latest()` is defined,\n * with no graph snapshot and nothing published. Content publishing is per\n * document (EC-224), surfaced by the CMS document Versions pane — not here.\n */\nexport function seedCollabSession(\n collab: Collaboration,\n projectId: string,\n options: {\n /** Author recorded on the Initial version. */\n author: string\n },\n): void {\n collab.versions.create(projectId, {\n snapshot: '',\n label: 'Initial',\n message: 'Project opened.',\n author: options.author,\n origin: 'import',\n })\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router';
3
- import { Search, Sparkles } from 'lucide-react';
3
+ import { Search } from 'lucide-react';
4
4
  import { useMemo, useState } from 'react';
5
5
  import { isPrimitive } from '@elytracms/core/component-registry';
6
- import { componentDrafts, componentStatus, getStudioRegistry, } from '@elytracms/studio/lib/registry';
7
- import { Badge, Code, Input, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
6
+ import { componentStatus, getStudioRegistry } from '@elytracms/studio/lib/registry';
7
+ import { Code, Input, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
8
8
  import { cn } from '@elytracms/studio/lib/utils';
9
9
  import { ComponentStatusDot } from './component-status';
10
10
  export function RegistryBrowser({ projectId, activeComponentId, }) {
@@ -35,9 +35,9 @@ export function RegistryBrowser({ projectId, activeComponentId, }) {
35
35
  }), [studio, query, namespace, category]);
36
36
  const primitives = results.filter((m) => isPrimitive(m.id));
37
37
  const projects = results.filter((m) => !isPrimitive(m.id));
38
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "shrink-0 space-y-2 border-b border-border p-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search components\u2026", className: "pl-8", "aria-label": "Search components" })] }), _jsxs(Row, { gap: "1", className: "rounded-md bg-muted/50 p-0.5", role: "group", "aria-label": "Filter by namespace", children: [_jsx(NamespaceTab, { label: "All", count: primitiveCount + projectCount, active: namespace === 'all', onClick: () => selectNamespace('all') }), _jsx(NamespaceTab, { label: "Primitives", count: primitiveCount, active: namespace === 'base.primitives', onClick: () => selectNamespace('base.primitives') }), _jsx(NamespaceTab, { label: "Project", count: projectCount, active: namespace === 'project', onClick: () => selectNamespace('project') })] }), categories.length > 0 ? (_jsxs(Row, { gap: "1", wrap: true, children: [_jsx(CategoryChip, { label: "All", active: category === null, onClick: () => setCategory(null) }), categories.map((c) => (_jsx(CategoryChip, { label: c, active: category === c, onClick: () => setCategory(c) }, c)))] })) : null] }), _jsxs("div", { className: "min-h-0 flex-1 overflow-auto p-2", children: [results.length === 0 ? (_jsx(Text, { size: "sm", tone: "muted", className: "p-3", children: query.trim()
39
- ? `No components match “${query}”.`
40
- : 'No components match the selected filters.' })) : (_jsxs(Stack, { gap: "3", children: [(namespace === 'all' || namespace === 'base.primitives') && primitives.length > 0 ? (_jsx(GroupedList, { heading: "base.primitives.*", count: primitives.length, manifests: primitives, projectId: projectId, activeComponentId: activeComponentId })) : null, (namespace === 'all' || namespace === 'project') && projects.length > 0 ? (_jsx(GroupedList, { heading: "project.*", count: projects.length, manifests: projects, projectId: projectId, activeComponentId: activeComponentId })) : null] })), namespace !== 'base.primitives' && !query && !category ? (_jsx(DraftSection, {})) : null] })] }));
38
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "shrink-0 space-y-2 border-b border-border p-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search components\u2026", className: "pl-8", "aria-label": "Search components" })] }), _jsxs(Row, { gap: "1", className: "rounded-md bg-muted/50 p-0.5", role: "group", "aria-label": "Filter by namespace", children: [_jsx(NamespaceTab, { label: "All", count: primitiveCount + projectCount, active: namespace === 'all', onClick: () => selectNamespace('all') }), _jsx(NamespaceTab, { label: "Primitives", count: primitiveCount, active: namespace === 'base.primitives', onClick: () => selectNamespace('base.primitives') }), _jsx(NamespaceTab, { label: "Project", count: projectCount, active: namespace === 'project', onClick: () => selectNamespace('project') })] }), categories.length > 0 ? (_jsxs(Row, { gap: "1", wrap: true, children: [_jsx(CategoryChip, { label: "All", active: category === null, onClick: () => setCategory(null) }), categories.map((c) => (_jsx(CategoryChip, { label: c, active: category === c, onClick: () => setCategory(c) }, c)))] })) : null] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-2", children: results.length === 0 ? (_jsx(Text, { size: "sm", tone: "muted", className: "p-3", children: query.trim()
39
+ ? `No components match “${query}”.`
40
+ : 'No components match the selected filters.' })) : (_jsxs(Stack, { gap: "3", children: [(namespace === 'all' || namespace === 'base.primitives') && primitives.length > 0 ? (_jsx(GroupedList, { heading: "base.primitives.*", count: primitives.length, manifests: primitives, projectId: projectId, activeComponentId: activeComponentId })) : null, (namespace === 'all' || namespace === 'project') && projects.length > 0 ? (_jsx(GroupedList, { heading: "project.*", count: projects.length, manifests: projects, projectId: projectId, activeComponentId: activeComponentId })) : null] })) })] }));
41
41
  }
42
42
  function NamespaceTab({ label, count, active, onClick, }) {
43
43
  return (_jsxs("button", { type: "button", "aria-pressed": active, onClick: onClick, className: cn('flex flex-1 items-center justify-center gap-1.5 rounded-[5px] px-2 py-1 text-xs font-medium transition-colors', active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'), children: [label, _jsx("span", { className: "font-mono text-[0.65rem] text-muted-foreground", children: count })] }));
@@ -55,10 +55,4 @@ function GroupedList({ heading, count, manifests, projectId, activeComponentId,
55
55
  return (_jsxs(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: studioPath.component(m.id) }, "aria-current": active ? 'page' : undefined, className: cn('flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors', active ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: [_jsx(ComponentStatusDot, { status: status }), _jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: m.title ?? m.id }), m.category ? (_jsx("span", { className: "shrink-0 text-[0.7rem] capitalize text-muted-foreground", children: m.category })) : null] }, m.id));
56
56
  }) })] }));
57
57
  }
58
- /** Read-only AI-draft hook points (EC-058). Inspectable, not opaque. */
59
- function DraftSection() {
60
- if (componentDrafts.length === 0)
61
- return null;
62
- return (_jsxs(Stack, { gap: "1", className: "mt-4", children: [_jsxs(Row, { gap: "2", className: "px-2", children: [_jsx(Code, { tone: "muted", children: "drafts" }), _jsx(Spacer, {}), _jsxs(Badge, { variant: "outline", children: [_jsx(Sparkles, { className: "size-3" }), " AI \u00B7 Phase 8"] })] }), _jsx(Stack, { gap: "1", children: componentDrafts.map((draft) => (_jsxs("div", { className: "rounded-md border border-dashed border-border px-2.5 py-2", children: [_jsxs(Row, { gap: "2", children: [_jsx(Text, { size: "sm", weight: "medium", children: draft.title }), _jsx(Spacer, {}), _jsx(Code, { tone: "muted", children: draft.manifestPreview.id })] }), _jsx(Text, { size: "xs", tone: "subtle", className: "mt-0.5", children: draft.prompt })] }, draft.id))) })] }));
63
- }
64
58
  //# sourceMappingURL=registry-browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry-browser.js","sourceRoot":"","sources":["../../../src/components/components/registry-browser.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { Search, Sparkles } from 'lucide-react'\nimport { useMemo, useState } from 'react'\nimport { isPrimitive } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport {\n componentDrafts,\n componentStatus,\n getStudioRegistry,\n} from '@elytracms/studio/lib/registry'\nimport { Badge, Code, Input, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ComponentStatusDot } from './component-status'\n\ntype NamespaceFilter = 'all' | 'base.primitives' | 'project'\n\nexport function RegistryBrowser({\n projectId,\n activeComponentId,\n}: {\n projectId: string\n activeComponentId?: string\n}) {\n const studio = getStudioRegistry()\n const [query, setQuery] = useState('')\n const [namespace, setNamespace] = useState<NamespaceFilter>('all')\n const [category, setCategory] = useState<string | null>(null)\n\n const primitiveCount = studio.registry.primitives().length\n const projectCount = studio.registry.projectComponents().length\n\n // Switching namespace clears any category that doesn't exist in the new one.\n function selectNamespace(ns: NamespaceFilter) {\n setNamespace(ns)\n setCategory(null)\n }\n\n // Categories are scoped to the active namespace so every chip can yield results.\n const categories = useMemo(() => {\n const set = new Set<string>()\n for (const m of studio.registry.list({ namespace: namespace === 'all' ? undefined : namespace })) {\n if (m.category) set.add(m.category)\n }\n return [...set].sort()\n }, [studio, namespace])\n\n const results = useMemo(\n () =>\n studio.registry.list({\n query: query.trim() || undefined,\n namespace: namespace === 'all' ? undefined : namespace,\n category: category ?? undefined,\n }),\n [studio, query, namespace, category],\n )\n\n const primitives = results.filter((m) => isPrimitive(m.id))\n const projects = results.filter((m) => !isPrimitive(m.id))\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 space-y-2 border-b border-border p-3\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search components…\"\n className=\"pl-8\"\n aria-label=\"Search components\"\n />\n </div>\n <Row gap=\"1\" className=\"rounded-md bg-muted/50 p-0.5\" role=\"group\" aria-label=\"Filter by namespace\">\n <NamespaceTab label=\"All\" count={primitiveCount + projectCount} active={namespace === 'all'} onClick={() => selectNamespace('all')} />\n <NamespaceTab label=\"Primitives\" count={primitiveCount} active={namespace === 'base.primitives'} onClick={() => selectNamespace('base.primitives')} />\n <NamespaceTab label=\"Project\" count={projectCount} active={namespace === 'project'} onClick={() => selectNamespace('project')} />\n </Row>\n {categories.length > 0 ? (\n <Row gap=\"1\" wrap>\n <CategoryChip label=\"All\" active={category === null} onClick={() => setCategory(null)} />\n {categories.map((c) => (\n <CategoryChip key={c} label={c} active={category === c} onClick={() => setCategory(c)} />\n ))}\n </Row>\n ) : null}\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto p-2\">\n {results.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\" className=\"p-3\">\n {query.trim()\n ? `No components match “${query}”.`\n : 'No components match the selected filters.'}\n </Text>\n ) : (\n <Stack gap=\"3\">\n {(namespace === 'all' || namespace === 'base.primitives') && primitives.length > 0 ? (\n <GroupedList\n heading=\"base.primitives.*\"\n count={primitives.length}\n manifests={primitives}\n projectId={projectId}\n activeComponentId={activeComponentId}\n />\n ) : null}\n {(namespace === 'all' || namespace === 'project') && projects.length > 0 ? (\n <GroupedList\n heading=\"project.*\"\n count={projects.length}\n manifests={projects}\n projectId={projectId}\n activeComponentId={activeComponentId}\n />\n ) : null}\n </Stack>\n )}\n\n {namespace !== 'base.primitives' && !query && !category ? (\n <DraftSection />\n ) : null}\n </div>\n </div>\n )\n}\n\nfunction NamespaceTab({\n label,\n count,\n active,\n onClick,\n}: {\n label: string\n count: number\n active: boolean\n onClick: () => void\n}) {\n return (\n <button\n type=\"button\"\n aria-pressed={active}\n onClick={onClick}\n className={cn(\n 'flex flex-1 items-center justify-center gap-1.5 rounded-[5px] px-2 py-1 text-xs font-medium transition-colors',\n active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {label}\n <span className=\"font-mono text-[0.65rem] text-muted-foreground\">{count}</span>\n </button>\n )\n}\n\nfunction CategoryChip({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs capitalize transition-colors',\n active\n ? 'border-primary/30 bg-primary/10 text-primary'\n : 'border-border text-muted-foreground hover:bg-muted',\n )}\n >\n {label}\n </button>\n )\n}\n\nfunction GroupedList({\n heading,\n count,\n manifests,\n projectId,\n activeComponentId,\n}: {\n heading: string\n count: number\n manifests: ComponentManifest[]\n projectId: string\n activeComponentId?: string\n}) {\n const studio = getStudioRegistry()\n return (\n <Stack gap=\"1\">\n <Row gap=\"2\" className=\"px-2\">\n <Code tone=\"muted\">{heading}</Code>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {count}\n </Text>\n </Row>\n <Stack gap=\"px\">\n {manifests.map((m) => {\n const status = componentStatus(studio, m)\n const active = m.id === activeComponentId\n return (\n <Link\n key={m.id}\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.component(m.id) }}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',\n active ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n <ComponentStatusDot status={status} />\n <span className=\"min-w-0 flex-1 truncate font-medium text-foreground\">{m.title ?? m.id}</span>\n {m.category ? (\n <span className=\"shrink-0 text-[0.7rem] capitalize text-muted-foreground\">{m.category}</span>\n ) : null}\n </Link>\n )\n })}\n </Stack>\n </Stack>\n )\n}\n\n/** Read-only AI-draft hook points (EC-058). Inspectable, not opaque. */\nfunction DraftSection() {\n if (componentDrafts.length === 0) return null\n return (\n <Stack gap=\"1\" className=\"mt-4\">\n <Row gap=\"2\" className=\"px-2\">\n <Code tone=\"muted\">drafts</Code>\n <Spacer />\n <Badge variant=\"outline\">\n <Sparkles className=\"size-3\" /> AI · Phase 8\n </Badge>\n </Row>\n <Stack gap=\"1\">\n {componentDrafts.map((draft) => (\n <div key={draft.id} className=\"rounded-md border border-dashed border-border px-2.5 py-2\">\n <Row gap=\"2\">\n <Text size=\"sm\" weight=\"medium\">\n {draft.title}\n </Text>\n <Spacer />\n <Code tone=\"muted\">{draft.manifestPreview.id}</Code>\n </Row>\n <Text size=\"xs\" tone=\"subtle\" className=\"mt-0.5\">\n {draft.prompt}\n </Text>\n </div>\n ))}\n </Stack>\n </Stack>\n )\n}\n"]}
1
+ {"version":3,"file":"registry-browser.js","sourceRoot":"","sources":["../../../src/components/components/registry-browser.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { Search } from 'lucide-react'\nimport { useMemo, useState } from 'react'\nimport { isPrimitive } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { componentStatus, getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { Code, Input, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ComponentStatusDot } from './component-status'\n\ntype NamespaceFilter = 'all' | 'base.primitives' | 'project'\n\nexport function RegistryBrowser({\n projectId,\n activeComponentId,\n}: {\n projectId: string\n activeComponentId?: string\n}) {\n const studio = getStudioRegistry()\n const [query, setQuery] = useState('')\n const [namespace, setNamespace] = useState<NamespaceFilter>('all')\n const [category, setCategory] = useState<string | null>(null)\n\n const primitiveCount = studio.registry.primitives().length\n const projectCount = studio.registry.projectComponents().length\n\n // Switching namespace clears any category that doesn't exist in the new one.\n function selectNamespace(ns: NamespaceFilter) {\n setNamespace(ns)\n setCategory(null)\n }\n\n // Categories are scoped to the active namespace so every chip can yield results.\n const categories = useMemo(() => {\n const set = new Set<string>()\n for (const m of studio.registry.list({ namespace: namespace === 'all' ? undefined : namespace })) {\n if (m.category) set.add(m.category)\n }\n return [...set].sort()\n }, [studio, namespace])\n\n const results = useMemo(\n () =>\n studio.registry.list({\n query: query.trim() || undefined,\n namespace: namespace === 'all' ? undefined : namespace,\n category: category ?? undefined,\n }),\n [studio, query, namespace, category],\n )\n\n const primitives = results.filter((m) => isPrimitive(m.id))\n const projects = results.filter((m) => !isPrimitive(m.id))\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 space-y-2 border-b border-border p-3\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search components…\"\n className=\"pl-8\"\n aria-label=\"Search components\"\n />\n </div>\n <Row gap=\"1\" className=\"rounded-md bg-muted/50 p-0.5\" role=\"group\" aria-label=\"Filter by namespace\">\n <NamespaceTab label=\"All\" count={primitiveCount + projectCount} active={namespace === 'all'} onClick={() => selectNamespace('all')} />\n <NamespaceTab label=\"Primitives\" count={primitiveCount} active={namespace === 'base.primitives'} onClick={() => selectNamespace('base.primitives')} />\n <NamespaceTab label=\"Project\" count={projectCount} active={namespace === 'project'} onClick={() => selectNamespace('project')} />\n </Row>\n {categories.length > 0 ? (\n <Row gap=\"1\" wrap>\n <CategoryChip label=\"All\" active={category === null} onClick={() => setCategory(null)} />\n {categories.map((c) => (\n <CategoryChip key={c} label={c} active={category === c} onClick={() => setCategory(c)} />\n ))}\n </Row>\n ) : null}\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto p-2\">\n {results.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\" className=\"p-3\">\n {query.trim()\n ? `No components match “${query}”.`\n : 'No components match the selected filters.'}\n </Text>\n ) : (\n <Stack gap=\"3\">\n {(namespace === 'all' || namespace === 'base.primitives') && primitives.length > 0 ? (\n <GroupedList\n heading=\"base.primitives.*\"\n count={primitives.length}\n manifests={primitives}\n projectId={projectId}\n activeComponentId={activeComponentId}\n />\n ) : null}\n {(namespace === 'all' || namespace === 'project') && projects.length > 0 ? (\n <GroupedList\n heading=\"project.*\"\n count={projects.length}\n manifests={projects}\n projectId={projectId}\n activeComponentId={activeComponentId}\n />\n ) : null}\n </Stack>\n )}\n </div>\n </div>\n )\n}\n\nfunction NamespaceTab({\n label,\n count,\n active,\n onClick,\n}: {\n label: string\n count: number\n active: boolean\n onClick: () => void\n}) {\n return (\n <button\n type=\"button\"\n aria-pressed={active}\n onClick={onClick}\n className={cn(\n 'flex flex-1 items-center justify-center gap-1.5 rounded-[5px] px-2 py-1 text-xs font-medium transition-colors',\n active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {label}\n <span className=\"font-mono text-[0.65rem] text-muted-foreground\">{count}</span>\n </button>\n )\n}\n\nfunction CategoryChip({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs capitalize transition-colors',\n active\n ? 'border-primary/30 bg-primary/10 text-primary'\n : 'border-border text-muted-foreground hover:bg-muted',\n )}\n >\n {label}\n </button>\n )\n}\n\nfunction GroupedList({\n heading,\n count,\n manifests,\n projectId,\n activeComponentId,\n}: {\n heading: string\n count: number\n manifests: ComponentManifest[]\n projectId: string\n activeComponentId?: string\n}) {\n const studio = getStudioRegistry()\n return (\n <Stack gap=\"1\">\n <Row gap=\"2\" className=\"px-2\">\n <Code tone=\"muted\">{heading}</Code>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {count}\n </Text>\n </Row>\n <Stack gap=\"px\">\n {manifests.map((m) => {\n const status = componentStatus(studio, m)\n const active = m.id === activeComponentId\n return (\n <Link\n key={m.id}\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.component(m.id) }}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',\n active ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n <ComponentStatusDot status={status} />\n <span className=\"min-w-0 flex-1 truncate font-medium text-foreground\">{m.title ?? m.id}</span>\n {m.category ? (\n <span className=\"shrink-0 text-[0.7rem] capitalize text-muted-foreground\">{m.category}</span>\n ) : null}\n </Link>\n )\n })}\n </Stack>\n </Stack>\n )\n}\n"]}
@@ -16,12 +16,3 @@ export interface UnsavedChangesGuardProps {
16
16
  * save / discard / cancel. Used by Settings now; reusable in the Builder later.
17
17
  */
18
18
  export declare function UnsavedChangesGuard({ when, onSave, title, description, saveLabel, }: UnsavedChangesGuardProps): import("react").JSX.Element;
19
- /**
20
- * Lower-level hook for surfaces that want to render their own guard UI.
21
- * Returns the blocked status plus proceed/reset resolvers.
22
- */
23
- export declare function useUnsavedChangesGuard(when: boolean): {
24
- blocked: boolean;
25
- proceed: (() => void) | undefined;
26
- reset: (() => void) | undefined;
27
- };
@@ -31,16 +31,4 @@ export function UnsavedChangesGuard({ when, onSave, title = 'Unsaved changes', d
31
31
  }
32
32
  return (_jsx(Dialog, { open: blocked, onOpenChange: (open) => (!open ? reset?.() : undefined), children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsxs(Row, { gap: "2", children: [_jsx("span", { className: "grid size-8 place-items-center rounded-lg border border-warning/30 bg-warning/10 text-warning", children: _jsx(TriangleAlert, { className: "size-4" }) }), _jsx(DialogTitle, { children: title })] }), _jsx(DialogDescription, { className: "pt-1", children: description })] }), _jsxs(DialogFooter, { className: "flex-col-reverse gap-2 sm:flex-row sm:justify-between", children: [_jsx(Button, { variant: "ghost", onClick: () => reset?.(), disabled: saving, children: "Cancel" }), _jsxs(Row, { gap: "2", justify: "end", children: [_jsx(Button, { variant: "outline", onClick: () => proceed?.(), disabled: saving, children: "Discard" }), _jsx(Button, { onClick: handleSave, disabled: saving, children: saving ? 'Saving…' : saveLabel })] })] })] }) }));
33
33
  }
34
- /**
35
- * Lower-level hook for surfaces that want to render their own guard UI.
36
- * Returns the blocked status plus proceed/reset resolvers.
37
- */
38
- export function useUnsavedChangesGuard(when) {
39
- const { status, proceed, reset } = useBlocker({
40
- shouldBlockFn: () => when,
41
- enableBeforeUnload: () => when,
42
- withResolver: true,
43
- });
44
- return { blocked: status === 'blocked', proceed, reset };
45
- }
46
34
  //# sourceMappingURL=guards.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/components/guards.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useBlocker } from '@elytracms/studio/router'\nimport { TriangleAlert } from 'lucide-react'\nimport { useState } from 'react'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Row,\n} from '@elytracms/studio/ui'\n\nexport interface UnsavedChangesGuardProps {\n /** Block navigation while true (i.e. the surface has unsaved edits). */\n when: boolean\n /**\n * Save handler. Resolve `true` to allow the pending navigation, `false` to\n * keep the user on the page (e.g. validation failed). Defaults to allowing.\n */\n onSave?: () => boolean | Promise<boolean>\n title?: string\n description?: string\n saveLabel?: string\n}\n\n/**\n * Reusable route-transition + unsaved-change guard (brief §8.1, §8.7). Blocks\n * in-app navigation (and the browser unload) while `when` is true, then offers\n * save / discard / cancel. Used by Settings now; reusable in the Builder later.\n */\nexport function UnsavedChangesGuard({\n when,\n onSave,\n title = 'Unsaved changes',\n description = 'You have unsaved changes. Save them before leaving, or discard them.',\n saveLabel = 'Save changes',\n}: UnsavedChangesGuardProps) {\n const { status, proceed, reset } = useBlocker({\n shouldBlockFn: () => when,\n enableBeforeUnload: () => when,\n withResolver: true,\n })\n const [saving, setSaving] = useState(false)\n const blocked = status === 'blocked'\n\n async function handleSave() {\n setSaving(true)\n try {\n const ok = (await onSave?.()) ?? true\n if (ok) proceed?.()\n else reset?.()\n } finally {\n setSaving(false)\n }\n }\n\n return (\n <Dialog open={blocked} onOpenChange={(open) => (!open ? reset?.() : undefined)}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <Row gap=\"2\">\n <span className=\"grid size-8 place-items-center rounded-lg border border-warning/30 bg-warning/10 text-warning\">\n <TriangleAlert className=\"size-4\" />\n </span>\n <DialogTitle>{title}</DialogTitle>\n </Row>\n <DialogDescription className=\"pt-1\">{description}</DialogDescription>\n </DialogHeader>\n <DialogFooter className=\"flex-col-reverse gap-2 sm:flex-row sm:justify-between\">\n <Button variant=\"ghost\" onClick={() => reset?.()} disabled={saving}>\n Cancel\n </Button>\n <Row gap=\"2\" justify=\"end\">\n <Button variant=\"outline\" onClick={() => proceed?.()} disabled={saving}>\n Discard\n </Button>\n <Button onClick={handleSave} disabled={saving}>\n {saving ? 'Saving…' : saveLabel}\n </Button>\n </Row>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\n/**\n * Lower-level hook for surfaces that want to render their own guard UI.\n * Returns the blocked status plus proceed/reset resolvers.\n */\nexport function useUnsavedChangesGuard(when: boolean) {\n const { status, proceed, reset } = useBlocker({\n shouldBlockFn: () => when,\n enableBeforeUnload: () => when,\n withResolver: true,\n })\n return { blocked: status === 'blocked', proceed, reset }\n}\n"]}
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/components/guards.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EACL,MAAM,EACN,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,GAAG,GACJ,MAAM,sBAAsB,CAAA;AAe7B;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAClC,IAAI,EACJ,MAAM,EACN,KAAK,GAAG,iBAAiB,EACzB,WAAW,GAAG,sEAAsE,EACpF,SAAS,GAAG,cAAc,GACD;IACzB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC;QAC5C,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI;QACzB,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAI;QAC9B,YAAY,EAAE,IAAI;KACnB,CAAC,CAAA;IACF,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,OAAO,GAAG,MAAM,KAAK,SAAS,CAAA;IAEpC,KAAK,UAAU,UAAU;QACvB,SAAS,CAAC,IAAI,CAAC,CAAA;QACf,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,CAAC,MAAM,MAAM,EAAE,EAAE,CAAC,IAAI,IAAI,CAAA;YACrC,IAAI,EAAE;gBAAE,OAAO,EAAE,EAAE,CAAA;;gBACd,KAAK,EAAE,EAAE,CAAA;QAChB,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,KAAK,CAAC,CAAA;QAClB,CAAC;IACH,CAAC;IAED,OAAO,CACL,KAAC,MAAM,IAAC,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,YAC5E,MAAC,aAAa,IAAC,SAAS,EAAC,UAAU,aACjC,MAAC,YAAY,eACX,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,eAAM,SAAS,EAAC,+FAA+F,YAC7G,KAAC,aAAa,IAAC,SAAS,EAAC,QAAQ,GAAG,GAC/B,EACP,KAAC,WAAW,cAAE,KAAK,GAAe,IAC9B,EACN,KAAC,iBAAiB,IAAC,SAAS,EAAC,MAAM,YAAE,WAAW,GAAqB,IACxD,EACf,MAAC,YAAY,IAAC,SAAS,EAAC,uDAAuD,aAC7E,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,uBAEzD,EACT,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,OAAO,EAAC,KAAK,aACxB,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,wBAE7D,EACT,KAAC,MAAM,IAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAC1C,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,GACxB,IACL,IACO,IACD,GACT,CACV,CAAA;AACH,CAAC","sourcesContent":["import { useBlocker } from '@elytracms/studio/router'\nimport { TriangleAlert } from 'lucide-react'\nimport { useState } from 'react'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Row,\n} from '@elytracms/studio/ui'\n\nexport interface UnsavedChangesGuardProps {\n /** Block navigation while true (i.e. the surface has unsaved edits). */\n when: boolean\n /**\n * Save handler. Resolve `true` to allow the pending navigation, `false` to\n * keep the user on the page (e.g. validation failed). Defaults to allowing.\n */\n onSave?: () => boolean | Promise<boolean>\n title?: string\n description?: string\n saveLabel?: string\n}\n\n/**\n * Reusable route-transition + unsaved-change guard (brief §8.1, §8.7). Blocks\n * in-app navigation (and the browser unload) while `when` is true, then offers\n * save / discard / cancel. Used by Settings now; reusable in the Builder later.\n */\nexport function UnsavedChangesGuard({\n when,\n onSave,\n title = 'Unsaved changes',\n description = 'You have unsaved changes. Save them before leaving, or discard them.',\n saveLabel = 'Save changes',\n}: UnsavedChangesGuardProps) {\n const { status, proceed, reset } = useBlocker({\n shouldBlockFn: () => when,\n enableBeforeUnload: () => when,\n withResolver: true,\n })\n const [saving, setSaving] = useState(false)\n const blocked = status === 'blocked'\n\n async function handleSave() {\n setSaving(true)\n try {\n const ok = (await onSave?.()) ?? true\n if (ok) proceed?.()\n else reset?.()\n } finally {\n setSaving(false)\n }\n }\n\n return (\n <Dialog open={blocked} onOpenChange={(open) => (!open ? reset?.() : undefined)}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <Row gap=\"2\">\n <span className=\"grid size-8 place-items-center rounded-lg border border-warning/30 bg-warning/10 text-warning\">\n <TriangleAlert className=\"size-4\" />\n </span>\n <DialogTitle>{title}</DialogTitle>\n </Row>\n <DialogDescription className=\"pt-1\">{description}</DialogDescription>\n </DialogHeader>\n <DialogFooter className=\"flex-col-reverse gap-2 sm:flex-row sm:justify-between\">\n <Button variant=\"ghost\" onClick={() => reset?.()} disabled={saving}>\n Cancel\n </Button>\n <Row gap=\"2\" justify=\"end\">\n <Button variant=\"outline\" onClick={() => proceed?.()} disabled={saving}>\n Discard\n </Button>\n <Button onClick={handleSave} disabled={saving}>\n {saving ? 'Saving…' : saveLabel}\n </Button>\n </Row>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
@@ -4,6 +4,7 @@ import { Button, Stack, Text } from '@elytracms/studio/ui';
4
4
  import { getStudioOperations, loadHydratedCms, loadProjectSession } from '@elytracms/studio/lib/operations';
5
5
  import { isPlaygroundMode } from '@elytracms/studio/lib/persistence';
6
6
  import { configureContentSession } from '@elytracms/studio/lib/data';
7
+ import { resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace';
7
8
  import { OperationErrorBanner } from './operation-error';
8
9
  const ProjectSessionContext = createContext(null);
9
10
  export function ProjectSessionProvider({ projectId, children, }) {
@@ -29,8 +30,12 @@ export function ProjectSessionProvider({ projectId, children, }) {
29
30
  return;
30
31
  }
31
32
  // Point the preview content engine at the hydrated documents before
32
- // any child renders (panels read it module-level, synchronously).
33
- configureContentSession(result.value.cms);
33
+ // any child renders (panels read it module-level, synchronously). EC-291:
34
+ // include the config-sourced routes so the canvas `resolveLink` derives a
35
+ // rich-text link's URL exactly as delivery does.
36
+ configureContentSession(result.value.cms
37
+ ? { ...result.value.cms, routes: resolveDeploymentRoutes() }
38
+ : null);
34
39
  setPhase({ kind: 'ready', session: result.value });
35
40
  });
36
41
  return () => {
@@ -47,7 +52,7 @@ export function ProjectSessionProvider({ projectId, children, }) {
47
52
  const result = await loadHydratedCms(operations, projectId);
48
53
  // Keep the preview engine in sync with the freshest persisted content.
49
54
  if (result.ok && result.value !== null)
50
- configureContentSession(result.value);
55
+ configureContentSession({ ...result.value, routes: resolveDeploymentRoutes() });
51
56
  return result;
52
57
  }, [playground, projectId]);
53
58
  if (phase.kind === 'loading')
@@ -1 +1 @@
1
- {"version":3,"file":"project-session.js","sourceRoot":"","sources":["../../src/components/project-session.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEnF,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAO3G,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAA;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAkCxD,MAAM,qBAAqB,GAAG,aAAa,CAA6B,IAAI,CAAC,CAAA;AAO7E,MAAM,UAAU,sBAAsB,CAAC,EACrC,SAAS,EACT,QAAQ,GAIT;IACC,MAAM,UAAU,GAAG,gBAAgB,EAAE,CAAA;IACrC,0EAA0E;IAC1E,oDAAoD;IACpD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAQ,GAAG,EAAE,CAC7C,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CACpE,CAAA;IACD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAEzC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,EAAE,CAAC;YACf,uBAAuB,CAAC,IAAI,CAAC,CAAA;YAC7B,OAAM;QACR,CAAC;QACD,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;QAC7B,KAAK,mBAAmB,EAAE;aACvB,IAAI,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,kBAAkB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;aACnE,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,IAAI,SAAS;gBAAE,OAAM;YACrB,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;gBAChD,OAAM;YACR,CAAC;YACD,oEAAoE;YACpE,kEAAkE;YAClE,uBAAuB,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YACzC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;QACpD,CAAC,CAAC,CAAA;QACJ,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAA;QAClB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAA;IAEpC,sEAAsE;IACtE,oEAAoE;IACpE,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;IAExD,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,IAAkD,EAAE;QACnF,IAAI,UAAU;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QAChD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,mBAAmB,EAAE,CAAA;QAClD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;QAC3D,uEAAuE;QACvE,IAAI,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI;YAAE,uBAAuB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC7E,OAAO,MAAM,CAAA;IACf,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAA;IAE3B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,KAAC,gBAAgB,KAAG,CAAA;IACzD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC3B,OAAO,CACL,KAAC,qBAAqB,IAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAI,CACvF,CAAA;IACH,CAAC;IACD,OAAO,CACL,KAAC,qBAAqB,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,YACvE,QAAQ,GACsB,CAClC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAA;IAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAA;IACtF,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,QAAQ,GAGT;IACC,MAAM,EAAE,OAAO,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACvC,MAAM,UAAU,GAAG,gBAAgB,EAAE,CAAA;IACrC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAIhC,GAAG,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;IAC1E,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAEzC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU;YAAE,OAAM;QACtB,IAAI,SAAS,GAAG,KAAK,CAAA;QACrB,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;QAC7B,KAAK,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YAC7B,IAAI,SAAS;gBAAE,OAAM;YACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;QACrG,CAAC,CAAC,CAAA;QACF,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAA;QAClB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IAElC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,KAAC,gBAAgB,KAAG,CAAA;IACzD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC3B,OAAO,CACL,KAAC,qBAAqB,IAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAI,CACvF,CAAA;IACH,CAAC;IACD,OAAO,4BAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,GAAI,CAAA;AACnC,CAAC;AAED,0FAA0F;AAC1F,SAAS,gBAAgB;IACvB,OAAO,CACL,cAAK,SAAS,EAAC,oDAAoD,iBAAa,mBAAmB,YACjG,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,sCAErB,GACH,CACP,CAAA;AACH,CAAC;AAED,mFAAmF;AACnF,SAAS,qBAAqB,CAAC,EAC7B,KAAK,EACL,OAAO,GAIR;IACC,OAAO,CACL,cAAK,SAAS,EAAC,oDAAoD,YACjE,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,iBAAiB,aACxC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,mEAE1B,EACP,KAAC,oBAAoB,IAAC,KAAK,EAAE,KAAK,GAAI,EACtC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,oJAGrB,EACP,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAC,YAAY,sBAEnE,IACH,GACJ,CACP,CAAA;AACH,CAAC","sourcesContent":["import { createContext, useCallback, useContext, useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Button, Stack, Text } from '@elytracms/studio/ui'\nimport { getStudioOperations, loadHydratedCms, loadProjectSession } from '@elytracms/studio/lib/operations'\nimport type {\n HydratedCms,\n HydratedProjectSession,\n OperationError,\n OperationResult,\n} from '@elytracms/studio/lib/operations'\nimport { isPlaygroundMode } from '@elytracms/studio/lib/persistence'\nimport { configureContentSession } from '@elytracms/studio/lib/data'\nimport { OperationErrorBanner } from './operation-error'\n\n/**\n * Project-session hydration gate (Milestone B sign-off fix, EC-177 gap 1).\n *\n * In Convex mode every project session must BOOT from persistence: the\n * builder from the latest persisted graph revision, the CMS from the saved\n * schema + documents, publishing from the persisted environment states.\n * This provider runs the `@elytracms/core/operations` hydration queries\n * (`lib/operations/session-hydration`) and — like `BackendGate` — gates its\n * children on a deterministic loading state, so fixture content never\n * flashes (and is never editable) before the persisted state arrives.\n *\n * Playground mode renders children synchronously with `session: null`\n * (fixtures), byte-identical to the pre-hydration behavior — the EC-138\n * determinism contract.\n *\n * Side effect on hydration: the content preview engine\n * (`lib/data/collection-content`) is pointed at the hydrated documents\n * BEFORE children render, so \"Preview with\" lists and section previews show\n * live entries, not fixture titles.\n */\n\nexport interface ProjectSessionStore {\n /** Hydrated persisted state (Convex mode); `null` in playground (fixtures). */\n session: HydratedProjectSession | null\n /**\n * Load the CURRENT persisted CMS state (fresh query — the CMS section\n * remounts per visit and must show edits persisted since project open).\n * Playground resolves `null` immediately (fixtures).\n */\n loadCms(): Promise<OperationResult<HydratedCms | null>>\n}\n\nconst ProjectSessionContext = createContext<ProjectSessionStore | null>(null)\n\ntype Phase =\n | { kind: 'loading' }\n | { kind: 'ready'; session: HydratedProjectSession | null }\n | { kind: 'error'; error: OperationError }\n\nexport function ProjectSessionProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const playground = isPlaygroundMode()\n // Playground renders children on the FIRST render (no effect round-trip):\n // fixture boots stay synchronous and deterministic.\n const [phase, setPhase] = useState<Phase>(() =>\n playground ? { kind: 'ready', session: null } : { kind: 'loading' },\n )\n const [attempt, setAttempt] = useState(0)\n\n useEffect(() => {\n if (playground) {\n configureContentSession(null)\n return\n }\n let cancelled = false\n setPhase({ kind: 'loading' })\n void getStudioOperations()\n .then(({ operations }) => loadProjectSession(operations, projectId))\n .then((result) => {\n if (cancelled) return\n if (!result.ok) {\n setPhase({ kind: 'error', error: result.error })\n return\n }\n // Point the preview content engine at the hydrated documents before\n // any child renders (panels read it module-level, synchronously).\n configureContentSession(result.value.cms)\n setPhase({ kind: 'ready', session: result.value })\n })\n return () => {\n cancelled = true\n }\n }, [projectId, playground, attempt])\n\n // Leaving the project resets the preview engine to fixtures; the next\n // project's gate reconfigures it before rendering its own children.\n useEffect(() => () => configureContentSession(null), [])\n\n const loadCms = useCallback(async (): Promise<OperationResult<HydratedCms | null>> => {\n if (playground) return { ok: true, value: null }\n const { operations } = await getStudioOperations()\n const result = await loadHydratedCms(operations, projectId)\n // Keep the preview engine in sync with the freshest persisted content.\n if (result.ok && result.value !== null) configureContentSession(result.value)\n return result\n }, [playground, projectId])\n\n if (phase.kind === 'loading') return <SessionHydrating />\n if (phase.kind === 'error') {\n return (\n <SessionHydrationError error={phase.error} onRetry={() => setAttempt((n) => n + 1)} />\n )\n }\n return (\n <ProjectSessionContext.Provider value={{ session: phase.session, loadCms }}>\n {children}\n </ProjectSessionContext.Provider>\n )\n}\n\nexport function useProjectSession(): ProjectSessionStore {\n const store = useContext(ProjectSessionContext)\n if (!store) {\n throw new Error('useProjectSession must be used within a <ProjectSessionProvider>.')\n }\n return store\n}\n\n/**\n * Gate one CMS surface mount on a FRESH persisted CMS load (Convex mode).\n * Render-prop: hands the hydrated content (or `null` → fixture fallback,\n * first-run) to the caller once it resolved. Playground renders synchronously\n * with `null`.\n */\nexport function CmsSessionGate({\n children,\n}: {\n children: (cms: HydratedCms | null) => ReactNode\n}) {\n const { loadCms } = useProjectSession()\n const playground = isPlaygroundMode()\n const [phase, setPhase] = useState<\n | { kind: 'loading' }\n | { kind: 'ready'; cms: HydratedCms | null }\n | { kind: 'error'; error: OperationError }\n >(() => (playground ? { kind: 'ready', cms: null } : { kind: 'loading' }))\n const [attempt, setAttempt] = useState(0)\n\n useEffect(() => {\n if (playground) return\n let cancelled = false\n setPhase({ kind: 'loading' })\n void loadCms().then((result) => {\n if (cancelled) return\n setPhase(result.ok ? { kind: 'ready', cms: result.value } : { kind: 'error', error: result.error })\n })\n return () => {\n cancelled = true\n }\n }, [playground, loadCms, attempt])\n\n if (phase.kind === 'loading') return <SessionHydrating />\n if (phase.kind === 'error') {\n return (\n <SessionHydrationError error={phase.error} onRetry={() => setAttempt((n) => n + 1)} />\n )\n }\n return <>{children(phase.cms)}</>\n}\n\n/** Deterministic loading frame while persisted state is fetched (mirrors BackendGate). */\nfunction SessionHydrating() {\n return (\n <div className=\"grid h-full min-h-64 flex-1 place-items-center p-8\" data-testid=\"session-hydrating\">\n <Text size=\"sm\" tone=\"muted\">\n Loading project…\n </Text>\n </div>\n )\n}\n\n/** A failed hydration is a visible, retryable state — never a fixture fallback. */\nfunction SessionHydrationError({\n error,\n onRetry,\n}: {\n error: OperationError\n onRetry: () => void\n}) {\n return (\n <div className=\"grid h-full min-h-64 flex-1 place-items-center p-8\">\n <Stack gap=\"3\" className=\"w-full max-w-xl\">\n <Text size=\"sm\" weight=\"semibold\">\n Couldn’t load this project’s saved state\n </Text>\n <OperationErrorBanner error={error} />\n <Text size=\"xs\" tone=\"muted\">\n The studio never falls back to sample content against a live backend — your data is\n safe; retry once the backend is reachable.\n </Text>\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} className=\"self-start\">\n Retry\n </Button>\n </Stack>\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"project-session.js","sourceRoot":"","sources":["../../src/components/project-session.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Button, Stack, Text } from '@elytracms/studio/ui'\nimport { getStudioOperations, loadHydratedCms, loadProjectSession } from '@elytracms/studio/lib/operations'\nimport type {\n HydratedCms,\n HydratedProjectSession,\n OperationError,\n OperationResult,\n} from '@elytracms/studio/lib/operations'\nimport { isPlaygroundMode } from '@elytracms/studio/lib/persistence'\nimport { configureContentSession } from '@elytracms/studio/lib/data'\nimport { resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport { OperationErrorBanner } from './operation-error'\n\n/**\n * Project-session hydration gate (Milestone B sign-off fix, EC-177 gap 1).\n *\n * In Convex mode every project session must BOOT from persistence: the\n * builder from the latest persisted graph revision, the CMS from the saved\n * schema + documents, publishing from the persisted environment states.\n * This provider runs the `@elytracms/core/operations` hydration queries\n * (`lib/operations/session-hydration`) and — like `BackendGate` — gates its\n * children on a deterministic loading state, so fixture content never\n * flashes (and is never editable) before the persisted state arrives.\n *\n * Playground mode renders children synchronously with `session: null`\n * (fixtures), byte-identical to the pre-hydration behavior — the EC-138\n * determinism contract.\n *\n * Side effect on hydration: the content preview engine\n * (`lib/data/collection-content`) is pointed at the hydrated documents\n * BEFORE children render, so \"Preview with\" lists and section previews show\n * live entries, not fixture titles.\n */\n\nexport interface ProjectSessionStore {\n /** Hydrated persisted state (Convex mode); `null` in playground (fixtures). */\n session: HydratedProjectSession | null\n /**\n * Load the CURRENT persisted CMS state (fresh query — the CMS section\n * remounts per visit and must show edits persisted since project open).\n * Playground resolves `null` immediately (fixtures).\n */\n loadCms(): Promise<OperationResult<HydratedCms | null>>\n}\n\nconst ProjectSessionContext = createContext<ProjectSessionStore | null>(null)\n\ntype Phase =\n | { kind: 'loading' }\n | { kind: 'ready'; session: HydratedProjectSession | null }\n | { kind: 'error'; error: OperationError }\n\nexport function ProjectSessionProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const playground = isPlaygroundMode()\n // Playground renders children on the FIRST render (no effect round-trip):\n // fixture boots stay synchronous and deterministic.\n const [phase, setPhase] = useState<Phase>(() =>\n playground ? { kind: 'ready', session: null } : { kind: 'loading' },\n )\n const [attempt, setAttempt] = useState(0)\n\n useEffect(() => {\n if (playground) {\n configureContentSession(null)\n return\n }\n let cancelled = false\n setPhase({ kind: 'loading' })\n void getStudioOperations()\n .then(({ operations }) => loadProjectSession(operations, projectId))\n .then((result) => {\n if (cancelled) return\n if (!result.ok) {\n setPhase({ kind: 'error', error: result.error })\n return\n }\n // Point the preview content engine at the hydrated documents before\n // any child renders (panels read it module-level, synchronously). EC-291:\n // include the config-sourced routes so the canvas `resolveLink` derives a\n // rich-text link's URL exactly as delivery does.\n configureContentSession(\n result.value.cms\n ? { ...result.value.cms, routes: resolveDeploymentRoutes() }\n : null,\n )\n setPhase({ kind: 'ready', session: result.value })\n })\n return () => {\n cancelled = true\n }\n }, [projectId, playground, attempt])\n\n // Leaving the project resets the preview engine to fixtures; the next\n // project's gate reconfigures it before rendering its own children.\n useEffect(() => () => configureContentSession(null), [])\n\n const loadCms = useCallback(async (): Promise<OperationResult<HydratedCms | null>> => {\n if (playground) return { ok: true, value: null }\n const { operations } = await getStudioOperations()\n const result = await loadHydratedCms(operations, projectId)\n // Keep the preview engine in sync with the freshest persisted content.\n if (result.ok && result.value !== null)\n configureContentSession({ ...result.value, routes: resolveDeploymentRoutes() })\n return result\n }, [playground, projectId])\n\n if (phase.kind === 'loading') return <SessionHydrating />\n if (phase.kind === 'error') {\n return (\n <SessionHydrationError error={phase.error} onRetry={() => setAttempt((n) => n + 1)} />\n )\n }\n return (\n <ProjectSessionContext.Provider value={{ session: phase.session, loadCms }}>\n {children}\n </ProjectSessionContext.Provider>\n )\n}\n\nexport function useProjectSession(): ProjectSessionStore {\n const store = useContext(ProjectSessionContext)\n if (!store) {\n throw new Error('useProjectSession must be used within a <ProjectSessionProvider>.')\n }\n return store\n}\n\n/**\n * Gate one CMS surface mount on a FRESH persisted CMS load (Convex mode).\n * Render-prop: hands the hydrated content (or `null` → fixture fallback,\n * first-run) to the caller once it resolved. Playground renders synchronously\n * with `null`.\n */\nexport function CmsSessionGate({\n children,\n}: {\n children: (cms: HydratedCms | null) => ReactNode\n}) {\n const { loadCms } = useProjectSession()\n const playground = isPlaygroundMode()\n const [phase, setPhase] = useState<\n | { kind: 'loading' }\n | { kind: 'ready'; cms: HydratedCms | null }\n | { kind: 'error'; error: OperationError }\n >(() => (playground ? { kind: 'ready', cms: null } : { kind: 'loading' }))\n const [attempt, setAttempt] = useState(0)\n\n useEffect(() => {\n if (playground) return\n let cancelled = false\n setPhase({ kind: 'loading' })\n void loadCms().then((result) => {\n if (cancelled) return\n setPhase(result.ok ? { kind: 'ready', cms: result.value } : { kind: 'error', error: result.error })\n })\n return () => {\n cancelled = true\n }\n }, [playground, loadCms, attempt])\n\n if (phase.kind === 'loading') return <SessionHydrating />\n if (phase.kind === 'error') {\n return (\n <SessionHydrationError error={phase.error} onRetry={() => setAttempt((n) => n + 1)} />\n )\n }\n return <>{children(phase.cms)}</>\n}\n\n/** Deterministic loading frame while persisted state is fetched (mirrors BackendGate). */\nfunction SessionHydrating() {\n return (\n <div className=\"grid h-full min-h-64 flex-1 place-items-center p-8\" data-testid=\"session-hydrating\">\n <Text size=\"sm\" tone=\"muted\">\n Loading project…\n </Text>\n </div>\n )\n}\n\n/** A failed hydration is a visible, retryable state — never a fixture fallback. */\nfunction SessionHydrationError({\n error,\n onRetry,\n}: {\n error: OperationError\n onRetry: () => void\n}) {\n return (\n <div className=\"grid h-full min-h-64 flex-1 place-items-center p-8\">\n <Stack gap=\"3\" className=\"w-full max-w-xl\">\n <Text size=\"sm\" weight=\"semibold\">\n Couldn’t load this project’s saved state\n </Text>\n <OperationErrorBanner error={error} />\n <Text size=\"xs\" tone=\"muted\">\n The studio never falls back to sample content against a live backend — your data is\n safe; retry once the backend is reachable.\n </Text>\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} className=\"self-start\">\n Retry\n </Button>\n </Stack>\n </div>\n )\n}\n"]}
@@ -1,17 +1,17 @@
1
- import type { ProjectId, SectionAvailabilityMap } from '@elytracms/core/studio-core';
2
- import type { StudioNavId } from './sections';
1
+ import type { Project } from '@elytracms/core/studio-core';
3
2
  export interface SectionNavProps {
4
- projectId: ProjectId;
5
- activeSection: StudioNavId | null;
6
- availability: SectionAvailabilityMap;
3
+ project: Project;
7
4
  /** User-toggled collapse (EC-214) — icons only when collapsed, persisted by the shell. */
8
5
  collapsed: boolean;
9
6
  onToggleCollapsed: () => void;
10
7
  }
11
8
  /**
12
- * Vertical section rail (brief §8.1). Availability-aware: `available`/`planned`
13
- * are navigable; `unavailable` is rendered disabled. User-collapsible to an
14
- * icon rail (EC-214) — the preference persists, and the active section carries a
15
- * strong highlight (accent fill + a leading marker bar).
9
+ * Vertical section rail (brief §8.1), now driven by the configurable sidebar
10
+ * (EC-237): a consumer-extendable tree of items / groups / separators resolved
11
+ * over the app-view module registry (EC-239). Availability-aware (`unavailable`
12
+ * items are absent per EC-221); items deep-link to a module + resource; the
13
+ * preference-persisted collapse and the strong active marker (EC-214) are
14
+ * preserved. Customization issues (duplicate ids, unknown modules, unresolvable
15
+ * deep-links) render at the rail foot — visible, never silent.
16
16
  */
17
- export declare function SectionNav({ projectId, activeSection, availability, collapsed, onToggleCollapsed, }: SectionNavProps): import("react").JSX.Element;
17
+ export declare function SectionNav({ project, collapsed, onToggleCollapsed }: SectionNavProps): import("react").JSX.Element;
@@ -1,33 +1,56 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/router';
3
- import { PanelLeftClose, PanelLeftOpen } from 'lucide-react';
3
+ import { AlertTriangle, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
4
4
  import { cn } from '@elytracms/studio/lib/utils';
5
- import { SECTION_NAV_ITEMS, activeSectionFromPath, navAvailabilityOf } from './sections';
5
+ import { Text } from '@elytracms/studio/ui';
6
+ import { useAppViews } from './app-views/default-modules';
7
+ import { activeModuleFromPath, useSidebar } from './app-views/sidebar';
6
8
  /**
7
- * Vertical section rail (brief §8.1). Availability-aware: `available`/`planned`
8
- * are navigable; `unavailable` is rendered disabled. User-collapsible to an
9
- * icon rail (EC-214) — the preference persists, and the active section carries a
10
- * strong highlight (accent fill + a leading marker bar).
9
+ * Vertical section rail (brief §8.1), now driven by the configurable sidebar
10
+ * (EC-237): a consumer-extendable tree of items / groups / separators resolved
11
+ * over the app-view module registry (EC-239). Availability-aware (`unavailable`
12
+ * items are absent per EC-221); items deep-link to a module + resource; the
13
+ * preference-persisted collapse and the strong active marker (EC-214) are
14
+ * preserved. Customization issues (duplicate ids, unknown modules, unresolvable
15
+ * deep-links) render at the rail foot — visible, never silent.
11
16
  */
12
- export function SectionNav({ projectId, activeSection, availability, collapsed, onToggleCollapsed, }) {
13
- // Recover the active id from the path if the shell didn't supply one — the
14
- // shared parser is the single source of truth for the section segment index.
17
+ export function SectionNav({ project, collapsed, onToggleCollapsed }) {
15
18
  const { pathname } = useLocation();
16
- const active = activeSection ?? activeSectionFromPath(pathname);
17
- return (_jsxs("nav", { "aria-label": "Sections", className: cn('flex shrink-0 flex-col gap-0.5 border-r border-border bg-card/40 p-2 transition-[width] duration-150', collapsed ? 'w-[3.5rem]' : 'w-56'), children: [SECTION_NAV_ITEMS.map((item) => {
18
- const state = navAvailabilityOf(availability, item.id);
19
- const isActive = item.id === active;
20
- const Icon = item.icon;
21
- const body = (_jsxs(_Fragment, { children: [isActive ? (_jsx("span", { className: "absolute inset-y-1.5 left-0 w-0.5 rounded-full bg-primary", "aria-hidden": true })) : null, _jsx(Icon, { className: "size-4 shrink-0" }), collapsed ? null : (_jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label })), state === 'planned' && !collapsed ? (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-warning", "aria-hidden": true, title: "Planned" })) : null] }));
22
- const base = cn('group relative flex h-9 items-center gap-2.5 rounded-md text-sm transition-colors', collapsed ? 'justify-center px-0' : 'px-3');
23
- // EC-221: an unavailable section is absent from the rail (not greyed) —
24
- // e.g. Extensions, cut from the v1 nav (vision §5). It stays reachable by
25
- // URL for Milestone E revival; the rail simply doesn't advertise it.
26
- if (state === 'unavailable')
19
+ const active = activeModuleFromPath(pathname);
20
+ const { nodes, issues: sidebarIssues } = useSidebar(project);
21
+ const { issues: moduleIssues } = useAppViews();
22
+ const issues = [...moduleIssues, ...sidebarIssues];
23
+ const projectId = project.id;
24
+ return (_jsxs("nav", { "aria-label": "Sections", className: cn('flex shrink-0 flex-col gap-0.5 border-r border-border bg-card/40 p-2 transition-[width] duration-150', collapsed ? 'w-[3.5rem]' : 'w-56'), children: [nodes.map((node) => {
25
+ if (node.kind === 'separator') {
26
+ return _jsx("div", { className: "my-1.5 border-t border-border/60", "aria-hidden": true }, node.id);
27
+ }
28
+ if (node.kind === 'group') {
29
+ const items = node.items.filter((item) => item.availability !== 'unavailable');
30
+ if (items.length === 0)
31
+ return null;
32
+ return (_jsxs("div", { className: "mt-2 first:mt-0", children: [collapsed ? (_jsx("div", { className: "my-1 border-t border-border/60", "aria-hidden": true })) : (_jsx(Text, { size: "xs", tone: "subtle", className: "px-3 pb-1 pt-1 font-medium uppercase tracking-wide", children: node.label })), items.map((item) => (_jsx(NavItemLink, { projectId: projectId, item: item, active: active, collapsed: collapsed, nested: true }, item.id)))] }, node.id));
33
+ }
34
+ // EC-221: an unavailable item is absent from the rail (not greyed) — it
35
+ // stays reachable by URL, the rail simply doesn't advertise it.
36
+ if (node.availability === 'unavailable')
27
37
  return null;
28
- return (_jsx(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: item.id }, title: item.label, "aria-current": isActive ? 'page' : undefined, className: cn(base, isActive
29
- ? 'bg-accent font-medium text-accent-foreground'
30
- : 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: body }, item.id));
31
- }), _jsx("button", { type: "button", onClick: onToggleCollapsed, "aria-label": collapsed ? 'Expand sidebar' : 'Collapse sidebar', "aria-pressed": collapsed, title: collapsed ? 'Expand sidebar' : 'Collapse sidebar', className: cn('mt-auto flex h-9 items-center gap-2.5 rounded-md text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground', collapsed ? 'justify-center px-0' : 'px-3'), children: collapsed ? (_jsx(PanelLeftOpen, { className: "size-4 shrink-0" })) : (_jsxs(_Fragment, { children: [_jsx(PanelLeftClose, { className: "size-4 shrink-0" }), _jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: "Collapse" })] })) })] }));
38
+ return (_jsx(NavItemLink, { projectId: projectId, item: node, active: active, collapsed: collapsed }, node.id));
39
+ }), _jsxs("div", { className: "mt-auto flex flex-col gap-0.5", children: [issues.length > 0 ? _jsx(NavIssues, { issues: issues, collapsed: collapsed }) : null, _jsx("button", { type: "button", onClick: onToggleCollapsed, "aria-label": collapsed ? 'Expand sidebar' : 'Collapse sidebar', "aria-pressed": collapsed, title: collapsed ? 'Expand sidebar' : 'Collapse sidebar', className: cn('flex h-9 items-center gap-2.5 rounded-md text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground', collapsed ? 'justify-center px-0' : 'px-3'), children: collapsed ? (_jsx(PanelLeftOpen, { className: "size-4 shrink-0" })) : (_jsxs(_Fragment, { children: [_jsx(PanelLeftClose, { className: "size-4 shrink-0" }), _jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: "Collapse" })] })) })] })] }));
40
+ }
41
+ /** One rail item (top-level or nested) — a deep-linkable Link to its module. */
42
+ function NavItemLink({ projectId, item, active, collapsed, nested = false, }) {
43
+ const isActive = item.moduleId === active;
44
+ const Icon = item.icon;
45
+ return (_jsxs(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: item.path }, title: item.label, "aria-current": isActive ? 'page' : undefined, className: cn('group relative flex h-9 items-center gap-2.5 rounded-md text-sm transition-colors', collapsed ? 'justify-center px-0' : nested ? 'pl-7 pr-3' : 'px-3', isActive
46
+ ? 'bg-accent font-medium text-accent-foreground'
47
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: [isActive ? (_jsx("span", { className: "absolute inset-y-1.5 left-0 w-0.5 rounded-full bg-primary", "aria-hidden": true })) : null, _jsx(Icon, { className: "size-4 shrink-0" }), collapsed ? null : _jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label }), item.availability === 'planned' && !collapsed ? (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-warning", "aria-hidden": true, title: "Planned" })) : null] }));
48
+ }
49
+ /** Visible customization issues at the rail foot — the seam's "never silent" (EC-236). */
50
+ function NavIssues({ issues, collapsed }) {
51
+ if (collapsed) {
52
+ return (_jsx("div", { className: "grid h-9 place-items-center text-amber-600 dark:text-amber-400", title: issues.map((issue) => issue.message).join('\n'), children: _jsx(AlertTriangle, { className: "size-4", "aria-label": `${issues.length} sidebar issues` }) }));
53
+ }
54
+ return (_jsx("div", { className: "mb-1 flex flex-col gap-1 rounded-md border border-amber-500/30 bg-amber-500/5 p-2", children: issues.map((issue, index) => (_jsxs("div", { className: "flex items-start gap-1.5 text-amber-600 dark:text-amber-400", title: issue.message, children: [_jsx(AlertTriangle, { className: "mt-0.5 size-3.5 shrink-0", "aria-hidden": true }), _jsx(Text, { size: "xs", className: "min-w-0 leading-snug", children: issue.message })] }, `${issue.point}:${issue.id ?? index}`))) }));
32
55
  }
33
56
  //# sourceMappingURL=section-nav.js.map