@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
@@ -0,0 +1,95 @@
1
+ import type { LucideIcon } from 'lucide-react';
2
+ import type { CustomizationIssue, Project, SectionAvailability } from '@elytracms/core/studio-core';
3
+ import { type ResolvedAppViews } from './module';
4
+ /**
5
+ * EC-237 — the configurable sidebar, an instance of the EC-236 customization seam.
6
+ *
7
+ * The section rail is a consumer-extendable ARRAY of nav entries — items (each
8
+ * targeting an EC-239 app-view module, optionally deep-linked to a resource),
9
+ * groups of sub-items, and separators. A consuming repo supplies its own array
10
+ * through `config.sidebar`; omitted → the studio derives the default rail from its
11
+ * module registry (every non-hidden module, in order). The supplied array is
12
+ * resolved through the shared {@link resolveOverrides} core: a duplicate id, an
13
+ * item whose target module isn't registered ("unknown view"), or a deep-link that
14
+ * doesn't resolve becomes a visible {@link CustomizationIssue} and the entry is
15
+ * dropped — never a silent failure. Sub-views (a focused "Taxonomy") are EC-239
16
+ * modules; this file decides only how the rail PRESENTS and orders them.
17
+ */
18
+ /** A nav item targeting an app-view module, optionally deep-linked to a resource. */
19
+ export interface SidebarItemConfig {
20
+ kind: 'item';
21
+ /** Stable id (defaults to `moduleId` via {@link navItem}). */
22
+ id: string;
23
+ /** The app-view module this item mounts — must be in the registry. */
24
+ moduleId: string;
25
+ /** Overrides the module's rail label. */
26
+ label?: string;
27
+ /** Overrides the module's rail icon. */
28
+ icon?: LucideIcon;
29
+ /** Trailing path segments targeting a specific resource, e.g. `['category']`. */
30
+ deepLink?: readonly string[];
31
+ }
32
+ export interface SidebarGroupConfig {
33
+ kind: 'group';
34
+ id: string;
35
+ label: string;
36
+ items: SidebarItemConfig[];
37
+ }
38
+ export interface SidebarSeparatorConfig {
39
+ kind: 'separator';
40
+ id?: string;
41
+ }
42
+ export type SidebarNodeConfig = SidebarItemConfig | SidebarGroupConfig | SidebarSeparatorConfig;
43
+ export interface ResolvedSidebarItem {
44
+ kind: 'item';
45
+ id: string;
46
+ moduleId: string;
47
+ label: string;
48
+ icon: LucideIcon;
49
+ /** The `_splat` to navigate to (moduleId + deep-link segments). */
50
+ path: string;
51
+ availability: SectionAvailability;
52
+ }
53
+ export interface ResolvedSidebarGroup {
54
+ kind: 'group';
55
+ id: string;
56
+ label: string;
57
+ items: ResolvedSidebarItem[];
58
+ }
59
+ export interface ResolvedSidebarSeparator {
60
+ kind: 'separator';
61
+ id: string;
62
+ }
63
+ export type ResolvedSidebarNode = ResolvedSidebarItem | ResolvedSidebarGroup | ResolvedSidebarSeparator;
64
+ export interface ResolvedSidebar {
65
+ nodes: ResolvedSidebarNode[];
66
+ issues: CustomizationIssue[];
67
+ }
68
+ /** A nav item; `id` defaults to `moduleId` when omitted. */
69
+ export declare function navItem(config: Omit<SidebarItemConfig, 'kind' | 'id'> & {
70
+ id?: string;
71
+ }): SidebarItemConfig;
72
+ /** A labelled group of sub-items; `id` defaults to a slug of the label. */
73
+ export declare function navGroup(label: string, items: SidebarItemConfig[], opts?: {
74
+ id?: string;
75
+ }): SidebarGroupConfig;
76
+ /** A divider between rail sections. */
77
+ export declare function navSeparator(id?: string): SidebarSeparatorConfig;
78
+ /** Identity helper that types a consumer's `config.sidebar` array. */
79
+ export declare function defineSidebar(nodes: SidebarNodeConfig[]): SidebarNodeConfig[];
80
+ /**
81
+ * Resolve a consumer sidebar (or the default rail) over the app-view registry
82
+ * through the shared customization core (EC-236). Every node — items, group
83
+ * headers, nested items, separators — is keyed by id and run through
84
+ * {@link resolveOverrides} for dedup + per-node validation, so a duplicate id or
85
+ * an invalid item is a visible issue and is dropped; the surviving nodes are
86
+ * rebuilt into the rail tree (groups keep their surviving sub-items).
87
+ */
88
+ export declare function resolveSidebar(registry: ResolvedAppViews, project: Project, entries?: readonly SidebarNodeConfig[]): ResolvedSidebar;
89
+ /** The active module id parsed from a studio pathname `/<projectId>/<module>[/…]`. */
90
+ export declare function activeModuleFromPath(pathname: string): string | null;
91
+ /**
92
+ * The active project's resolved sidebar — the default rail or `config.sidebar`,
93
+ * resolved over the module registry through the EC-236 seam (static per boot).
94
+ */
95
+ export declare function useSidebar(project: Project): ResolvedSidebar;
@@ -0,0 +1,153 @@
1
+ import { useMemo } from 'react';
2
+ import { resolveOverrides } from '@elytracms/core/studio-core';
3
+ import { resolveDeploymentSidebar } from '@elytracms/studio/lib/workspace';
4
+ import { moduleAvailability } from './module';
5
+ import { useAppViews } from './default-modules';
6
+ // --- Builder helpers (the typed consumer API; carry live icons) ---
7
+ /** A nav item; `id` defaults to `moduleId` when omitted. */
8
+ export function navItem(config) {
9
+ return {
10
+ kind: 'item',
11
+ id: config.id ?? config.moduleId,
12
+ moduleId: config.moduleId,
13
+ ...(config.label !== undefined ? { label: config.label } : {}),
14
+ ...(config.icon !== undefined ? { icon: config.icon } : {}),
15
+ ...(config.deepLink !== undefined ? { deepLink: config.deepLink } : {}),
16
+ };
17
+ }
18
+ /** A labelled group of sub-items; `id` defaults to a slug of the label. */
19
+ export function navGroup(label, items, opts = {}) {
20
+ return { kind: 'group', id: opts.id ?? slugify(label), label, items };
21
+ }
22
+ /** A divider between rail sections. */
23
+ export function navSeparator(id) {
24
+ return { kind: 'separator', ...(id !== undefined ? { id } : {}) };
25
+ }
26
+ /** Identity helper that types a consumer's `config.sidebar` array. */
27
+ export function defineSidebar(nodes) {
28
+ return nodes;
29
+ }
30
+ function slugify(label) {
31
+ return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'group';
32
+ }
33
+ // --- Resolution (EC-236 seam) ---
34
+ /** The default rail: every non-hidden module as a plain item, in registry order. */
35
+ function defaultSidebar(modules) {
36
+ return modules
37
+ .filter((module) => !module.hidden)
38
+ .map((module) => ({ kind: 'item', id: module.id, moduleId: module.id }));
39
+ }
40
+ /** The `_splat` path for an item (module id + its deep-link segments). */
41
+ function itemPath(item) {
42
+ return [item.moduleId, ...(item.deepLink ?? [])].join('/');
43
+ }
44
+ /** Validate one item against the registry — unknown module or unresolvable deep-link. */
45
+ function validateItem(registry, item) {
46
+ const module = registry.get(item.moduleId);
47
+ if (!module)
48
+ return `targets module "${item.moduleId}", which is not registered`;
49
+ if (item.deepLink && item.deepLink.length > 0) {
50
+ const parse = module.parsePath;
51
+ if (!parse) {
52
+ return `deep-links into module "${item.moduleId}", which takes no path`;
53
+ }
54
+ if (parse([...item.deepLink]) === null) {
55
+ return `deep-link "${item.deepLink.join('/')}" does not resolve in module "${item.moduleId}"`;
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ /**
61
+ * Resolve a consumer sidebar (or the default rail) over the app-view registry
62
+ * through the shared customization core (EC-236). Every node — items, group
63
+ * headers, nested items, separators — is keyed by id and run through
64
+ * {@link resolveOverrides} for dedup + per-node validation, so a duplicate id or
65
+ * an invalid item is a visible issue and is dropped; the surviving nodes are
66
+ * rebuilt into the rail tree (groups keep their surviving sub-items).
67
+ */
68
+ export function resolveSidebar(registry, project, entries) {
69
+ const config = entries ?? defaultSidebar(registry.modules);
70
+ const flat = [];
71
+ config.forEach((node, index) => {
72
+ if (node.kind === 'separator') {
73
+ flat.push({ id: node.id ?? `__separator:${index}`, value: { node, isItem: false } });
74
+ return;
75
+ }
76
+ if (node.kind === 'group') {
77
+ flat.push({ id: node.id, value: { node, isItem: false } });
78
+ node.items.forEach((item) => flat.push({ id: item.id, value: { node: item, isItem: true } }));
79
+ return;
80
+ }
81
+ flat.push({ id: node.id, value: { node, isItem: true } });
82
+ });
83
+ const resolved = resolveOverrides({
84
+ point: 'sidebar',
85
+ defaults: [],
86
+ overrides: flat,
87
+ allowNew: true,
88
+ validate: (entry) => entry.value.isItem
89
+ ? validateItem(registry, entry.value.node)
90
+ : null,
91
+ });
92
+ const survived = (id) => resolved.get(id) !== undefined;
93
+ const toItem = (item) => {
94
+ const module = registry.get(item.moduleId);
95
+ return {
96
+ kind: 'item',
97
+ id: item.id,
98
+ moduleId: item.moduleId,
99
+ label: item.label ?? module.label,
100
+ icon: item.icon ?? module.icon,
101
+ path: itemPath(item),
102
+ availability: moduleAvailability(project, module),
103
+ };
104
+ };
105
+ // Emit each surviving id at most once — `resolveOverrides` keeps the first of a
106
+ // duplicate, so the rebuild must skip later occurrences of that same id too.
107
+ const emitted = new Set();
108
+ const claim = (id) => {
109
+ if (!survived(id) || emitted.has(id))
110
+ return false;
111
+ emitted.add(id);
112
+ return true;
113
+ };
114
+ const nodes = [];
115
+ config.forEach((node, index) => {
116
+ if (node.kind === 'separator') {
117
+ const id = node.id ?? `__separator:${index}`;
118
+ if (claim(id))
119
+ nodes.push({ kind: 'separator', id });
120
+ return;
121
+ }
122
+ if (node.kind === 'group') {
123
+ if (!claim(node.id))
124
+ return;
125
+ const groupItems = node.items.filter((item) => claim(item.id)).map(toItem);
126
+ if (groupItems.length > 0) {
127
+ nodes.push({ kind: 'group', id: node.id, label: node.label, items: groupItems });
128
+ }
129
+ return;
130
+ }
131
+ if (claim(node.id))
132
+ nodes.push(toItem(node));
133
+ });
134
+ return { nodes, issues: resolved.issues };
135
+ }
136
+ /** The active module id parsed from a studio pathname `/<projectId>/<module>[/…]`. */
137
+ export function activeModuleFromPath(pathname) {
138
+ return pathname.split('/')[2] || null;
139
+ }
140
+ /**
141
+ * The active project's resolved sidebar — the default rail or `config.sidebar`,
142
+ * resolved over the module registry through the EC-236 seam (static per boot).
143
+ */
144
+ export function useSidebar(project) {
145
+ const registry = useAppViews();
146
+ return useMemo(() => {
147
+ // Config carries the sidebar opaquely (`SidebarEntry` = object); the studio
148
+ // owns + validates the rich item/group/separator shape, so cast it back here.
149
+ const entries = resolveDeploymentSidebar();
150
+ return resolveSidebar(registry, project, entries);
151
+ }, [registry, project]);
152
+ }
153
+ //# sourceMappingURL=sidebar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../../../src/components/app-views/sidebar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAE9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAA;AAC1E,OAAO,EAAE,kBAAkB,EAA6C,MAAM,UAAU,CAAA;AACxF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAgF/C,qEAAqE;AAErE,4DAA4D;AAC5D,MAAM,UAAU,OAAO,CACrB,MAAgE;IAEhE,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,QAAQ;QAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxE,CAAA;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,QAAQ,CACtB,KAAa,EACb,KAA0B,EAC1B,OAAwB,EAAE;IAE1B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AACvE,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,YAAY,CAAC,EAAW;IACtC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AACnE,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,aAAa,CAAC,KAA0B;IACtD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,OAAO,CAAC,KAAa;IAC5B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAA;AAClG,CAAC;AAED,mCAAmC;AAEnC,oFAAoF;AACpF,SAAS,cAAc,CAAC,OAAiC;IACvD,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;SAClC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;AAC5E,CAAC;AAED,0EAA0E;AAC1E,SAAS,QAAQ,CAAC,IAAuB;IACvC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC5D,CAAC;AAED,yFAAyF;AACzF,SAAS,YAAY,CAAC,QAA0B,EAAE,IAAuB;IACvE,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC1C,IAAI,CAAC,MAAM;QAAE,OAAO,mBAAmB,IAAI,CAAC,QAAQ,4BAA4B,CAAA;IAChF,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAA;QAC9B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,2BAA2B,IAAI,CAAC,QAAQ,wBAAwB,CAAA;QACzE,CAAC;QACD,IAAI,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACvC,OAAO,cAAc,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,iCAAiC,IAAI,CAAC,QAAQ,GAAG,CAAA;QAC/F,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,QAA0B,EAC1B,OAAgB,EAChB,OAAsC;IAEtC,MAAM,MAAM,GAAG,OAAO,IAAI,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;IAM1D,MAAM,IAAI,GAAkC,EAAE,CAAA;IAC9C,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC7B,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC9B,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,eAAe,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;YACpF,OAAM;QACR,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;YAC1D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAA;YAC7F,OAAM;QACR,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,gBAAgB,CAAO;QACtC,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE,EAAE;QACZ,SAAS,EAAE,IAAI;QACf,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAClB,KAAK,CAAC,KAAK,CAAC,MAAM;YAChB,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,IAAyB,CAAC;YAC/D,CAAC,CAAC,IAAI;KACX,CAAC,CAAA;IACF,MAAM,QAAQ,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,SAAS,CAAA;IAE/D,MAAM,MAAM,GAAG,CAAC,IAAuB,EAAuB,EAAE;QAC9D,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAE,CAAA;QAC3C,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK;YACjC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI;YAC9B,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;YACpB,YAAY,EAAE,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC;SAClD,CAAA;IACH,CAAC,CAAA;IAED,gFAAgF;IAChF,6EAA6E;IAC7E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;IACjC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE;QAC3B,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAO,KAAK,CAAA;QAClD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACf,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,MAAM,KAAK,GAA0B,EAAE,CAAA;IACvC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC7B,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC9B,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,eAAe,KAAK,EAAE,CAAA;YAC5C,IAAI,KAAK,CAAC,EAAE,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAA;YACpD,OAAM;QACR,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,OAAM;YAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAC1E,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAA;YAClF,CAAC;YACD,OAAM;QACR,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;IAEF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAA;AAC3C,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,OAAgB;IACzC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,4EAA4E;QAC5E,8EAA8E;QAC9E,MAAM,OAAO,GAAG,wBAAwB,EAA8C,CAAA;QACtF,OAAO,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACnD,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAA;AACzB,CAAC","sourcesContent":["import { useMemo } from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport { resolveOverrides } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue, Project, SectionAvailability } from '@elytracms/core/studio-core'\nimport { resolveDeploymentSidebar } from '@elytracms/studio/lib/workspace'\nimport { moduleAvailability, type AppViewModule, type ResolvedAppViews } from './module'\nimport { useAppViews } from './default-modules'\n\n/**\n * EC-237 — the configurable sidebar, an instance of the EC-236 customization seam.\n *\n * The section rail is a consumer-extendable ARRAY of nav entries — items (each\n * targeting an EC-239 app-view module, optionally deep-linked to a resource),\n * groups of sub-items, and separators. A consuming repo supplies its own array\n * through `config.sidebar`; omitted → the studio derives the default rail from its\n * module registry (every non-hidden module, in order). The supplied array is\n * resolved through the shared {@link resolveOverrides} core: a duplicate id, an\n * item whose target module isn't registered (\"unknown view\"), or a deep-link that\n * doesn't resolve becomes a visible {@link CustomizationIssue} and the entry is\n * dropped — never a silent failure. Sub-views (a focused \"Taxonomy\") are EC-239\n * modules; this file decides only how the rail PRESENTS and orders them.\n */\n\n// --- Consumer config shapes (typed builder helpers below) ---\n\n/** A nav item targeting an app-view module, optionally deep-linked to a resource. */\nexport interface SidebarItemConfig {\n kind: 'item'\n /** Stable id (defaults to `moduleId` via {@link navItem}). */\n id: string\n /** The app-view module this item mounts — must be in the registry. */\n moduleId: string\n /** Overrides the module's rail label. */\n label?: string\n /** Overrides the module's rail icon. */\n icon?: LucideIcon\n /** Trailing path segments targeting a specific resource, e.g. `['category']`. */\n deepLink?: readonly string[]\n}\n\nexport interface SidebarGroupConfig {\n kind: 'group'\n id: string\n label: string\n items: SidebarItemConfig[]\n}\n\nexport interface SidebarSeparatorConfig {\n kind: 'separator'\n id?: string\n}\n\nexport type SidebarNodeConfig = SidebarItemConfig | SidebarGroupConfig | SidebarSeparatorConfig\n\n// --- Resolved (rail-ready) shapes ---\n\nexport interface ResolvedSidebarItem {\n kind: 'item'\n id: string\n moduleId: string\n label: string\n icon: LucideIcon\n /** The `_splat` to navigate to (moduleId + deep-link segments). */\n path: string\n availability: SectionAvailability\n}\nexport interface ResolvedSidebarGroup {\n kind: 'group'\n id: string\n label: string\n items: ResolvedSidebarItem[]\n}\nexport interface ResolvedSidebarSeparator {\n kind: 'separator'\n id: string\n}\nexport type ResolvedSidebarNode =\n | ResolvedSidebarItem\n | ResolvedSidebarGroup\n | ResolvedSidebarSeparator\n\nexport interface ResolvedSidebar {\n nodes: ResolvedSidebarNode[]\n issues: CustomizationIssue[]\n}\n\n// --- Builder helpers (the typed consumer API; carry live icons) ---\n\n/** A nav item; `id` defaults to `moduleId` when omitted. */\nexport function navItem(\n config: Omit<SidebarItemConfig, 'kind' | 'id'> & { id?: string },\n): SidebarItemConfig {\n return {\n kind: 'item',\n id: config.id ?? config.moduleId,\n moduleId: config.moduleId,\n ...(config.label !== undefined ? { label: config.label } : {}),\n ...(config.icon !== undefined ? { icon: config.icon } : {}),\n ...(config.deepLink !== undefined ? { deepLink: config.deepLink } : {}),\n }\n}\n\n/** A labelled group of sub-items; `id` defaults to a slug of the label. */\nexport function navGroup(\n label: string,\n items: SidebarItemConfig[],\n opts: { id?: string } = {},\n): SidebarGroupConfig {\n return { kind: 'group', id: opts.id ?? slugify(label), label, items }\n}\n\n/** A divider between rail sections. */\nexport function navSeparator(id?: string): SidebarSeparatorConfig {\n return { kind: 'separator', ...(id !== undefined ? { id } : {}) }\n}\n\n/** Identity helper that types a consumer's `config.sidebar` array. */\nexport function defineSidebar(nodes: SidebarNodeConfig[]): SidebarNodeConfig[] {\n return nodes\n}\n\nfunction slugify(label: string): string {\n return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'group'\n}\n\n// --- Resolution (EC-236 seam) ---\n\n/** The default rail: every non-hidden module as a plain item, in registry order. */\nfunction defaultSidebar(modules: readonly AppViewModule[]): SidebarItemConfig[] {\n return modules\n .filter((module) => !module.hidden)\n .map((module) => ({ kind: 'item', id: module.id, moduleId: module.id }))\n}\n\n/** The `_splat` path for an item (module id + its deep-link segments). */\nfunction itemPath(item: SidebarItemConfig): string {\n return [item.moduleId, ...(item.deepLink ?? [])].join('/')\n}\n\n/** Validate one item against the registry — unknown module or unresolvable deep-link. */\nfunction validateItem(registry: ResolvedAppViews, item: SidebarItemConfig): string | null {\n const module = registry.get(item.moduleId)\n if (!module) return `targets module \"${item.moduleId}\", which is not registered`\n if (item.deepLink && item.deepLink.length > 0) {\n const parse = module.parsePath\n if (!parse) {\n return `deep-links into module \"${item.moduleId}\", which takes no path`\n }\n if (parse([...item.deepLink]) === null) {\n return `deep-link \"${item.deepLink.join('/')}\" does not resolve in module \"${item.moduleId}\"`\n }\n }\n return null\n}\n\n/**\n * Resolve a consumer sidebar (or the default rail) over the app-view registry\n * through the shared customization core (EC-236). Every node — items, group\n * headers, nested items, separators — is keyed by id and run through\n * {@link resolveOverrides} for dedup + per-node validation, so a duplicate id or\n * an invalid item is a visible issue and is dropped; the surviving nodes are\n * rebuilt into the rail tree (groups keep their surviving sub-items).\n */\nexport function resolveSidebar(\n registry: ResolvedAppViews,\n project: Project,\n entries?: readonly SidebarNodeConfig[],\n): ResolvedSidebar {\n const config = entries ?? defaultSidebar(registry.modules)\n\n // Flatten the tree into a single keyspace so dedup + validation route through\n // the shared core. Separators without an id get a positional one (never a user\n // collision); groups + items keep theirs. `value` carries the node for validate.\n type Flat = { node: SidebarNodeConfig; isItem: boolean }\n const flat: { id: string; value: Flat }[] = []\n config.forEach((node, index) => {\n if (node.kind === 'separator') {\n flat.push({ id: node.id ?? `__separator:${index}`, value: { node, isItem: false } })\n return\n }\n if (node.kind === 'group') {\n flat.push({ id: node.id, value: { node, isItem: false } })\n node.items.forEach((item) => flat.push({ id: item.id, value: { node: item, isItem: true } }))\n return\n }\n flat.push({ id: node.id, value: { node, isItem: true } })\n })\n\n const resolved = resolveOverrides<Flat>({\n point: 'sidebar',\n defaults: [],\n overrides: flat,\n allowNew: true,\n validate: (entry) =>\n entry.value.isItem\n ? validateItem(registry, entry.value.node as SidebarItemConfig)\n : null,\n })\n const survived = (id: string) => resolved.get(id) !== undefined\n\n const toItem = (item: SidebarItemConfig): ResolvedSidebarItem => {\n const module = registry.get(item.moduleId)!\n return {\n kind: 'item',\n id: item.id,\n moduleId: item.moduleId,\n label: item.label ?? module.label,\n icon: item.icon ?? module.icon,\n path: itemPath(item),\n availability: moduleAvailability(project, module),\n }\n }\n\n // Emit each surviving id at most once — `resolveOverrides` keeps the first of a\n // duplicate, so the rebuild must skip later occurrences of that same id too.\n const emitted = new Set<string>()\n const claim = (id: string) => {\n if (!survived(id) || emitted.has(id)) return false\n emitted.add(id)\n return true\n }\n\n const nodes: ResolvedSidebarNode[] = []\n config.forEach((node, index) => {\n if (node.kind === 'separator') {\n const id = node.id ?? `__separator:${index}`\n if (claim(id)) nodes.push({ kind: 'separator', id })\n return\n }\n if (node.kind === 'group') {\n if (!claim(node.id)) return\n const groupItems = node.items.filter((item) => claim(item.id)).map(toItem)\n if (groupItems.length > 0) {\n nodes.push({ kind: 'group', id: node.id, label: node.label, items: groupItems })\n }\n return\n }\n if (claim(node.id)) nodes.push(toItem(node))\n })\n\n return { nodes, issues: resolved.issues }\n}\n\n/** The active module id parsed from a studio pathname `/<projectId>/<module>[/…]`. */\nexport function activeModuleFromPath(pathname: string): string | null {\n return pathname.split('/')[2] || null\n}\n\n/**\n * The active project's resolved sidebar — the default rail or `config.sidebar`,\n * resolved over the module registry through the EC-236 seam (static per boot).\n */\nexport function useSidebar(project: Project): ResolvedSidebar {\n const registry = useAppViews()\n return useMemo(() => {\n // Config carries the sidebar opaquely (`SidebarEntry` = object); the studio\n // owns + validates the rich item/group/separator shape, so cast it back here.\n const entries = resolveDeploymentSidebar() as readonly SidebarNodeConfig[] | undefined\n return resolveSidebar(registry, project, entries)\n }, [registry, project])\n}\n"]}
@@ -1,13 +1,87 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { ComponentNode, ProjectGraph } from '@elytracms/core/project-graph';
3
- import type { BuilderAction, OperationError } from '@elytracms/studio/lib/operations';
4
- import type { WorkingGraph } from '@elytracms/studio/lib/working-graph';
5
- export type Viewport = 'desktop' | 'tablet' | 'mobile';
2
+ import type { ComponentNode, InsertTarget, PropValue } from '@elytracms/core/project-graph';
3
+ /**
4
+ * A canvas preview-width key (EC-250): `'full'` (unconstrained) or a configured
5
+ * breakpoint name (`'sm'`, `'md'`, …). Dynamic because the set is sourced from
6
+ * the consuming site's breakpoints, so it is a string, not a fixed union — the
7
+ * switcher resolves the available options via {@link resolveCanvasViewports}.
8
+ */
9
+ export type Viewport = string;
10
+ /**
11
+ * The builder store's action union. EC-287 retired the DB-resident project graph
12
+ * and its persistence mirror, so these actions are pure in-memory editor state —
13
+ * no action maps to a persisted `graph.*` command anymore. The block-field editor
14
+ * lifts committed edits into the CMS document via `onChange`
15
+ * (`composition-field-editor`); nothing persists a graph.
16
+ */
17
+ export type BuilderAction = {
18
+ type: 'select';
19
+ id: string | null;
20
+ } | {
21
+ type: 'hover';
22
+ id: string | null;
23
+ } | {
24
+ type: 'viewport';
25
+ viewport: Viewport;
26
+ } | {
27
+ type: 'undo';
28
+ } | {
29
+ type: 'redo';
30
+ } | {
31
+ type: 'insert';
32
+ target: InsertTarget;
33
+ node: ComponentNode;
34
+ select?: boolean;
35
+ } | {
36
+ type: 'remove';
37
+ id: string;
38
+ } | {
39
+ type: 'duplicate';
40
+ id: string;
41
+ } | {
42
+ type: 'reorder';
43
+ id: string;
44
+ delta: -1 | 1;
45
+ } | {
46
+ type: 'move';
47
+ id: string;
48
+ target: InsertTarget;
49
+ }
50
+ /**
51
+ * EC-162 layout gesture: wrap the reference node and the dragged subject in
52
+ * a two-column Grid container as ONE atomic structural edit (one undo step).
53
+ */
54
+ | {
55
+ type: 'wrap';
56
+ referenceId: string;
57
+ side: 'left' | 'right';
58
+ subject: {
59
+ kind: 'node';
60
+ nodeId: string;
61
+ } | {
62
+ kind: 'component';
63
+ node: ComponentNode;
64
+ };
65
+ } | {
66
+ type: 'setProp';
67
+ id: string;
68
+ name: string;
69
+ value: PropValue;
70
+ coalesceKey?: string;
71
+ } | {
72
+ type: 'removeProp';
73
+ id: string;
74
+ name: string;
75
+ };
6
76
  export interface BuilderState {
7
- past: WorkingGraph[];
8
- present: WorkingGraph;
9
- future: WorkingGraph[];
10
- pageId: string;
77
+ /**
78
+ * The edited composition: one root-bearing component tree (EC-287). The canvas
79
+ * wraps a page document's `body` blocks into a synthetic root and edits it
80
+ * directly — no page/layout wrapper. Undo/redo snapshot this root.
81
+ */
82
+ past: ComponentNode[];
83
+ present: ComponentNode;
84
+ future: ComponentNode[];
11
85
  selectedId: string | null;
12
86
  hoveredId: string | null;
13
87
  viewport: Viewport;
@@ -28,41 +102,19 @@ export interface BuilderStore extends BuilderState {
28
102
  canRedo: boolean;
29
103
  selectedNode: ComponentNode | null;
30
104
  dispatch: (action: Action) => void;
31
- /**
32
- * Most recent failed/denied persisted mutation (structured, from the
33
- * operation result). Rendered by the shell — never a silent failure.
34
- */
35
- persistError: OperationError | null;
36
- clearPersistError: () => void;
37
- /** Await all queued persisted mutations (publish flows, tests). */
38
- flushPersistence: () => Promise<void>;
39
105
  }
40
- export declare function initialBuilderState(graph: ProjectGraph | WorkingGraph): BuilderState;
106
+ export declare function initialBuilderState(composition: ComponentNode): BuilderState;
41
107
  /**
42
- * Undo/redo design (EC-141): the history stack stays *UI state* — an
43
- * in-memory stack of working-graph snapshots, exactly as before. The graph
44
- * domain is append-only (load-latest → edit → save-revision), so a persisted
45
- * "undo" cannot rewind the timeline; instead every committed edit is mirrored
46
- * onto the operations layer as the matching `graph.*` command, and an
47
- * undo/redo persists the restored snapshot as a *new* revision
48
- * (`graph.save`, message "Undo"/"Redo"). This is the honest mapping of a
49
- * linear session history onto an append-only revision log: the session and
50
- * the persisted latest revision always agree, and the persisted timeline
51
- * records that an undo happened rather than pretending it didn't.
52
- *
53
- * Without a `projectId` (styleguide, isolated tests) the store is a pure
54
- * in-memory editor and persists nothing.
108
+ * Undo/redo design (EC-141): the history stack is *UI state* — an in-memory
109
+ * stack of composition-root snapshots. EC-287 retired the DB-resident project
110
+ * graph and its append-only revision log, so the store no longer mirrors edits onto a
111
+ * persistence layer: it is a pure in-memory editor. The block-field editor lifts
112
+ * its committed composition into the open CMS document via `onChange`
113
+ * (`composition-field-editor`); the document's own Save persists it as content.
55
114
  */
56
- export declare function BuilderProvider({ graph, workingGraph, projectId, children, }: {
57
- /** Canonical (layouts-only) project graph; lifted into a working graph. */
58
- graph?: ProjectGraph;
59
- /**
60
- * A pre-built working graph (the composition-field editor's synthetic page);
61
- * used as-is when present, overriding `graph`.
62
- */
63
- workingGraph?: WorkingGraph;
64
- /** Enables persistence through the shared operations client. */
65
- projectId?: string;
115
+ export declare function BuilderProvider({ composition, children, }: {
116
+ /** The composition root to edit (the composition-field editor's synthetic root). */
117
+ composition: ComponentNode;
66
118
  children: ReactNode;
67
119
  }): import("react").JSX.Element;
68
120
  export declare function useBuilder(): BuilderStore;