@elytracms/studio 0.0.17 → 0.0.19

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 (43) hide show
  1. package/dist/components/app-views/default-modules.d.ts +102 -26
  2. package/dist/components/app-views/default-modules.js +166 -53
  3. package/dist/components/app-views/default-modules.js.map +1 -1
  4. package/dist/components/app-views/module.d.ts +81 -50
  5. package/dist/components/app-views/module.js +25 -43
  6. package/dist/components/app-views/module.js.map +1 -1
  7. package/dist/components/app-views/sidebar.d.ts +64 -73
  8. package/dist/components/app-views/sidebar.js +120 -125
  9. package/dist/components/app-views/sidebar.js.map +1 -1
  10. package/dist/components/app-views/studio-config.d.ts +77 -0
  11. package/dist/components/app-views/studio-config.js +31 -0
  12. package/dist/components/app-views/studio-config.js.map +1 -0
  13. package/dist/components/cms/cms-index.js +8 -8
  14. package/dist/components/cms/cms-index.js.map +1 -1
  15. package/dist/components/cms/collection-focus.d.ts +10 -0
  16. package/dist/components/cms/collection-focus.js +16 -0
  17. package/dist/components/cms/collection-focus.js.map +1 -0
  18. package/dist/components/cms/field-control.d.ts +8 -5
  19. package/dist/components/cms/field-control.js +21 -10
  20. package/dist/components/cms/field-control.js.map +1 -1
  21. package/dist/components/cms/field-renderer.d.ts +28 -30
  22. package/dist/components/cms/field-renderer.js +15 -20
  23. package/dist/components/cms/field-renderer.js.map +1 -1
  24. package/dist/components/section-nav.d.ts +8 -7
  25. package/dist/components/section-nav.js +36 -26
  26. package/dist/components/section-nav.js.map +1 -1
  27. package/dist/components/studio-project-screen.js +16 -16
  28. package/dist/components/studio-project-screen.js.map +1 -1
  29. package/dist/components/studio-shell.js +13 -13
  30. package/dist/components/studio-shell.js.map +1 -1
  31. package/dist/customization.d.ts +48 -0
  32. package/dist/customization.js +46 -0
  33. package/dist/customization.js.map +1 -0
  34. package/dist/lib/cms-views/view-model.d.ts +2 -2
  35. package/dist/lib/cms-views/views-store.d.ts +1 -1
  36. package/dist/lib/workspace/index.d.ts +32 -24
  37. package/dist/lib/workspace/index.js +35 -27
  38. package/dist/lib/workspace/index.js.map +1 -1
  39. package/dist/router/match.d.ts +16 -16
  40. package/dist/router/match.js +17 -17
  41. package/dist/router/match.js.map +1 -1
  42. package/dist/ui/badge.d.ts +1 -1
  43. package/package.json +6 -2
@@ -1,41 +1,117 @@
1
+ import type { ReactNode } from 'react';
1
2
  import type { LucideIcon } from 'lucide-react';
2
3
  import type { SectionId } from '@elytracms/core/studio-core';
3
- import { type AppViewModule, type ResolvedAppViews } from './module';
4
+ import { type AppViewContext, type AppViewDeepLink, type AppViewModule, type SidebarViewNode } from './module';
4
5
  /**
5
- * EC-239 — the studio's DEFAULT app-view module registry, plus the surfaces the
6
- * modules render. Each module here is the former `StudioSectionResolver` case body
7
- * promoted to a self-contained, config-mountable unit; a consuming repo adds or
8
- * replaces modules through `config.appViews` (resolved over this registry by
9
- * {@link resolveAppViews}). The CMS surface is exposed as a factory
10
- * ({@link cmsAppView}) so a focused view ("Taxonomy", "Pages") is the SAME module
11
- * instantiated with config, not a copy — the EC-239 reuse proof.
6
+ * EC-237/239 — the studio's BUILT-IN views + the consumer-facing `nav` namespace,
7
+ * an instance of the EC-236 customization seam.
8
+ *
9
+ * Each built-in surface (the former `StudioSectionResolver` case body) is promoted
10
+ * to a self-contained {@link AppViewModule} factory. The `nav` namespace is the
11
+ * single, typed builder a consuming repo's `studio.config.tsx` uses to declare its
12
+ * section rail — `nav.cms(...)`, `nav.group(...)`, `nav.view(...)` — passing the
13
+ * view objects (and a focus config) DIRECTLY, never a string id. The CMS surface
14
+ * is a factory ({@link cmsView}) so a focused view ("Catalog", "Categories") is the
15
+ * SAME surface instantiated with config, not a copy — the EC-239 reuse proof.
12
16
  */
