@elytracms/studio 0.0.14 → 0.0.15

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 (93) 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/builder/builder-store.d.ts +85 -39
  13. package/dist/components/builder/builder-store.js +29 -125
  14. package/dist/components/builder/builder-store.js.map +1 -1
  15. package/dist/components/builder/canvas.js +23 -55
  16. package/dist/components/builder/canvas.js.map +1 -1
  17. package/dist/components/builder/clipboard-actions.js +3 -3
  18. package/dist/components/builder/clipboard-actions.js.map +1 -1
  19. package/dist/components/builder/composition-field-editor.js +13 -23
  20. package/dist/components/builder/composition-field-editor.js.map +1 -1
  21. package/dist/components/builder/insert-context.js +1 -1
  22. package/dist/components/builder/insert-context.js.map +1 -1
  23. package/dist/components/builder/page-tree.js +3 -4
  24. package/dist/components/builder/page-tree.js.map +1 -1
  25. package/dist/components/builder/property-inspector.js +4 -64
  26. package/dist/components/builder/property-inspector.js.map +1 -1
  27. package/dist/components/cms/cell.d.ts +0 -4
  28. package/dist/components/cms/cell.js +1 -5
  29. package/dist/components/cms/cell.js.map +1 -1
  30. package/dist/components/cms/cms-store.d.ts +1 -1
  31. package/dist/components/cms/cms-store.js +4 -9
  32. package/dist/components/cms/cms-store.js.map +1 -1
  33. package/dist/components/cms/collection-table.js +1 -1
  34. package/dist/components/cms/collection-table.js.map +1 -1
  35. package/dist/components/cms/detail-editor.js +2 -2
  36. package/dist/components/cms/detail-editor.js.map +1 -1
  37. package/dist/components/cms/doc-state.d.ts +0 -5
  38. package/dist/components/cms/doc-state.js +1 -7
  39. package/dist/components/cms/doc-state.js.map +1 -1
  40. package/dist/components/cms/editable-cell.js +1 -1
  41. package/dist/components/cms/editable-cell.js.map +1 -1
  42. package/dist/components/collab/collab-context.d.ts +6 -14
  43. package/dist/components/collab/collab-context.js +3 -12
  44. package/dist/components/collab/collab-context.js.map +1 -1
  45. package/dist/components/collab/collab-seed.d.ts +7 -19
  46. package/dist/components/collab/collab-seed.js +9 -31
  47. package/dist/components/collab/collab-seed.js.map +1 -1
  48. package/dist/components/components/registry-browser.js +6 -12
  49. package/dist/components/components/registry-browser.js.map +1 -1
  50. package/dist/components/guards.d.ts +0 -9
  51. package/dist/components/guards.js +0 -12
  52. package/dist/components/guards.js.map +1 -1
  53. package/dist/components/section-state.d.ts +0 -2
  54. package/dist/components/section-state.js +0 -9
  55. package/dist/components/section-state.js.map +1 -1
  56. package/dist/components/section-surface.d.ts +3 -7
  57. package/dist/components/section-surface.js +5 -12
  58. package/dist/components/section-surface.js.map +1 -1
  59. package/dist/components/sections.d.ts +0 -1
  60. package/dist/components/sections.js +0 -10
  61. package/dist/components/sections.js.map +1 -1
  62. package/dist/components/studio-project-screen.js +13 -14
  63. package/dist/components/studio-project-screen.js.map +1 -1
  64. package/dist/components/studio-shell.d.ts +0 -4
  65. package/dist/components/studio-shell.js +1 -5
  66. package/dist/components/studio-shell.js.map +1 -1
  67. package/dist/lib/operations/graph-commands.d.ts +2 -6
  68. package/dist/lib/operations/graph-commands.js +1 -2
  69. package/dist/lib/operations/graph-commands.js.map +1 -1
  70. package/dist/lib/operations/index.d.ts +0 -2
  71. package/dist/lib/operations/index.js +0 -2
  72. package/dist/lib/operations/index.js.map +1 -1
  73. package/dist/lib/operations/session-hydration.d.ts +14 -24
  74. package/dist/lib/operations/session-hydration.js +15 -34
  75. package/dist/lib/operations/session-hydration.js.map +1 -1
  76. package/dist/lib/persistence/convex-adapter.d.ts +1 -3
  77. package/dist/lib/persistence/convex-adapter.js +1 -154
  78. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  79. package/dist/lib/persona/labels.js +0 -2
  80. package/dist/lib/persona/labels.js.map +1 -1
  81. package/dist/lib/references/reference-model.d.ts +0 -16
  82. package/dist/lib/references/reference-model.js +2 -54
  83. package/dist/lib/references/reference-model.js.map +1 -1
  84. package/dist/lib/references/references-store.js +3 -4
  85. package/dist/lib/references/references-store.js.map +1 -1
  86. package/dist/lib/registry/index.d.ts +0 -16
  87. package/dist/lib/registry/index.js +0 -15
  88. package/dist/lib/registry/index.js.map +1 -1
  89. package/dist/lib/webhooks/tags.js +2 -16
  90. package/dist/lib/webhooks/tags.js.map +1 -1
  91. package/package.json +3 -2
  92. package/convex/graphs.ts +0 -55
  93. 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"]}