13
- /** Config for a CMS-backed module instance — `collections` focuses the lens (EC-239). */
14
- export interface CmsAppViewConfig {
15
- id: string;
16
- label: string;
17
+ /** Config for a CMS-backed view — `collections`/`collection` focus the lens (EC-239). */
18
+ export interface CmsViewConfig {
19
+ /** Explicit stable id (the route segment). Omitted → derived from the focus. */
20
+ id?: string;
21
+ label?: string;
17
22
  description?: string;
18
23
  icon?: LucideIcon;
19
- /** Restrict the surface to these collection ids (a focused "Taxonomy"/"Pages" lens). Omitted → all. */
24
+ /**
25
+ * Scope the rail auto-expansion (EC-293) to these collection ids, in order. The
26
+ * parent expands into one focused-lens child per id; the parent index shows the
27
+ * same subset. Omitted → every collection. Ignored for a single-collection lens.
28
+ */
20
29
  collections?: readonly string[];
21
- /** The availability section this instance rides on (EC-221); defaults to `cms`. */
30
+ /** Single-collection lens: a LEAF rail item that opens straight into this collection. */
31
+ collection?: string;
32
+ /**
33
+ * EC-293 escape hatch: explicit rail children for this CMS parent, overriding the
34
+ * default per-collection auto-expansion. Ignored for a single-collection lens.
35
+ */
36
+ children?: SidebarViewNode[];
37
+ /** The availability section this view rides on (EC-221); defaults to `cms`. */
38
+ availabilitySection?: SectionId;
39
+ }
40
+ /** Common label/icon overrides for a built-in view (`nav.routes({ label: 'URLs' })`). */
41
+ export interface BuiltinViewConfig {
42
+ label?: string;
43
+ icon?: LucideIcon;
44
+ }
45
+ /** Config for a fully custom view (`nav.view({...})`) — the net-new surface escape hatch. */
46
+ export interface CustomViewConfig {
47
+ /** Explicit stable id (the route segment). Omitted → a slug of the label. */
48
+ id?: string;
49
+ label: string;
50
+ description?: string;
51
+ icon?: LucideIcon;
52
+ render: (ctx: AppViewContext) => ReactNode;
53
+ parsePath?: (segments: string[]) => AppViewDeepLink | null;
22
54
  availabilitySection?: SectionId;
23
55
  }
24
56
  /**
25
- * Build a CMS app-view module — the default `cms` view, or a focused instance.
26
- * Every instance shares one surface ({@link CmsScreen}); `collections` narrows the
27
- * collections sidebar + index to a subset, so "Taxonomy" is `cmsAppView({ id:
28
- * 'taxonomy', collections: ['category', 'tag'] })`, not a forked screen.
57
+ * Build a CMS view — the default `cms` view, or a focused instance. Every instance
58
+ * shares one surface ({@link CmsScreen}); `collections` narrows the sidebar + index
59
+ * to a subset, `collection` is a single-collection lens that opens straight into
60
+ * that collection's list. The id is derived from the focus (deterministic, stable
61
+ * across rail reorders) unless `id` is given — so two focused views never collide.
29
62
  */
30
- export declare function cmsAppView(config: CmsAppViewConfig): AppViewModule;
63
+ export declare function cmsView(config?: CmsViewConfig): AppViewModule;
64
+ /** The components registry browser view. */
65
+ export declare function componentsView(config?: BuiltinViewConfig): AppViewModule;
66
+ /** The resolved routes & redirects view (read-only; routing is config-as-code). */
67
+ export declare function routesView(config?: BuiltinViewConfig): AppViewModule;
68
+ /** The asset library view. */
69
+ export declare function assetsView(config?: BuiltinViewConfig): AppViewModule;
70
+ /** The extensions marketplace view. */
71
+ export declare function extensionsView(config?: BuiltinViewConfig): AppViewModule;
72
+ /** The project settings view. */
73
+ export declare function settingsView(config?: BuiltinViewConfig): AppViewModule;
74
+ /** The legacy `builder` redirect — a hidden, routable view (so `/builder?page=` still lands). */
75
+ export declare function builderView(): AppViewModule;
76
+ /** Build a fully custom view (the `nav.view(...)` escape hatch). */
77
+ export declare function customView(config: CustomViewConfig): AppViewModule;
31
78
  /**
32
- * The studio's built-in modules in canonical rail order (components · CMS · routes
33
- * · assets · extensions · settings), with the legacy `builder` redirect kept as a
34
- * hidden, routable module (so `/builder?page=` still lands on the page document).
79
+ * The studio's built-in views in canonical rail order (components · CMS · routes ·
80
+ * assets · extensions · settings), plus the hidden `builder` redirect (routable but
81
+ * unlisted). The default rail (no `defineStudio({ sidebar })`) is these as plain
82
+ * entries; the router registry always includes them so deep-links resolve even
83
+ * when a consumer's rail omits one.
35
84
  */
36
- export declare const DEFAULT_APP_VIEWS: readonly AppViewModule[];
85
+ export declare const DEFAULT_VIEWS: readonly AppViewModule[];
37
86
  /**
38
- * The active project's resolved app-view modules — the default registry composed
39
- * with `config.appViews` through the EC-236 seam (config is static per studio boot).
87
+ * The `nav` namespace — the single typed builder for `studio.config.tsx`'s section
88
+ * rail. Each call returns a {@link SidebarViewNode} (or a group / separator) that
89
+ * carries its view DIRECTLY: `nav.cms({ collection })` is a focused CMS view,
90
+ * `nav.view({ render })` a net-new surface, `nav.group(label, [...])` a header with
91
+ * sub-views, `nav.separator()` a divider. No string ids, no `moduleId` — the rail
92
+ * is the views.
40
93
  */
41
- export declare function useAppViews(): ResolvedAppViews;
94
+ export declare const nav: {
95
+ /**
96
+ * The CMS view. `nav.cms()` / `nav.cms({ collections })` is a PARENT that nests its
97
+ * collections in the rail (each a focused `cms:<id>` lens); `nav.cms({ collection })`
98
+ * is a single-collection LEAF; `nav.cms({ children })` supplies explicit children.
99
+ */
100
+ readonly cms: (config?: CmsViewConfig) => SidebarViewNode;
101
+ /** The component registry browser. */
102
+ readonly components: (config?: BuiltinViewConfig) => SidebarViewNode;
103
+ /** The asset library. */
104
+ readonly assets: (config?: BuiltinViewConfig) => SidebarViewNode;
105
+ /** The resolved routes & redirects. */
106
+ readonly routes: (config?: BuiltinViewConfig) => SidebarViewNode;
107
+ /** Project settings. */
108
+ readonly settings: (config?: BuiltinViewConfig) => SidebarViewNode;
109
+ /** The extensions marketplace. */
110
+ readonly extensions: (config?: BuiltinViewConfig) => SidebarViewNode;
111
+ /** A fully custom view — pass your own `render` (and optional `parsePath`). */
112
+ readonly view: (config: CustomViewConfig) => SidebarViewNode;
113
+ /** A labelled group of views (a header with sub-entries). */
114
+ readonly group: (label: string, children: SidebarViewNode[]) => import("./module").SidebarGroupNode;
115
+ /** A divider between rail sections. */
116
+ readonly separator: () => import("./module").SidebarSeparatorNode;
117
+ };
@@ -1,12 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo } from 'react';
3
- import { Blocks, Database, Image, Puzzle, Settings as SettingsIcon, Signpost } from 'lucide-react';
2
+ import { useEffect } from 'react';
3
+ import { Blocks, Database, FileCog, Image, LayoutGrid, Puzzle, Settings as SettingsIcon, Signpost, } from 'lucide-react';
4
4
  import { isSingleton, resolveSingletonDocument } from '@elytracms/core/cms-core';
5
5
  import { Navigate, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router';
6
6
  import { Text } from '@elytracms/studio/ui';
7
7
  import { getInstantiatedKit } from '@elytracms/studio/lib/starters/kit-session';
8
- import { resolveDeploymentAppViews } from '@elytracms/studio/lib/workspace';
9
- import { cmsDeepLink, componentsDeepLink, resolveAppViews, } from './module';
8
+ import { cmsDeepLink, componentsDeepLink, groupNode, separatorNode, viewNode, } from './module';
10
9
  import { SectionSurface } from '../section-surface';
11
10
  import { SectionStateView } from '../section-state';
12
11
  import { SettingsForm } from '../settings-form';
@@ -18,78 +17,127 @@ import { ComponentsIndex } from '../components/components-index';
18
17
  import { ComponentDetail } from '../components/component-detail';
19
18
  import { CmsSessionGate } from '../project-session';
20
19
  import { CmsProvider, useCms } from '../cms/cms-store';
21
- import { CollectionsSidebar } from '../cms/collections-sidebar';
20
+ import { focusCollectionList } from '../cms/collection-focus';
22
21
  import { CmsIndex } from '../cms/cms-index';
23
22
  import { CollectionTable } from '../cms/collection-table';
24
23
  import { DetailEditor } from '../cms/detail-editor';
25
24
  import { OperationErrorBanner } from '../operation-error';
25
+ function slugify(label) {
26
+ return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'view';
27
+ }
28
+ /** Derive a stable, deterministic id from the CMS focus (overridable via `config.id`). */
29
+ function cmsViewId(config) {
30
+ if (config.id)
31
+ return config.id;
32
+ if (config.collection)
33
+ return `cms:${config.collection}`;
34
+ if (config.collections && config.collections.length > 0) {
35
+ return `cms:${[...config.collections].join('-')}`;
36
+ }
37
+ return 'cms';
38
+ }
26
39
  /**
27
- * Build a CMS app-view module — the default `cms` view, or a focused instance.
28
- * Every instance shares one surface ({@link CmsScreen}); `collections` narrows the
29
- * collections sidebar + index to a subset, so "Taxonomy" is `cmsAppView({ id:
30
- * 'taxonomy', collections: ['category', 'tag'] })`, not a forked screen.
40
+ * Build a CMS view — the default `cms` view, or a focused instance. Every instance
41
+ * shares one surface ({@link CmsScreen}); `collections` narrows the sidebar + index
42
+ * to a subset, `collection` is a single-collection lens that opens straight into
43
+ * that collection's list. The id is derived from the focus (deterministic, stable
44
+ * across rail reorders) unless `id` is given — so two focused views never collide.
31
45
  */
32
- export function cmsAppView(config) {
46
+ export function cmsView(config = {}) {
47
+ const single = config.collection;
48
+ const focus = single ? [single] : config.collections;
33
49
  return {
34
- id: config.id,
35
- label: config.label,
50
+ id: cmsViewId(config),
51
+ label: config.label ?? 'CMS',
36
52
  description: config.description ?? 'Model collections, fields, and documents.',
37
53
  icon: config.icon ?? Database,
38
54
  availabilitySection: config.availabilitySection ?? 'cms',
39
- parsePath: cmsDeepLink,
40
- render: (ctx) => (_jsx(CmsScreen, { project: ctx.project, collectionId: ctx.deepLink.collectionId ?? null, documentId: ctx.deepLink.documentId ?? null, state: ctx.search.state, field: ctx.search.field, focusCollections: config.collections })),
55
+ // A single-collection lens routes `[]`→that collection, `[d]`→its document;
56
+ // an unfocused / multi-collection view uses the full index/collection/document arity.
57
+ parsePath: single
58
+ ? (segments) => segments.length === 0
59
+ ? { collectionId: single, documentId: null }
60
+ : segments.length === 1
61
+ ? { collectionId: single, documentId: segments[0] }
62
+ : null
63
+ : cmsDeepLink,
64
+ render: (ctx) => (_jsx(CmsScreen, { project: ctx.project, collectionId: ctx.deepLink.collectionId ?? null, documentId: ctx.deepLink.documentId ?? null, state: ctx.search.state, field: ctx.search.field, focusCollections: focus })),
41
65
  };
42
66
  }
43
67
  /**
44
- * The studio's built-in modules in canonical rail order (components · CMS · routes
45
- * · assets · extensions · settings), with the legacy `builder` redirect kept as a
46
- * hidden, routable module (so `/builder?page=` still lands on the page document).
68
+ * A focused-lens rail child for one collection (EC-293) — the SAME `cms:<id>` view a
69
+ * single-collection `nav.cms({ collection })` produces, carrying the collection's own
70
+ * label + a singleton-aware icon (the former `CollectionsSidebar` row, lifted into
71
+ * the rail). CMS auto-expansion maps the project's collections through this.
47
72
  */
48
- export const DEFAULT_APP_VIEWS = [
49
- {
73
+ function cmsCollectionLeaf(collection) {
74
+ const singleton = isSingleton(collection);
75
+ return viewNode(cmsView({
76
+ collection: collection.id,
77
+ label: collection.form?.label ?? collection.id,
78
+ icon: singleton ? FileCog : collection.kind === 'asset' ? Image : Database,
79
+ }));
80
+ }
81
+ /** The components registry browser view. */
82
+ export function componentsView(config = {}) {
83
+ return {
50
84
  id: 'components',
51
- label: 'Components',
85
+ label: config.label ?? 'Components',
52
86
  description: 'Browse the registry of primitives and project components.',
53
- icon: Blocks,
87
+ icon: config.icon ?? Blocks,
54
88
  availabilitySection: 'components',
55
89
  parsePath: componentsDeepLink,
56
90
  render: (ctx) => (_jsx(ComponentsScreen, { projectId: ctx.projectId, componentId: ctx.deepLink.componentId ?? null, state: ctx.search.state })),
57
- },
58
- cmsAppView({ id: 'cms', label: 'CMS' }),
59
- {
91
+ };
92
+ }
93
+ /** The resolved routes & redirects view (read-only; routing is config-as-code). */
94
+ export function routesView(config = {}) {
95
+ return {
60
96
  id: 'routes',
61
- label: 'Routes',
97
+ label: config.label ?? 'Routes',
62
98
  description: 'View the resolved URL routes & redirects (defined in elytra.config.ts).',
63
- icon: Signpost,
99
+ icon: config.icon ?? Signpost,
64
100
  // Routes/redirects are CMS routing concerns, so the surface rides on the CMS availability.
65
101
  availabilitySection: 'cms',
66
102
  render: (ctx) => ctx.search.state && ctx.search.state !== 'working' ? (_jsx(SectionStateView, { state: ctx.search.state, title: "Routes" })) : (_jsx(RoutesShell, { project: ctx.project })),
67
- },
68
- {
103
+ };
104
+ }
105
+ /** The asset library view. */
106
+ export function assetsView(config = {}) {
107
+ return {
69
108
  id: 'assets',
70
- label: 'Assets',
109
+ label: config.label ?? 'Assets',
71
110
  description: 'Manage uploaded media and metadata.',
72
- icon: Image,
111
+ icon: config.icon ?? Image,
73
112
  availabilitySection: 'assets',
74
113
  render: (ctx) => (_jsx(SectionSurface, { project: ctx.project, section: "assets", forcedState: ctx.search.state, children: _jsx(AssetLibrary, { projectId: ctx.projectId, focusAssetId: ctx.search.asset }) })),
75
- },
76
- {
114
+ };
115
+ }
116
+ /** The extensions marketplace view. */
117
+ export function extensionsView(config = {}) {
118
+ return {
77
119
  id: 'extensions',
78
- label: 'Extensions',
120
+ label: config.label ?? 'Extensions',
79
121
  description: 'Install components, field types, adapters, and templates from the marketplace.',
80
- icon: Puzzle,
122
+ icon: config.icon ?? Puzzle,
81
123
  availabilitySection: 'extensions',
82
124
  render: (ctx) => ctx.search.state && ctx.search.state !== 'working' ? (_jsx(SectionStateView, { state: ctx.search.state, title: "Extensions" })) : (_jsx(ExtensionsView, {})),
83
- },
84
- {
125
+ };
126
+ }
127
+ /** The project settings view. */
128
+ export function settingsView(config = {}) {
129
+ return {
85
130
  id: 'settings',
86
- label: 'Settings',
131
+ label: config.label ?? 'Settings',
87
132
  description: 'Configure project metadata and locales.',
88
- icon: SettingsIcon,
133
+ icon: config.icon ?? SettingsIcon,
89
134
  availabilitySection: 'settings',
90
135
  render: (ctx) => (_jsx(SectionSurface, { project: ctx.project, section: "settings", forcedState: ctx.search.state, children: _jsx(SettingsForm, { project: ctx.project }) })),
91
- },
92
- {
136
+ };
137
+ }
138
+ /** The legacy `builder` redirect — a hidden, routable view (so `/builder?page=` still lands). */
139
+ export function builderView() {
140
+ return {
93
141
  id: 'builder',
94
142
  label: 'Builder',
95
143
  description: 'Legacy builder route — redirects to the page document editor.',
@@ -104,16 +152,80 @@ export const DEFAULT_APP_VIEWS = [
104
152
  ? studioPath.cmsDocument('page', ctx.search.page)
105
153
  : studioPath.cmsCollection('page'),
106
154
  }, replace: true })),
107
- },
155
+ };
156
+ }
157
+ /** Build a fully custom view (the `nav.view(...)` escape hatch). */
158
+ export function customView(config) {
159
+ return {
160
+ id: config.id ?? slugify(config.label),
161
+ label: config.label,
162
+ description: config.description ?? config.label,
163
+ icon: config.icon ?? LayoutGrid,
164
+ render: config.render,
165
+ ...(config.parsePath ? { parsePath: config.parsePath } : {}),
166
+ ...(config.availabilitySection ? { availabilitySection: config.availabilitySection } : {}),
167
+ };
168
+ }
169
+ /**
170
+ * The studio's built-in views in canonical rail order (components · CMS · routes ·
171
+ * assets · extensions · settings), plus the hidden `builder` redirect (routable but
172
+ * unlisted). The default rail (no `defineStudio({ sidebar })`) is these as plain
173
+ * entries; the router registry always includes them so deep-links resolve even
174
+ * when a consumer's rail omits one.
175
+ */
176
+ export const DEFAULT_VIEWS = [
177
+ componentsView(),
178
+ cmsView(),
179
+ routesView(),
180
+ assetsView(),
181
+ extensionsView(),
182
+ settingsView(),
183
+ builderView(),
108
184
  ];
109
185
  /**
110
- * The active project's resolved app-view modules — the default registry composed
111
- * with `config.appViews` through the EC-236 seam (config is static per studio boot).
186
+ * The `nav` namespace — the single typed builder for `studio.config.tsx`'s section
187
+ * rail. Each call returns a {@link SidebarViewNode} (or a group / separator) that
188
+ * carries its view DIRECTLY: `nav.cms({ collection })` is a focused CMS view,
189
+ * `nav.view({ render })` a net-new surface, `nav.group(label, [...])` a header with
190
+ * sub-views, `nav.separator()` a divider. No string ids, no `moduleId` — the rail
191
+ * is the views.
112
192
  */