@@ -20,5 +20,3 @@ export declare function SectionStateView({ state, title, description, action, cl
20
20
  export declare function SectionSkeleton({ className }: {
21
21
  className?: string;
22
22
  }): import("react").JSX.Element;
23
- /** The full set of forceable states, for fixtures and the state switcher. */
24
- export declare const FORCEABLE_STATES: readonly SectionState[];
@@ -66,13 +66,4 @@ export function SectionStateView({ state, title, description, action, className,
66
66
  export function SectionSkeleton({ className }) {
67
67
  return (_jsxs("div", { "data-section-state": "loading", className: cn('space-y-4 p-6', className), "aria-busy": true, children: [_jsx("div", { className: "h-7 w-48 animate-pulse rounded-md bg-muted" }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsx("div", { className: "h-28 animate-pulse rounded-xl bg-muted/70" }, i))) })] }));
68
68
  }
69
- /** The full set of forceable states, for fixtures and the state switcher. */
70
- export const FORCEABLE_STATES = [
71
- 'working',
72
- 'empty',
73
- 'loading',
74
- 'error',
75
- 'unavailable',
76
- 'planned',
77
- ];
78
69
  //# sourceMappingURL=section-state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"section-state.js","sourceRoot":"","sources":["../../src/components/section-state.tsx"],"names":[],"mappings":"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","sourcesContent":["import { CircleAlert, Clock, Inbox, Loader2, Lock, type LucideIcon } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport type { SectionAvailability, SectionState } from '@elytracms/core/studio-core'\nimport { Badge, Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * Resolve the runtime state of a section (brief §8.1, §11). A `forced` value\n * (e.g. from a `?state=` param) wins, making every state reachable\n * deterministically; otherwise availability decides.\n */\nexport function resolveSectionState(\n availability: SectionAvailability,\n forced?: SectionState | null,\n): SectionState {\n if (forced) return forced\n if (availability === 'planned') return 'planned'\n if (availability === 'unavailable') return 'unavailable'\n return 'working'\n}\n\ninterface StatePreset {\n icon: LucideIcon\n title: string\n description: string\n badge: { label: string; variant: 'outline' | 'warning' | 'error' | 'info' }\n spin?: boolean\n}\n\nconst PRESETS: Record<Exclude<SectionState, 'working'>, StatePreset> = {\n empty: {\n icon: Inbox,\n title: 'Nothing here yet',\n description: 'This section has no content for the active project.',\n badge: { label: 'empty', variant: 'outline' },\n },\n loading: {\n icon: Loader2,\n title: 'Loading…',\n description: 'Fetching the latest state for this section.',\n badge: { label: 'loading', variant: 'info' },\n spin: true,\n },\n error: {\n icon: CircleAlert,\n title: 'Something went wrong',\n description: 'This section could not be loaded. Try again in a moment.',\n badge: { label: 'error', variant: 'error' },\n },\n unavailable: {\n icon: Lock,\n title: 'Not available',\n description: 'This section is not available for the active project.',\n badge: { label: 'unavailable', variant: 'outline' },\n },\n planned: {\n icon: Clock,\n title: 'Planned',\n description: 'This surface is planned and not yet enabled for this project.',\n badge: { label: 'planned', variant: 'warning' },\n },\n}\n\nexport interface SectionStateViewProps {\n state: Exclude<SectionState, 'working'>\n /** Section-specific heading, e.g. \"CMS\" — overrides the generic preset title. */\n title?: string\n description?: ReactNode\n action?: ReactNode\n className?: string\n}\n\n/** Loading uses a skeleton; the rest use a centered placeholder. Consistent across sections. */\nexport function SectionStateView({\n state,\n title,\n description,\n action,\n className,\n}: SectionStateViewProps) {\n if (state === 'loading') return <SectionSkeleton className={className} />\n\n const preset = PRESETS[state]\n const Icon = preset.icon\n const tone =\n state === 'error'\n ? 'border-destructive/30 bg-destructive/5'\n : state === 'planned'\n ? 'border-warning/30 bg-warning/5'\n : 'border-border bg-card/40'\n\n return (\n <div\n data-section-state={state}\n className={cn('grid min-h-[18rem] place-items-center p-8', className)}\n >\n <Stack\n gap=\"3\"\n align=\"center\"\n className={cn('max-w-sm rounded-xl border border-dashed px-8 py-10 text-center', tone)}\n >\n <span className=\"grid size-11 place-items-center rounded-lg border border-border bg-background text-muted-foreground\">\n <Icon className=\"size-5\" />\n </span>\n <Stack gap=\"1\" align=\"center\">\n <Heading level={2} size=\"h3\">\n {title ?? preset.title}\n </Heading>\n <Text size=\"sm\" tone=\"muted\">\n {description ?? preset.description}\n </Text>\n </Stack>\n <Badge variant={preset.badge.variant}>{preset.badge.label}</Badge>\n {action ? <div className=\"pt-1\">{action}</div> : null}\n </Stack>\n </div>\n )\n}\n\n/** Shimmer skeleton standing in for an in-flight section load. */\nexport function SectionSkeleton({ className }: { className?: string }) {\n return (\n <div data-section-state=\"loading\" className={cn('space-y-4 p-6', className)} aria-busy>\n <div className=\"h-7 w-48 animate-pulse rounded-md bg-muted\" />\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-28 animate-pulse rounded-xl bg-muted/70\" />\n ))}\n </div>\n </div>\n )\n}\n\n/** The full set of forceable states, for fixtures and the state switcher. */\nexport const FORCEABLE_STATES: readonly SectionState[] = [\n 'working',\n 'empty',\n 'loading',\n 'error',\n 'unavailable',\n 'planned',\n]\n"]}
1
+ {"version":3,"file":"section-state.js","sourceRoot":"","sources":["../../src/components/section-state.tsx"],"names":[],"mappings":"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","sourcesContent":["import { CircleAlert, Clock, Inbox, Loader2, Lock, type LucideIcon } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport type { SectionAvailability, SectionState } from '@elytracms/core/studio-core'\nimport { Badge, Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * Resolve the runtime state of a section (brief §8.1, §11). A `forced` value\n * (e.g. from a `?state=` param) wins, making every state reachable\n * deterministically; otherwise availability decides.\n */\nexport function resolveSectionState(\n availability: SectionAvailability,\n forced?: SectionState | null,\n): SectionState {\n if (forced) return forced\n if (availability === 'planned') return 'planned'\n if (availability === 'unavailable') return 'unavailable'\n return 'working'\n}\n\ninterface StatePreset {\n icon: LucideIcon\n title: string\n description: string\n badge: { label: string; variant: 'outline' | 'warning' | 'error' | 'info' }\n spin?: boolean\n}\n\nconst PRESETS: Record<Exclude<SectionState, 'working'>, StatePreset> = {\n empty: {\n icon: Inbox,\n title: 'Nothing here yet',\n description: 'This section has no content for the active project.',\n badge: { label: 'empty', variant: 'outline' },\n },\n loading: {\n icon: Loader2,\n title: 'Loading…',\n description: 'Fetching the latest state for this section.',\n badge: { label: 'loading', variant: 'info' },\n spin: true,\n },\n error: {\n icon: CircleAlert,\n title: 'Something went wrong',\n description: 'This section could not be loaded. Try again in a moment.',\n badge: { label: 'error', variant: 'error' },\n },\n unavailable: {\n icon: Lock,\n title: 'Not available',\n description: 'This section is not available for the active project.',\n badge: { label: 'unavailable', variant: 'outline' },\n },\n planned: {\n icon: Clock,\n title: 'Planned',\n description: 'This surface is planned and not yet enabled for this project.',\n badge: { label: 'planned', variant: 'warning' },\n },\n}\n\nexport interface SectionStateViewProps {\n state: Exclude<SectionState, 'working'>\n /** Section-specific heading, e.g. \"CMS\" — overrides the generic preset title. */\n title?: string\n description?: ReactNode\n action?: ReactNode\n className?: string\n}\n\n/** Loading uses a skeleton; the rest use a centered placeholder. Consistent across sections. */\nexport function SectionStateView({\n state,\n title,\n description,\n action,\n className,\n}: SectionStateViewProps) {\n if (state === 'loading') return <SectionSkeleton className={className} />\n\n const preset = PRESETS[state]\n const Icon = preset.icon\n const tone =\n state === 'error'\n ? 'border-destructive/30 bg-destructive/5'\n : state === 'planned'\n ? 'border-warning/30 bg-warning/5'\n : 'border-border bg-card/40'\n\n return (\n <div\n data-section-state={state}\n className={cn('grid min-h-[18rem] place-items-center p-8', className)}\n >\n <Stack\n gap=\"3\"\n align=\"center\"\n className={cn('max-w-sm rounded-xl border border-dashed px-8 py-10 text-center', tone)}\n >\n <span className=\"grid size-11 place-items-center rounded-lg border border-border bg-background text-muted-foreground\">\n <Icon className=\"size-5\" />\n </span>\n <Stack gap=\"1\" align=\"center\">\n <Heading level={2} size=\"h3\">\n {title ?? preset.title}\n </Heading>\n <Text size=\"sm\" tone=\"muted\">\n {description ?? preset.description}\n </Text>\n </Stack>\n <Badge variant={preset.badge.variant}>{preset.badge.label}</Badge>\n {action ? <div className=\"pt-1\">{action}</div> : null}\n </Stack>\n </div>\n )\n}\n\n/** Shimmer skeleton standing in for an in-flight section load. */\nexport function SectionSkeleton({ className }: { className?: string }) {\n return (\n <div data-section-state=\"loading\" className={cn('space-y-4 p-6', className)} aria-busy>\n <div className=\"h-7 w-48 animate-pulse rounded-md bg-muted\" />\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-28 animate-pulse rounded-xl bg-muted/70\" />\n ))}\n </div>\n </div>\n )\n}\n"]}
@@ -7,19 +7,15 @@ export interface SectionSurfaceProps {
7
7
  section: SectionId;
8
8
  /** Forced state from `?state=`; overrides availability. */
9
9
  forcedState?: SectionState;
10
- /** Working-state content. When omitted, a phase placeholder is shown. */
10
+ /** Working-state content. */
11
11
  children?: ReactNode;
12
12
  }
13
13
  /**
14
14
  * Resolves a section's deterministic state (brief §8.1, §11) and renders it: the
15
- * working content (or a phase placeholder) when `working`, else a consistent
16
- * state view. A small state switcher makes every state reachable for inspection.
15
+ * working content when `working`, else a consistent state view. A small state
16
+ * switcher makes every state reachable for inspection.
17
17
  */
18
18
  export declare function SectionSurface({ project, section, forcedState, children }: SectionSurfaceProps): import("react").JSX.Element;
19
- /** Placeholder for an available section whose live surface lands in a later phase. */
20
- export declare function PhasePlaceholder({ section }: {
21
- section: SectionId;
22
- }): import("react").JSX.Element;
23
19
  /**
24
20
  * Deterministic state switcher (brief §11). Links to the same section with each
25
21
  * `?state=` value so agents/humans can reproduce every state by URL or click.
@@ -1,8 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router';
3
- import { ArrowUpRight } from 'lucide-react';
4
3
  import { availabilityOf, sectionStateSchema } from '@elytracms/core/studio-core';
5
- import { Badge, Button, Code, Row, Stack, Text } from '@elytracms/studio/ui';
4
+ import { Code, Row, Stack, Text } from '@elytracms/studio/ui';
6
5
  import { cn } from '@elytracms/studio/lib/utils';
7
6
  import { resolveSectionState, SectionStateView } from './section-state';
8
7
  import { sectionNavItem } from './sections';
@@ -13,19 +12,13 @@ export function parseSectionState(raw) {
13
12
  }
14
13
  /**
15
14
  * Resolves a section's deterministic state (brief §8.1, §11) and renders it: the
16
- * working content (or a phase placeholder) when `working`, else a consistent
17
- * state view. A small state switcher makes every state reachable for inspection.
15
+ * working content when `working`, else a consistent state view. A small state
16
+ * switcher makes every state reachable for inspection.
18
17
  */
19
18
  export function SectionSurface({ project, section, forcedState, children }) {
20
19
  const availability = availabilityOf(project.settings.sectionAvailability, section);
21
20
  const state = resolveSectionState(availability, forcedState);
22
- return (_jsxs(Stack, { gap: "none", className: "min-h-full", children: [_jsx(StateSwitcher, { projectId: project.id, section: section, current: forcedState }), state === 'working' ? ((children ?? _jsx(PhasePlaceholder, { section: section }))) : (_jsx(SectionStateView, { state: state, title: sectionNavItem(section).label }))] }));
23
- }
24
- /** Placeholder for an available section whose live surface lands in a later phase. */
25
- export function PhasePlaceholder({ section }) {
26
- const item = sectionNavItem(section);
27
- const Icon = item.icon;
28
- return (_jsx("div", { "data-section-state": "working", className: "grid flex-1 place-items-center p-8", children: _jsxs(Stack, { gap: "3", align: "center", className: "max-w-md text-center", children: [_jsx("span", { className: "grid size-12 place-items-center rounded-xl border border-border bg-card text-primary", children: _jsx(Icon, { className: "size-6" }) }), _jsxs(Stack, { gap: "1", align: "center", children: [_jsxs(Text, { weight: "semibold", children: [item.label, " is enabled"] }), _jsxs(Text, { size: "sm", tone: "muted", children: [item.description, " The full surface arrives in ", item.phase, "."] })] }), _jsx(Badge, { variant: "primary", children: item.phase })] }) }));
21
+ return (_jsxs(Stack, { gap: "none", className: "min-h-full", children: [_jsx(StateSwitcher, { projectId: project.id, section: section, current: forcedState }), state === 'working' ? (children) : (_jsx(SectionStateView, { state: state, title: sectionNavItem(section).label }))] }));
29
22
  }
30
23
  /**
31
24
  * Deterministic state switcher (brief §11). Links to the same section with each
@@ -41,6 +34,6 @@ export function StateSwitcher({ projectId, section, current, }) {
41
34
  return (_jsx(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: section }, search: { state: s }, className: cn('rounded-md px-2 py-0.5 font-mono text-xs transition-colors', active
42
35
  ? 'bg-accent text-accent-foreground'
43
36
  : 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: s }, s));
44
- }), _jsx(Code, { tone: "muted", className: "ml-2 hidden sm:inline-flex", children: activeKey === 'default' ? 'auto' : `?state=${activeKey}` }), _jsx("span", { className: "flex-1" }), _jsx(Button, { variant: "ghost", size: "sm", asChild: true, className: "hidden md:inline-flex", children: _jsxs(Link, { to: "/", className: "gap-1", children: ["Docs ", _jsx(ArrowUpRight, {})] }) })] }));
37
+ }), _jsx(Code, { tone: "muted", className: "ml-2 hidden sm:inline-flex", children: activeKey === 'default' ? 'auto' : `?state=${activeKey}` })] }));
45
38
  }
46
39
  //# sourceMappingURL=section-surface.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"section-surface.js","sourceRoot":"","sources":["../../src/components/section-surface.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE3C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAEhF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC5E,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C,0FAA0F;AAC1F,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;IAChD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AACjD,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAuB;IAC7F,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAA;IAClF,MAAM,KAAK,GAAG,mBAAmB,CAAC,YAAY,EAAE,WAAW,CAAC,CAAA;IAE5D,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,MAAM,EAAC,SAAS,EAAC,YAAY,aACtC,KAAC,aAAa,IAAC,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,GAAI,EAC/E,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACrB,CAAC,QAAQ,IAAI,KAAC,gBAAgB,IAAC,OAAO,EAAE,OAAO,GAAI,CAAC,CACrD,CAAC,CAAC,CAAC,CACF,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,KAAK,GAAI,CACzE,IACK,CACT,CAAA;AACH,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,gBAAgB,CAAC,EAAE,OAAO,EAA0B;IAClE,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;IACtB,OAAO,CACL,oCAAwB,SAAS,EAAC,SAAS,EAAC,oCAAoC,YAC9E,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,sBAAsB,aAC5D,eAAM,SAAS,EAAC,sFAAsF,YACpG,KAAC,IAAI,IAAC,SAAS,EAAC,QAAQ,GAAG,GACtB,EACP,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aAC3B,MAAC,IAAI,IAAC,MAAM,EAAC,UAAU,aAAE,IAAI,CAAC,KAAK,mBAAmB,EACtD,MAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,aACzB,IAAI,CAAC,WAAW,mCAA+B,IAAI,CAAC,KAAK,SACrD,IACD,EACR,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,YAAE,IAAI,CAAC,KAAK,GAAS,IACvC,GACJ,CACP,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,SAAS,EACT,OAAO,EACP,OAAO,GAKR;IACC,MAAM,MAAM,GAAmB,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,CAAC,CAAA;IACjG,MAAM,SAAS,GAAG,OAAO,IAAI,SAAS,CAAA;IACtC,OAAO,CACL,MAAC,GAAG,IACF,GAAG,EAAC,GAAG,EACP,IAAI,QACJ,SAAS,EAAC,oEAAoE,iBAClE,gBAAgB,aAE5B,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,QAAC,SAAS,EAAC,MAAM,sBAE5C,EACP,KAAC,IAAI,IACH,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,EACtC,MAAM,EAAE,EAAE,EACV,SAAS,EAAE,EAAE,CACX,4DAA4D,EAC5D,SAAS,KAAK,SAAS;oBACrB,CAAC,CAAC,kCAAkC;oBACpC,CAAC,CAAC,4DAA4D,CACjE,wBAGI,EACN,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBAChB,MAAM,MAAM,GAAG,CAAC,KAAK,SAAS,CAAA;gBAC9B,OAAO,CACL,KAAC,IAAI,IAEH,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,EACtC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EACpB,SAAS,EAAE,EAAE,CACX,4DAA4D,EAC5D,MAAM;wBACJ,CAAC,CAAC,kCAAkC;wBACpC,CAAC,CAAC,4DAA4D,CACjE,YAEA,CAAC,IAXG,CAAC,CAYD,CACR,CAAA;YACH,CAAC,CAAC,EACF,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,4BAA4B,YACtD,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,SAAS,EAAE,GACpD,EACP,eAAM,SAAS,EAAC,QAAQ,GAAG,EAC3B,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,QAAC,SAAS,EAAC,uBAAuB,YACzE,MAAC,IAAI,IAAC,EAAE,EAAC,GAAG,EAAC,SAAS,EAAC,OAAO,sBACvB,KAAC,YAAY,KAAG,IAChB,GACA,IACL,CACP,CAAA;AACH,CAAC","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router'\nimport { ArrowUpRight } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport { availabilityOf, sectionStateSchema } from '@elytracms/core/studio-core'\nimport type { Project, ProjectId, SectionId, SectionState } from '@elytracms/core/studio-core'\nimport { Badge, Button, Code, Row, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { resolveSectionState, SectionStateView } from './section-state'\nimport { sectionNavItem } from './sections'\n\n/** Parse a `?state=` search param into a SectionState (undefined when absent/invalid). */\nexport function parseSectionState(raw: unknown): SectionState | undefined {\n const parsed = sectionStateSchema.safeParse(raw)\n return parsed.success ? parsed.data : undefined\n}\n\nexport interface SectionSurfaceProps {\n project: Project\n section: SectionId\n /** Forced state from `?state=`; overrides availability. */\n forcedState?: SectionState\n /** Working-state content. When omitted, a phase placeholder is shown. */\n children?: ReactNode\n}\n\n/**\n * Resolves a section's deterministic state (brief §8.1, §11) and renders it: the\n * working content (or a phase placeholder) when `working`, else a consistent\n * state view. A small state switcher makes every state reachable for inspection.\n */\nexport function SectionSurface({ project, section, forcedState, children }: SectionSurfaceProps) {\n const availability = availabilityOf(project.settings.sectionAvailability, section)\n const state = resolveSectionState(availability, forcedState)\n\n return (\n <Stack gap=\"none\" className=\"min-h-full\">\n <StateSwitcher projectId={project.id} section={section} current={forcedState} />\n {state === 'working' ? (\n (children ?? <PhasePlaceholder section={section} />)\n ) : (\n <SectionStateView state={state} title={sectionNavItem(section).label} />\n )}\n </Stack>\n )\n}\n\n/** Placeholder for an available section whose live surface lands in a later phase. */\nexport function PhasePlaceholder({ section }: { section: SectionId }) {\n const item = sectionNavItem(section)\n const Icon = item.icon\n return (\n <div data-section-state=\"working\" className=\"grid flex-1 place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"max-w-md text-center\">\n <span className=\"grid size-12 place-items-center rounded-xl border border-border bg-card text-primary\">\n <Icon className=\"size-6\" />\n </span>\n <Stack gap=\"1\" align=\"center\">\n <Text weight=\"semibold\">{item.label} is enabled</Text>\n <Text size=\"sm\" tone=\"muted\">\n {item.description} The full surface arrives in {item.phase}.\n </Text>\n </Stack>\n <Badge variant=\"primary\">{item.phase}</Badge>\n </Stack>\n </div>\n )\n}\n\n/**\n * Deterministic state switcher (brief §11). Links to the same section with each\n * `?state=` value so agents/humans can reproduce every state by URL or click.\n */\nexport function StateSwitcher({\n projectId,\n section,\n current,\n}: {\n projectId: ProjectId\n section: SectionId\n current?: SectionState\n}) {\n const states: SectionState[] = ['working', 'empty', 'loading', 'error', 'unavailable', 'planned']\n const activeKey = current ?? 'default'\n return (\n <Row\n gap=\"1\"\n wrap\n className=\"shrink-0 border-b border-dashed border-border/70 px-4 py-2 sm:px-6\"\n data-testid=\"state-switcher\"\n >\n <Text size=\"xs\" tone=\"subtle\" mono className=\"mr-1\">\n state\n </Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{}}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n activeKey === 'default'\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n default\n </Link>\n {states.map((s) => {\n const active = s === activeKey\n return (\n <Link\n key={s}\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{ state: s }}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n active\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {s}\n </Link>\n )\n })}\n <Code tone=\"muted\" className=\"ml-2 hidden sm:inline-flex\">\n {activeKey === 'default' ? 'auto' : `?state=${activeKey}`}\n </Code>\n <span className=\"flex-1\" />\n <Button variant=\"ghost\" size=\"sm\" asChild className=\"hidden md:inline-flex\">\n <Link to=\"/\" className=\"gap-1\">\n Docs <ArrowUpRight />\n </Link>\n </Button>\n </Row>\n )\n}\n"]}
1
+ {"version":3,"file":"section-surface.js","sourceRoot":"","sources":["../../src/components/section-surface.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router'\nimport type { ReactNode } from 'react'\nimport { availabilityOf, sectionStateSchema } from '@elytracms/core/studio-core'\nimport type { Project, ProjectId, SectionId, SectionState } from '@elytracms/core/studio-core'\nimport { Code, Row, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { resolveSectionState, SectionStateView } from './section-state'\nimport { sectionNavItem } from './sections'\n\n/** Parse a `?state=` search param into a SectionState (undefined when absent/invalid). */\nexport function parseSectionState(raw: unknown): SectionState | undefined {\n const parsed = sectionStateSchema.safeParse(raw)\n return parsed.success ? parsed.data : undefined\n}\n\nexport interface SectionSurfaceProps {\n project: Project\n section: SectionId\n /** Forced state from `?state=`; overrides availability. */\n forcedState?: SectionState\n /** Working-state content. */\n children?: ReactNode\n}\n\n/**\n * Resolves a section's deterministic state (brief §8.1, §11) and renders it: the\n * working content when `working`, else a consistent state view. A small state\n * switcher makes every state reachable for inspection.\n */\nexport function SectionSurface({ project, section, forcedState, children }: SectionSurfaceProps) {\n const availability = availabilityOf(project.settings.sectionAvailability, section)\n const state = resolveSectionState(availability, forcedState)\n\n return (\n <Stack gap=\"none\" className=\"min-h-full\">\n <StateSwitcher projectId={project.id} section={section} current={forcedState} />\n {state === 'working' ? (\n children\n ) : (\n <SectionStateView state={state} title={sectionNavItem(section).label} />\n )}\n </Stack>\n )\n}\n\n/**\n * Deterministic state switcher (brief §11). Links to the same section with each\n * `?state=` value so agents/humans can reproduce every state by URL or click.\n */\nexport function StateSwitcher({\n projectId,\n section,\n current,\n}: {\n projectId: ProjectId\n section: SectionId\n current?: SectionState\n}) {\n const states: SectionState[] = ['working', 'empty', 'loading', 'error', 'unavailable', 'planned']\n const activeKey = current ?? 'default'\n return (\n <Row\n gap=\"1\"\n wrap\n className=\"shrink-0 border-b border-dashed border-border/70 px-4 py-2 sm:px-6\"\n data-testid=\"state-switcher\"\n >\n <Text size=\"xs\" tone=\"subtle\" mono className=\"mr-1\">\n state\n </Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{}}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n activeKey === 'default'\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n default\n </Link>\n {states.map((s) => {\n const active = s === activeKey\n return (\n <Link\n key={s}\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{ state: s }}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n active\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {s}\n </Link>\n )\n })}\n <Code tone=\"muted\" className=\"ml-2 hidden sm:inline-flex\">\n {activeKey === 'default' ? 'auto' : `?state=${activeKey}`}\n </Code>\n </Row>\n )\n}\n"]}
@@ -12,7 +12,6 @@ export interface SectionNavItem {
12
12
  label: string;
13
13
  description: string;
14
14
  icon: LucideIcon;
15
- phase: string;
16
15
  }
17
16
  /** Navigation items in canonical order (brief §8.1 + EC-170). */
18
17
  export declare const SECTION_NAV_ITEMS: readonly SectionNavItem[];
@@ -1,14 +1,5 @@
1
1
  import { Blocks, Database, Image, Puzzle, Settings, Signpost } from 'lucide-react';
2
2
  import { SECTIONS, SECTION_IDS, availabilityOf, sectionIdSchema } from '@elytracms/core/studio-core';
3
- /** Which Studio phase delivers each section's live surface (for placeholders). */
4
- const SECTION_PHASE = {
5
- components: 'Phase 4',
6
- cms: 'Phase 6',
7
- routes: 'Milestone B',
8
- assets: 'Phase 6',
9
- extensions: 'Phase 11',
10
- settings: 'Phase 2',
11
- };
12
3
  const ICONS = {
13
4
  components: Blocks,
14
5
  cms: Database,
@@ -34,7 +25,6 @@ export const SECTION_NAV_ITEMS = NAV_IDS.map((id) => ({
34
25
  label: descriptorOf(id).label,
35
26
  description: descriptorOf(id).description,
36
27
  icon: ICONS[id],
37
- phase: SECTION_PHASE[id],
38
28
  }));
39
29
  export function sectionNavItem(id) {
40
30
  return SECTION_NAV_ITEMS.find((item) => item.id === id);