113
- export function useAppViews() {
114
- return useMemo(() => resolveAppViews(DEFAULT_APP_VIEWS, resolveDeploymentAppViews()), []);
115
- }
116
- // --- Surfaces (moved verbatim from the resolver; CMS gains a focus lens) ---
193
+ export const nav = {
194
+ /**
195
+ * The CMS view. `nav.cms()` / `nav.cms({ collections })` is a PARENT that nests its
196
+ * collections in the rail (each a focused `cms:<id>` lens); `nav.cms({ collection })`
197
+ * is a single-collection LEAF; `nav.cms({ children })` supplies explicit children.
198
+ */
199
+ cms: (config = {}) => {
200
+ // A single-collection lens is a leaf — it opens that collection directly.
201
+ if (config.collection)
202
+ return viewNode(cmsView(config));
203
+ // Otherwise a parent that expands its collections as nested rail children (EC-293).
204
+ const node = { kind: 'view', view: cmsView(config) };
205
+ if (config.children)
206
+ node.children = config.children;
207
+ else
208
+ node.expand = (collections) => focusCollectionList(collections, config.collections).map(cmsCollectionLeaf);
209
+ return node;
210
+ },
211
+ /** The component registry browser. */
212
+ components: (config = {}) => viewNode(componentsView(config)),
213
+ /** The asset library. */
214
+ assets: (config = {}) => viewNode(assetsView(config)),
215
+ /** The resolved routes & redirects. */
216
+ routes: (config = {}) => viewNode(routesView(config)),
217
+ /** Project settings. */
218
+ settings: (config = {}) => viewNode(settingsView(config)),
219
+ /** The extensions marketplace. */
220
+ extensions: (config = {}) => viewNode(extensionsView(config)),
221
+ /** A fully custom view — pass your own `render` (and optional `parsePath`). */
222
+ view: (config) => viewNode(customView(config)),
223
+ /** A labelled group of views (a header with sub-entries). */
224
+ group: (label, children) => groupNode(label, children),
225
+ /** A divider between rail sections. */
226
+ separator: () => separatorNode(),
227
+ };
228
+ // --- Surfaces (the view render bodies; CMS gains a focus lens) ---
117
229
  /**
118
230
  * Components section (EC-050): a registry-browser sidebar beside the active
119
231
  * screen — the index (no selection) or a component's detail. Forced states
@@ -126,11 +238,12 @@ function ComponentsScreen({ projectId, componentId, state, }) {
126
238
  return (_jsxs("div", { className: "flex h-full min-h-0", children: [_jsx("aside", { className: "hidden h-full min-h-0 w-[300px] shrink-0 border-r border-border md:block", children: _jsx(RegistryBrowser, { projectId: projectId, activeComponentId: componentId ?? undefined }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: componentId === null ? (_jsx(ComponentsIndex, { projectId: projectId })) : (_jsx(ComponentDetail, { projectId: projectId, componentId: componentId })) })] }));
127
239
  }
128
240
  /**
129
- * CMS section: hydrates the persisted (or kit-seeded) collections + documents,
130
- * then renders the collections sidebar beside the active screen (index, a
131
- * collection table, or a document editor). Forced states (?state=) replace the
132
- * whole surface. `focusCollections` (EC-239) narrows the sidebar + index to a
133
- * subset, so a focused module ("Taxonomy") reuses this surface with config.
241
+ * CMS section: hydrates the persisted (or kit-seeded) collections + documents, then
242
+ * renders the active screen full-width (index, a collection table, or a document
243
+ * editor). EC-293: the collection list lives in the section rail (nested under the
244
+ * CMS view), so there is no in-surface collections sidebar. Forced states (?state=)
245
+ * replace the whole surface. `focusCollections` (EC-239) narrows the index to a
246
+ * subset, so a focused view ("Catalog") reuses this surface with config.
134
247
  */
135
248
  function CmsScreen({ project, collectionId, documentId, state, field, focusCollections, }) {
136
249
  const projectId = project.id;
@@ -146,7 +259,7 @@ function CmsScreen({ project, collectionId, documentId, state, field, focusColle
146
259
  _jsx(CmsSessionGate, { children: (cms) => (_jsx(CmsProvider, { projectId: projectId, initialCollections: cms?.collections ?? kit?.collections, initialDocuments: cms?.documents ?? kit?.documents, initialPublishedKeys: cms?.publishedKeys, initialLocaleConfig: cms
147
260
  ? // Hydrated sessions follow the project's persisted locale settings.
148
261
  { default: project.settings.defaultLocale, locales: [...project.settings.locales] }
149
- : kit?.localeConfig, children: _jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(CmsPersistErrorBanner, {}), _jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsx("aside", { className: "hidden h-full min-h-0 w-[240px] shrink-0 border-r border-border md:block", children: _jsx(CollectionsSidebar, { projectId: projectId, activeCollectionId: collectionId ?? undefined, focusCollections: focusCollections }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: _jsx(CmsContent, { projectId: projectId, collectionId: collectionId, documentId: documentId, field: field, focusCollections: focusCollections }) })] })] }) }, projectId)) }));
262
+ : kit?.localeConfig, children: _jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(CmsPersistErrorBanner, {}), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: _jsx(CmsContent, { projectId: projectId, collectionId: collectionId, documentId: documentId, field: field, focusCollections: focusCollections }) })] }) }, projectId)) }));
150
263
  }
151
264
  /** Resolves the active CMS screen inside the provider (needs the live store). */
152
265
  function CmsContent({ projectId, collectionId, documentId, field, focusCollections, }) {
@@ -1 +1 @@
1
- {"version":3,"file":"default-modules.js","sourceRoot":"","sources":["../../../src/components/app-views/default-modules.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo } from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport { Blocks, Database, Image, Puzzle, Settings as SettingsIcon, Signpost } from 'lucide-react'\nimport { isSingleton, resolveSingletonDocument } from '@elytracms/core/cms-core'\nimport type { Project, SectionId, SectionState } from '@elytracms/core/studio-core'\nimport { Navigate, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { Text } from '@elytracms/studio/ui'\nimport { getInstantiatedKit } from '@elytracms/studio/lib/starters/kit-session'\nimport { resolveDeploymentAppViews } from '@elytracms/studio/lib/workspace'\nimport {\n cmsDeepLink,\n componentsDeepLink,\n resolveAppViews,\n type AppViewModule,\n type ResolvedAppViews,\n} from './module'\nimport { SectionSurface } from '../section-surface'\nimport { SectionStateView } from '../section-state'\nimport { SettingsForm } from '../settings-form'\nimport { AssetLibrary } from '../assets/asset-library'\nimport { RoutesShell } from '../routes/routes-shell'\nimport { ExtensionsView } from '../extensions/extensions-view'\nimport { RegistryBrowser } from '../components/registry-browser'\nimport { ComponentsIndex } from '../components/components-index'\nimport { ComponentDetail } from '../components/component-detail'\nimport { CmsSessionGate } from '../project-session'\nimport { CmsProvider, useCms } from '../cms/cms-store'\nimport { CollectionsSidebar } from '../cms/collections-sidebar'\nimport { CmsIndex } from '../cms/cms-index'\nimport { CollectionTable } from '../cms/collection-table'\nimport { DetailEditor } from '../cms/detail-editor'\nimport { OperationErrorBanner } from '../operation-error'\n\n/**\n * EC-239 — the studio's DEFAULT app-view module registry, plus the surfaces the\n * modules render. Each module here is the former `StudioSectionResolver` case body\n * promoted to a self-contained, config-mountable unit; a consuming repo adds or\n * replaces modules through `config.appViews` (resolved over this registry by\n * {@link resolveAppViews}). The CMS surface is exposed as a factory\n * ({@link cmsAppView}) so a focused view (\"Taxonomy\", \"Pages\") is the SAME module\n * instantiated with config, not a copy — the EC-239 reuse proof.\n */\n\n// --- The CMS surface, reusable via a config factory (the EC-239 reuse seam) ---\n\n/** Config for a CMS-backed module instance — `collections` focuses the lens (EC-239). */\nexport interface CmsAppViewConfig {\n id: string\n label: string\n description?: string\n icon?: LucideIcon\n /** Restrict the surface to these collection ids (a focused \"Taxonomy\"/\"Pages\" lens). Omitted → all. */\n collections?: readonly string[]\n /** The availability section this instance rides on (EC-221); defaults to `cms`. */\n availabilitySection?: SectionId\n}\n\n/**\n * Build a CMS app-view module — the default `cms` view, or a focused instance.\n * Every instance shares one surface ({@link CmsScreen}); `collections` narrows the\n * collections sidebar + index to a subset, so \"Taxonomy\" is `cmsAppView({ id:\n * 'taxonomy', collections: ['category', 'tag'] })`, not a forked screen.\n */\nexport function cmsAppView(config: CmsAppViewConfig): AppViewModule {\n return {\n id: config.id,\n label: config.label,\n description: config.description ?? 'Model collections, fields, and documents.',\n icon: config.icon ?? Database,\n availabilitySection: config.availabilitySection ?? 'cms',\n parsePath: cmsDeepLink,\n render: (ctx) => (\n <CmsScreen\n project={ctx.project}\n collectionId={ctx.deepLink.collectionId ?? null}\n documentId={ctx.deepLink.documentId ?? null}\n state={ctx.search.state}\n field={ctx.search.field}\n focusCollections={config.collections}\n />\n ),\n }\n}\n\n/**\n * The studio's built-in modules in canonical rail order (components · CMS · routes\n * · assets · extensions · settings), with the legacy `builder` redirect kept as a\n * hidden, routable module (so `/builder?page=` still lands on the page document).\n */\nexport const DEFAULT_APP_VIEWS: readonly AppViewModule[] = [\n {\n id: 'components',\n label: 'Components',\n description: 'Browse the registry of primitives and project components.',\n icon: Blocks,\n availabilitySection: 'components',\n parsePath: componentsDeepLink,\n render: (ctx) => (\n <ComponentsScreen\n projectId={ctx.projectId}\n componentId={ctx.deepLink.componentId ?? null}\n state={ctx.search.state}\n />\n ),\n },\n cmsAppView({ id: 'cms', label: 'CMS' }),\n {\n id: 'routes',\n label: 'Routes',\n description: 'View the resolved URL routes & redirects (defined in elytra.config.ts).',\n icon: Signpost,\n // Routes/redirects are CMS routing concerns, so the surface rides on the CMS availability.\n availabilitySection: 'cms',\n render: (ctx) =>\n ctx.search.state && ctx.search.state !== 'working' ? (\n <SectionStateView state={ctx.search.state} title=\"Routes\" />\n ) : (\n <RoutesShell project={ctx.project} />\n ),\n },\n {\n id: 'assets',\n label: 'Assets',\n description: 'Manage uploaded media and metadata.',\n icon: Image,\n availabilitySection: 'assets',\n render: (ctx) => (\n <SectionSurface project={ctx.project} section=\"assets\" forcedState={ctx.search.state}>\n {/* The AssetsProvider is mounted at the project level (EC-195). */}\n <AssetLibrary projectId={ctx.projectId} focusAssetId={ctx.search.asset} />\n </SectionSurface>\n ),\n },\n {\n id: 'extensions',\n label: 'Extensions',\n description: 'Install components, field types, adapters, and templates from the marketplace.',\n icon: Puzzle,\n availabilitySection: 'extensions',\n render: (ctx) =>\n ctx.search.state && ctx.search.state !== 'working' ? (\n <SectionStateView state={ctx.search.state} title=\"Extensions\" />\n ) : (\n <ExtensionsView />\n ),\n },\n {\n id: 'settings',\n label: 'Settings',\n description: 'Configure project metadata and locales.',\n icon: SettingsIcon,\n availabilitySection: 'settings',\n render: (ctx) => (\n <SectionSurface project={ctx.project} section=\"settings\" forcedState={ctx.search.state}>\n <SettingsForm project={ctx.project} />\n </SectionSurface>\n ),\n },\n {\n id: 'builder',\n label: 'Builder',\n description: 'Legacy builder route — redirects to the page document editor.',\n icon: Blocks,\n hidden: true,\n render: (ctx) => (\n // Retired surface (EC-187 slice 6): a page is a document in the `page`\n // collection. `?page=<id>` lands on that document; else the page list.\n <Navigate\n to={STUDIO_PROJECT_ROUTE}\n params={{\n projectId: ctx.projectId,\n _splat: ctx.search.page\n ? studioPath.cmsDocument('page', ctx.search.page)\n : studioPath.cmsCollection('page'),\n }}\n replace\n />\n ),\n },\n]\n\n/**\n * The active project's resolved app-view modules — the default registry composed\n * with `config.appViews` through the EC-236 seam (config is static per studio boot).\n */\nexport function useAppViews(): ResolvedAppViews {\n return useMemo(() => resolveAppViews(DEFAULT_APP_VIEWS, resolveDeploymentAppViews()), [])\n}\n\n// --- Surfaces (moved verbatim from the resolver; CMS gains a focus lens) ---\n\n/**\n * Components section (EC-050): a registry-browser sidebar beside the active\n * screen — the index (no selection) or a component's detail. Forced states\n * (?state=) replace the whole surface.\n */\nfunction ComponentsScreen({\n projectId,\n componentId,\n state,\n}: {\n projectId: string\n componentId: string | null\n state?: SectionState\n}) {\n if (state && state !== 'working') {\n return <SectionStateView state={state} title=\"Components\" />\n }\n return (\n <div className=\"flex h-full min-h-0\">\n <aside className=\"hidden h-full min-h-0 w-[300px] shrink-0 border-r border-border md:block\">\n <RegistryBrowser projectId={projectId} activeComponentId={componentId ?? undefined} />\n </aside>\n <div className=\"min-h-0 flex-1 overflow-auto\">\n {componentId === null ? (\n <ComponentsIndex projectId={projectId} />\n ) : (\n <ComponentDetail projectId={projectId} componentId={componentId} />\n )}\n </div>\n </div>\n )\n}\n\n/**\n * CMS section: hydrates the persisted (or kit-seeded) collections + documents,\n * then renders the collections sidebar beside the active screen (index, a\n * collection table, or a document editor). Forced states (?state=) replace the\n * whole surface. `focusCollections` (EC-239) narrows the sidebar + index to a\n * subset, so a focused module (\"Taxonomy\") reuses this surface with config.\n */\nfunction CmsScreen({\n project,\n collectionId,\n documentId,\n state,\n field,\n focusCollections,\n}: {\n project: Project\n collectionId: string | null\n documentId: string | null\n state?: SectionState\n field?: string\n focusCollections?: readonly string[]\n}) {\n const projectId = project.id\n if (state && state !== 'working') {\n return <SectionStateView state={state} title=\"CMS\" />\n }\n\n // A project created from a starter kit (EC-119) opens the CMS on the kit's schemas + content.\n const kit = getInstantiatedKit(projectId)\n\n return (\n // EC-177 gap 1: in Convex mode every CMS mount loads the CURRENT persisted\n // schema + documents (fixtures only when nothing is persisted — first run).\n // Playground renders synchronously with `null` → fixture seeds, as before.\n <CmsSessionGate>\n {(cms) => (\n <CmsProvider\n key={projectId}\n projectId={projectId}\n initialCollections={cms?.collections ?? kit?.collections}\n initialDocuments={cms?.documents ?? kit?.documents}\n initialPublishedKeys={cms?.publishedKeys}\n initialLocaleConfig={\n cms\n ? // Hydrated sessions follow the project's persisted locale settings.\n { default: project.settings.defaultLocale, locales: [...project.settings.locales] }\n : kit?.localeConfig\n }\n >\n <div className=\"flex h-full min-h-0 flex-col\">\n <CmsPersistErrorBanner />\n <div className=\"flex min-h-0 flex-1\">\n <aside className=\"hidden h-full min-h-0 w-[240px] shrink-0 border-r border-border md:block\">\n <CollectionsSidebar\n projectId={projectId}\n activeCollectionId={collectionId ?? undefined}\n focusCollections={focusCollections}\n />\n </aside>\n <div className=\"min-h-0 flex-1 overflow-hidden\">\n <CmsContent\n projectId={projectId}\n collectionId={collectionId}\n documentId={documentId}\n field={field}\n focusCollections={focusCollections}\n />\n </div>\n </div>\n </div>\n </CmsProvider>\n )}\n </CmsSessionGate>\n )\n}\n\n/** Resolves the active CMS screen inside the provider (needs the live store). */\nfunction CmsContent({\n projectId,\n collectionId,\n documentId,\n field,\n focusCollections,\n}: {\n projectId: string\n collectionId: string | null\n documentId: string | null\n field?: string\n focusCollections?: readonly string[]\n}) {\n const cms = useCms()\n\n if (collectionId === null) return <CmsIndex projectId={projectId} focusCollections={focusCollections} />\n\n if (documentId === null) {\n const collection = cms.getCollection(collectionId)\n // EC-274: a singleton is a UI lens — at the collection URL it opens its one\n // document directly *while in its expected shape*, but degrades to the list\n // (with a \"for review\" lane) on drift. SingletonGate decides per the data.\n if (collection && isSingleton(collection)) {\n return <SingletonGate projectId={projectId} collectionId={collectionId} />\n }\n return <CollectionTable projectId={projectId} collectionId={collectionId} />\n }\n\n return (\n <DetailEditor\n projectId={projectId}\n collectionId={collectionId}\n documentId={documentId}\n focusPath={field}\n />\n )\n}\n\n/**\n * EC-274 — the singleton lens gate. At a singleton collection's URL, resolve its\n * lone document and open it directly; on drift (> 1 document) fall back to the\n * list with the \"for review\" lane so an editor can resolve the conflict.\n */\nfunction SingletonGate({ projectId, collectionId }: { projectId: string; collectionId: string }) {\n const cms = useCms()\n const collection = cms.getCollection(collectionId)\n const docs = cms.documentsOf(collectionId)\n const resolution = collection\n ? resolveSingletonDocument(collection, docs)\n : ({ kind: 'empty' } as const)\n\n // Materialize the lone document on first visit (an empty draft, persisted like\n // any create). Idempotent in the store; only fires while genuinely empty.\n useEffect(() => {\n if (resolution.kind === 'empty') cms.ensureSingletonDocument(collectionId)\n }, [resolution.kind, cms, collectionId])\n\n if (resolution.kind === 'ready') {\n return (\n <DetailEditor projectId={projectId} collectionId={collectionId} documentId={resolution.doc.id} />\n )\n }\n if (resolution.kind === 'drift') {\n return <CollectionTable projectId={projectId} collectionId={collectionId} />\n }\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Text size=\"sm\" tone=\"muted\">\n Opening {collection?.form?.label ?? collectionId}…\n </Text>\n </div>\n )\n}\n\n/** Failed/denied persisted CMS mutations are always visible (EC-141). */\nfunction CmsPersistErrorBanner() {\n const cms = useCms()\n if (!cms.persistError) return null\n return (\n <div className=\"shrink-0 border-b border-border p-2\">\n <OperationErrorBanner error={cms.persistError} onDismiss={cms.clearPersistError} />\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"default-modules.js","sourceRoot":"","sources":["../../../src/components/app-views/default-modules.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect } from 'react'\nimport type { ReactNode } from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport {\n Blocks,\n Database,\n FileCog,\n Image,\n LayoutGrid,\n Puzzle,\n Settings as SettingsIcon,\n Signpost,\n} from 'lucide-react'\nimport { isSingleton, resolveSingletonDocument } from '@elytracms/core/cms-core'\nimport type { CollectionDef } from '@elytracms/core/cms-core'\nimport type { Project, SectionId, SectionState } from '@elytracms/core/studio-core'\nimport { Navigate, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { Text } from '@elytracms/studio/ui'\nimport { getInstantiatedKit } from '@elytracms/studio/lib/starters/kit-session'\nimport {\n cmsDeepLink,\n componentsDeepLink,\n groupNode,\n separatorNode,\n viewNode,\n type AppViewContext,\n type AppViewDeepLink,\n type AppViewModule,\n type SidebarViewNode,\n} from './module'\nimport { SectionSurface } from '../section-surface'\nimport { SectionStateView } from '../section-state'\nimport { SettingsForm } from '../settings-form'\nimport { AssetLibrary } from '../assets/asset-library'\nimport { RoutesShell } from '../routes/routes-shell'\nimport { ExtensionsView } from '../extensions/extensions-view'\nimport { RegistryBrowser } from '../components/registry-browser'\nimport { ComponentsIndex } from '../components/components-index'\nimport { ComponentDetail } from '../components/component-detail'\nimport { CmsSessionGate } from '../project-session'\nimport { CmsProvider, useCms } from '../cms/cms-store'\nimport { focusCollectionList } from '../cms/collection-focus'\nimport { CmsIndex } from '../cms/cms-index'\nimport { CollectionTable } from '../cms/collection-table'\nimport { DetailEditor } from '../cms/detail-editor'\nimport { OperationErrorBanner } from '../operation-error'\n\n/**\n * EC-237/239 — the studio's BUILT-IN views + the consumer-facing `nav` namespace,\n * an instance of the EC-236 customization seam.\n *\n * Each built-in surface (the former `StudioSectionResolver` case body) is promoted\n * to a self-contained {@link AppViewModule} factory. The `nav` namespace is the\n * single, typed builder a consuming repo's `studio.config.tsx` uses to declare its\n * section rail — `nav.cms(...)`, `nav.group(...)`, `nav.view(...)` — passing the\n * view objects (and a focus config) DIRECTLY, never a string id. The CMS surface\n * is a factory ({@link cmsView}) so a focused view (\"Catalog\", \"Categories\") is the\n * SAME surface instantiated with config, not a copy — the EC-239 reuse proof.\n */\n\n// --- The CMS surface, reusable via a config factory (the EC-239 reuse seam) ---\n\n/** Config for a CMS-backed view — `collections`/`collection` focus the lens (EC-239). */\nexport interface CmsViewConfig {\n /** Explicit stable id (the route segment). Omitted → derived from the focus. */\n id?: string\n label?: string\n description?: string\n icon?: LucideIcon\n /**\n * Scope the rail auto-expansion (EC-293) to these collection ids, in order. The\n * parent expands into one focused-lens child per id; the parent index shows the\n * same subset. Omitted → every collection. Ignored for a single-collection lens.\n */\n collections?: readonly string[]\n /** Single-collection lens: a LEAF rail item that opens straight into this collection. */\n collection?: string\n /**\n * EC-293 escape hatch: explicit rail children for this CMS parent, overriding the\n * default per-collection auto-expansion. Ignored for a single-collection lens.\n */\n children?: SidebarViewNode[]\n /** The availability section this view rides on (EC-221); defaults to `cms`. */\n availabilitySection?: SectionId\n}\n\n/** Common label/icon overrides for a built-in view (`nav.routes({ label: 'URLs' })`). */\nexport interface BuiltinViewConfig {\n label?: string\n icon?: LucideIcon\n}\n\n/** Config for a fully custom view (`nav.view({...})`) — the net-new surface escape hatch. */\nexport interface CustomViewConfig {\n /** Explicit stable id (the route segment). Omitted → a slug of the label. */\n id?: string\n label: string\n description?: string\n icon?: LucideIcon\n render: (ctx: AppViewContext) => ReactNode\n parsePath?: (segments: string[]) => AppViewDeepLink | null\n availabilitySection?: SectionId\n}\n\nfunction slugify(label: string): string {\n return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'view'\n}\n\n/** Derive a stable, deterministic id from the CMS focus (overridable via `config.id`). */\nfunction cmsViewId(config: CmsViewConfig): string {\n if (config.id) return config.id\n if (config.collection) return `cms:${config.collection}`\n if (config.collections && config.collections.length > 0) {\n return `cms:${[...config.collections].join('-')}`\n }\n return 'cms'\n}\n\n/**\n * Build a CMS view — the default `cms` view, or a focused instance. Every instance\n * shares one surface ({@link CmsScreen}); `collections` narrows the sidebar + index\n * to a subset, `collection` is a single-collection lens that opens straight into\n * that collection's list. The id is derived from the focus (deterministic, stable\n * across rail reorders) unless `id` is given — so two focused views never collide.\n */\nexport function cmsView(config: CmsViewConfig = {}): AppViewModule {\n const single = config.collection\n const focus = single ? [single] : config.collections\n return {\n id: cmsViewId(config),\n label: config.label ?? 'CMS',\n description: config.description ?? 'Model collections, fields, and documents.',\n icon: config.icon ?? Database,\n availabilitySection: config.availabilitySection ?? 'cms',\n // A single-collection lens routes `[]`→that collection, `[d]`→its document;\n // an unfocused / multi-collection view uses the full index/collection/document arity.\n parsePath: single\n ? (segments) =>\n segments.length === 0\n ? { collectionId: single, documentId: null }\n : segments.length === 1\n ? { collectionId: single, documentId: segments[0]! }\n : null\n : cmsDeepLink,\n render: (ctx) => (\n <CmsScreen\n project={ctx.project}\n collectionId={ctx.deepLink.collectionId ?? null}\n documentId={ctx.deepLink.documentId ?? null}\n state={ctx.search.state}\n field={ctx.search.field}\n focusCollections={focus}\n />\n ),\n }\n}\n\n/**\n * A focused-lens rail child for one collection (EC-293) — the SAME `cms:<id>` view a\n * single-collection `nav.cms({ collection })` produces, carrying the collection's own\n * label + a singleton-aware icon (the former `CollectionsSidebar` row, lifted into\n * the rail). CMS auto-expansion maps the project's collections through this.\n */\nfunction cmsCollectionLeaf(collection: CollectionDef): SidebarViewNode {\n const singleton = isSingleton(collection)\n return viewNode(\n cmsView({\n collection: collection.id,\n label: collection.form?.label ?? collection.id,\n icon: singleton ? FileCog : collection.kind === 'asset' ? Image : Database,\n }),\n )\n}\n\n/** The components registry browser view. */\nexport function componentsView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'components',\n label: config.label ?? 'Components',\n description: 'Browse the registry of primitives and project components.',\n icon: config.icon ?? Blocks,\n availabilitySection: 'components',\n parsePath: componentsDeepLink,\n render: (ctx) => (\n <ComponentsScreen\n projectId={ctx.projectId}\n componentId={ctx.deepLink.componentId ?? null}\n state={ctx.search.state}\n />\n ),\n }\n}\n\n/** The resolved routes & redirects view (read-only; routing is config-as-code). */\nexport function routesView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'routes',\n label: config.label ?? 'Routes',\n description: 'View the resolved URL routes & redirects (defined in elytra.config.ts).',\n icon: config.icon ?? Signpost,\n // Routes/redirects are CMS routing concerns, so the surface rides on the CMS availability.\n availabilitySection: 'cms',\n render: (ctx) =>\n ctx.search.state && ctx.search.state !== 'working' ? (\n <SectionStateView state={ctx.search.state} title=\"Routes\" />\n ) : (\n <RoutesShell project={ctx.project} />\n ),\n }\n}\n\n/** The asset library view. */\nexport function assetsView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'assets',\n label: config.label ?? 'Assets',\n description: 'Manage uploaded media and metadata.',\n icon: config.icon ?? Image,\n availabilitySection: 'assets',\n render: (ctx) => (\n <SectionSurface project={ctx.project} section=\"assets\" forcedState={ctx.search.state}>\n {/* The AssetsProvider is mounted at the project level (EC-195). */}\n <AssetLibrary projectId={ctx.projectId} focusAssetId={ctx.search.asset} />\n </SectionSurface>\n ),\n }\n}\n\n/** The extensions marketplace view. */\nexport function extensionsView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'extensions',\n label: config.label ?? 'Extensions',\n description: 'Install components, field types, adapters, and templates from the marketplace.',\n icon: config.icon ?? Puzzle,\n availabilitySection: 'extensions',\n render: (ctx) =>\n ctx.search.state && ctx.search.state !== 'working' ? (\n <SectionStateView state={ctx.search.state} title=\"Extensions\" />\n ) : (\n <ExtensionsView />\n ),\n }\n}\n\n/** The project settings view. */\nexport function settingsView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'settings',\n label: config.label ?? 'Settings',\n description: 'Configure project metadata and locales.',\n icon: config.icon ?? SettingsIcon,\n availabilitySection: 'settings',\n render: (ctx) => (\n <SectionSurface project={ctx.project} section=\"settings\" forcedState={ctx.search.state}>\n <SettingsForm project={ctx.project} />\n </SectionSurface>\n ),\n }\n}\n\n/** The legacy `builder` redirect — a hidden, routable view (so `/builder?page=` still lands). */\nexport function builderView(): AppViewModule {\n return {\n id: 'builder',\n label: 'Builder',\n description: 'Legacy builder route — redirects to the page document editor.',\n icon: Blocks,\n hidden: true,\n render: (ctx) => (\n // Retired surface (EC-187 slice 6): a page is a document in the `page`\n // collection. `?page=<id>` lands on that document; else the page list.\n <Navigate\n to={STUDIO_PROJECT_ROUTE}\n params={{\n projectId: ctx.projectId,\n _splat: ctx.search.page\n ? studioPath.cmsDocument('page', ctx.search.page)\n : studioPath.cmsCollection('page'),\n }}\n replace\n />\n ),\n }\n}\n\n/** Build a fully custom view (the `nav.view(...)` escape hatch). */\nexport function customView(config: CustomViewConfig): AppViewModule {\n return {\n id: config.id ?? slugify(config.label),\n label: config.label,\n description: config.description ?? config.label,\n icon: config.icon ?? LayoutGrid,\n render: config.render,\n ...(config.parsePath ? { parsePath: config.parsePath } : {}),\n ...(config.availabilitySection ? { availabilitySection: config.availabilitySection } : {}),\n }\n}\n\n/**\n * The studio's built-in views in canonical rail order (components · CMS · routes ·\n * assets · extensions · settings), plus the hidden `builder` redirect (routable but\n * unlisted). The default rail (no `defineStudio({ sidebar })`) is these as plain\n * entries; the router registry always includes them so deep-links resolve even\n * when a consumer's rail omits one.\n */\nexport const DEFAULT_VIEWS: readonly AppViewModule[] = [\n componentsView(),\n cmsView(),\n routesView(),\n assetsView(),\n extensionsView(),\n settingsView(),\n builderView(),\n]\n\n/**\n * The `nav` namespace — the single typed builder for `studio.config.tsx`'s section\n * rail. Each call returns a {@link SidebarViewNode} (or a group / separator) that\n * carries its view DIRECTLY: `nav.cms({ collection })` is a focused CMS view,\n * `nav.view({ render })` a net-new surface, `nav.group(label, [...])` a header with\n * sub-views, `nav.separator()` a divider. No string ids, no `moduleId` — the rail\n * is the views.\n */\nexport const nav = {\n /**\n * The CMS view. `nav.cms()` / `nav.cms({ collections })` is a PARENT that nests its\n * collections in the rail (each a focused `cms:<id>` lens); `nav.cms({ collection })`\n * is a single-collection LEAF; `nav.cms({ children })` supplies explicit children.\n */\n cms: (config: CmsViewConfig = {}): SidebarViewNode => {\n // A single-collection lens is a leaf — it opens that collection directly.\n if (config.collection) return viewNode(cmsView(config))\n // Otherwise a parent that expands its collections as nested rail children (EC-293).\n const node: SidebarViewNode = { kind: 'view', view: cmsView(config) }\n if (config.children) node.children = config.children\n else\n node.expand = (collections) =>\n focusCollectionList(collections, config.collections).map(cmsCollectionLeaf)\n return node\n },\n /** The component registry browser. */\n components: (config: BuiltinViewConfig = {}): SidebarViewNode => viewNode(componentsView(config)),\n /** The asset library. */\n assets: (config: BuiltinViewConfig = {}): SidebarViewNode => viewNode(assetsView(config)),\n /** The resolved routes & redirects. */\n routes: (config: BuiltinViewConfig = {}): SidebarViewNode => viewNode(routesView(config)),\n /** Project settings. */\n settings: (config: BuiltinViewConfig = {}): SidebarViewNode => viewNode(settingsView(config)),\n /** The extensions marketplace. */\n extensions: (config: BuiltinViewConfig = {}): SidebarViewNode => viewNode(extensionsView(config)),\n /** A fully custom view — pass your own `render` (and optional `parsePath`). */\n view: (config: CustomViewConfig): SidebarViewNode => viewNode(customView(config)),\n /** A labelled group of views (a header with sub-entries). */\n group: (label: string, children: SidebarViewNode[]) => groupNode(label, children),\n /** A divider between rail sections. */\n separator: () => separatorNode(),\n} as const\n\n// --- Surfaces (the view render bodies; CMS gains a focus lens) ---\n\n/**\n * Components section (EC-050): a registry-browser sidebar beside the active\n * screen — the index (no selection) or a component's detail. Forced states\n * (?state=) replace the whole surface.\n */\nfunction ComponentsScreen({\n projectId,\n componentId,\n state,\n}: {\n projectId: string\n componentId: string | null\n state?: SectionState\n}) {\n if (state && state !== 'working') {\n return <SectionStateView state={state} title=\"Components\" />\n }\n return (\n <div className=\"flex h-full min-h-0\">\n <aside className=\"hidden h-full min-h-0 w-[300px] shrink-0 border-r border-border md:block\">\n <RegistryBrowser projectId={projectId} activeComponentId={componentId ?? undefined} />\n </aside>\n <div className=\"min-h-0 flex-1 overflow-auto\">\n {componentId === null ? (\n <ComponentsIndex projectId={projectId} />\n ) : (\n <ComponentDetail projectId={projectId} componentId={componentId} />\n )}\n </div>\n </div>\n )\n}\n\n/**\n * CMS section: hydrates the persisted (or kit-seeded) collections + documents, then\n * renders the active screen full-width (index, a collection table, or a document\n * editor). EC-293: the collection list lives in the section rail (nested under the\n * CMS view), so there is no in-surface collections sidebar. Forced states (?state=)\n * replace the whole surface. `focusCollections` (EC-239) narrows the index to a\n * subset, so a focused view (\"Catalog\") reuses this surface with config.\n */\nfunction CmsScreen({\n project,\n collectionId,\n documentId,\n state,\n field,\n focusCollections,\n}: {\n project: Project\n collectionId: string | null\n documentId: string | null\n state?: SectionState\n field?: string\n focusCollections?: readonly string[]\n}) {\n const projectId = project.id\n if (state && state !== 'working') {\n return <SectionStateView state={state} title=\"CMS\" />\n }\n\n // A project created from a starter kit (EC-119) opens the CMS on the kit's schemas + content.\n const kit = getInstantiatedKit(projectId)\n\n return (\n // EC-177 gap 1: in Convex mode every CMS mount loads the CURRENT persisted\n // schema + documents (fixtures only when nothing is persisted — first run).\n // Playground renders synchronously with `null` → fixture seeds, as before.\n <CmsSessionGate>\n {(cms) => (\n <CmsProvider\n key={projectId}\n projectId={projectId}\n initialCollections={cms?.collections ?? kit?.collections}\n initialDocuments={cms?.documents ?? kit?.documents}\n initialPublishedKeys={cms?.publishedKeys}\n initialLocaleConfig={\n cms\n ? // Hydrated sessions follow the project's persisted locale settings.\n { default: project.settings.defaultLocale, locales: [...project.settings.locales] }\n : kit?.localeConfig\n }\n >\n <div className=\"flex h-full min-h-0 flex-col\">\n <CmsPersistErrorBanner />\n {/* EC-293: the collection list now lives in the section rail (nested\n under the CMS view), so the surface is full-width — the old 240px\n CollectionsSidebar aside is gone. */}\n <div className=\"min-h-0 flex-1 overflow-hidden\">\n <CmsContent\n projectId={projectId}\n collectionId={collectionId}\n documentId={documentId}\n field={field}\n focusCollections={focusCollections}\n />\n </div>\n </div>\n </CmsProvider>\n )}\n </CmsSessionGate>\n )\n}\n\n/** Resolves the active CMS screen inside the provider (needs the live store). */\nfunction CmsContent({\n projectId,\n collectionId,\n documentId,\n field,\n focusCollections,\n}: {\n projectId: string\n collectionId: string | null\n documentId: string | null\n field?: string\n focusCollections?: readonly string[]\n}) {\n const cms = useCms()\n\n if (collectionId === null) return <CmsIndex projectId={projectId} focusCollections={focusCollections} />\n\n if (documentId === null) {\n const collection = cms.getCollection(collectionId)\n // EC-274: a singleton is a UI lens — at the collection URL it opens its one\n // document directly *while in its expected shape*, but degrades to the list\n // (with a \"for review\" lane) on drift. SingletonGate decides per the data.\n if (collection && isSingleton(collection)) {\n return <SingletonGate projectId={projectId} collectionId={collectionId} />\n }\n return <CollectionTable projectId={projectId} collectionId={collectionId} />\n }\n\n return (\n <DetailEditor\n projectId={projectId}\n collectionId={collectionId}\n documentId={documentId}\n focusPath={field}\n />\n )\n}\n\n/**\n * EC-274 — the singleton lens gate. At a singleton collection's URL, resolve its\n * lone document and open it directly; on drift (> 1 document) fall back to the\n * list with the \"for review\" lane so an editor can resolve the conflict.\n */\nfunction SingletonGate({ projectId, collectionId }: { projectId: string; collectionId: string }) {\n const cms = useCms()\n const collection = cms.getCollection(collectionId)\n const docs = cms.documentsOf(collectionId)\n const resolution = collection\n ? resolveSingletonDocument(collection, docs)\n : ({ kind: 'empty' } as const)\n\n // Materialize the lone document on first visit (an empty draft, persisted like\n // any create). Idempotent in the store; only fires while genuinely empty.\n useEffect(() => {\n if (resolution.kind === 'empty') cms.ensureSingletonDocument(collectionId)\n }, [resolution.kind, cms, collectionId])\n\n if (resolution.kind === 'ready') {\n return (\n <DetailEditor projectId={projectId} collectionId={collectionId} documentId={resolution.doc.id} />\n )\n }\n if (resolution.kind === 'drift') {\n return <CollectionTable projectId={projectId} collectionId={collectionId} />\n }\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Text size=\"sm\" tone=\"muted\">\n Opening {collection?.form?.label ?? collectionId}…\n </Text>\n </div>\n )\n}\n\n/** Failed/denied persisted CMS mutations are always visible (EC-141). */\nfunction CmsPersistErrorBanner() {\n const cms = useCms()\n if (!cms.persistError) return null\n return (\n <div className=\"shrink-0 border-b border-border p-2\">\n <OperationErrorBanner error={cms.persistError} onDismiss={cms.clearPersistError} />\n </div>\n )\n}\n"]}