@elytracms/studio 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/convex/cms.ts +8 -31
  2. package/convex/content.ts +3 -44
  3. package/convex/delivery.ts +8 -42
  4. package/convex/functions.ts +17 -2
  5. package/convex/members.ts +2 -2
  6. package/convex/referenceMaintenance.ts +4 -73
  7. package/convex/references.ts +4 -2
  8. package/convex/schema.ts +17 -37
  9. package/convex/sync.ts +10 -42
  10. package/convex/triggers.ts +57 -0
  11. package/convex/webhooks.ts +3 -2
  12. package/dist/components/app-views/default-modules.d.ts +41 -0
  13. package/dist/components/app-views/default-modules.js +201 -0
  14. package/dist/components/app-views/default-modules.js.map +1 -0
  15. package/dist/components/app-views/module.d.ts +93 -0
  16. package/dist/components/app-views/module.js +75 -0
  17. package/dist/components/app-views/module.js.map +1 -0
  18. package/dist/components/app-views/sidebar.d.ts +95 -0
  19. package/dist/components/app-views/sidebar.js +153 -0
  20. package/dist/components/app-views/sidebar.js.map +1 -0
  21. package/dist/components/builder/builder-store.d.ts +92 -40
  22. package/dist/components/builder/builder-store.js +30 -126
  23. package/dist/components/builder/builder-store.js.map +1 -1
  24. package/dist/components/builder/canvas-breakpoints.d.ts +53 -0
  25. package/dist/components/builder/canvas-breakpoints.js +55 -0
  26. package/dist/components/builder/canvas-breakpoints.js.map +1 -0
  27. package/dist/components/builder/canvas.js +35 -61
  28. package/dist/components/builder/canvas.js.map +1 -1
  29. package/dist/components/builder/clipboard-actions.js +3 -3
  30. package/dist/components/builder/clipboard-actions.js.map +1 -1
  31. package/dist/components/builder/composition-field-editor.js +31 -40
  32. package/dist/components/builder/composition-field-editor.js.map +1 -1
  33. package/dist/components/builder/insert-context.js +1 -1
  34. package/dist/components/builder/insert-context.js.map +1 -1
  35. package/dist/components/builder/page-tree.js +3 -4
  36. package/dist/components/builder/page-tree.js.map +1 -1
  37. package/dist/components/builder/property-inspector.js +4 -64
  38. package/dist/components/builder/property-inspector.js.map +1 -1
  39. package/dist/components/cms/bulk-actions.d.ts +23 -5
  40. package/dist/components/cms/bulk-actions.js +7 -3
  41. package/dist/components/cms/bulk-actions.js.map +1 -1
  42. package/dist/components/cms/bulk-bar.d.ts +22 -6
  43. package/dist/components/cms/bulk-bar.js +46 -9
  44. package/dist/components/cms/bulk-bar.js.map +1 -1
  45. package/dist/components/cms/cell.d.ts +0 -4
  46. package/dist/components/cms/cell.js +1 -5
  47. package/dist/components/cms/cell.js.map +1 -1
  48. package/dist/components/cms/cms-index.d.ts +4 -2
  49. package/dist/components/cms/cms-index.js +6 -4
  50. package/dist/components/cms/cms-index.js.map +1 -1
  51. package/dist/components/cms/cms-store.d.ts +7 -6
  52. package/dist/components/cms/cms-store.js +55 -18
  53. package/dist/components/cms/cms-store.js.map +1 -1
  54. package/dist/components/cms/collection-table.js +8 -4
  55. package/dist/components/cms/collection-table.js.map +1 -1
  56. package/dist/components/cms/collections-sidebar.d.ts +10 -1
  57. package/dist/components/cms/collections-sidebar.js +13 -2
  58. package/dist/components/cms/collections-sidebar.js.map +1 -1
  59. package/dist/components/cms/detail-editor.js +2 -2
  60. package/dist/components/cms/detail-editor.js.map +1 -1
  61. package/dist/components/cms/doc-state.d.ts +0 -5
  62. package/dist/components/cms/doc-state.js +1 -7
  63. package/dist/components/cms/doc-state.js.map +1 -1
  64. package/dist/components/cms/editable-cell.js +1 -1
  65. package/dist/components/cms/editable-cell.js.map +1 -1
  66. package/dist/components/cms/field-control.d.ts +11 -2
  67. package/dist/components/cms/field-control.js +22 -1
  68. package/dist/components/cms/field-control.js.map +1 -1
  69. package/dist/components/cms/field-renderer.d.ts +71 -0
  70. package/dist/components/cms/field-renderer.js +67 -0
  71. package/dist/components/cms/field-renderer.js.map +1 -0
  72. package/dist/components/cms/filter-builder.js +6 -28
  73. package/dist/components/cms/filter-builder.js.map +1 -1
  74. package/dist/components/cms/scalar-value-editor.d.ts +35 -0
  75. package/dist/components/cms/scalar-value-editor.js +43 -0
  76. package/dist/components/cms/scalar-value-editor.js.map +1 -0
  77. package/dist/components/collab/collab-context.d.ts +6 -14
  78. package/dist/components/collab/collab-context.js +3 -12
  79. package/dist/components/collab/collab-context.js.map +1 -1
  80. package/dist/components/collab/collab-seed.d.ts +7 -19
  81. package/dist/components/collab/collab-seed.js +9 -31
  82. package/dist/components/collab/collab-seed.js.map +1 -1
  83. package/dist/components/components/registry-browser.js +6 -12
  84. package/dist/components/components/registry-browser.js.map +1 -1
  85. package/dist/components/guards.d.ts +0 -9
  86. package/dist/components/guards.js +0 -12
  87. package/dist/components/guards.js.map +1 -1
  88. package/dist/components/project-session.js +8 -3
  89. package/dist/components/project-session.js.map +1 -1
  90. package/dist/components/section-nav.d.ts +10 -10
  91. package/dist/components/section-nav.js +48 -25
  92. package/dist/components/section-nav.js.map +1 -1
  93. package/dist/components/section-state.d.ts +0 -2
  94. package/dist/components/section-state.js +0 -9
  95. package/dist/components/section-state.js.map +1 -1
  96. package/dist/components/section-surface.d.ts +3 -7
  97. package/dist/components/section-surface.js +6 -14
  98. package/dist/components/section-surface.js.map +1 -1
  99. package/dist/components/sections.d.ts +0 -1
  100. package/dist/components/sections.js +0 -10
  101. package/dist/components/sections.js.map +1 -1
  102. package/dist/components/studio-project-screen.d.ts +3 -4
  103. package/dist/components/studio-project-screen.js +41 -160
  104. package/dist/components/studio-project-screen.js.map +1 -1
  105. package/dist/components/studio-shell.d.ts +0 -4
  106. package/dist/components/studio-shell.js +14 -14
  107. package/dist/components/studio-shell.js.map +1 -1
  108. package/dist/lib/data/collection-content.d.ts +19 -1
  109. package/dist/lib/data/collection-content.js +16 -0
  110. package/dist/lib/data/collection-content.js.map +1 -1
  111. package/dist/lib/operations/graph-commands.d.ts +2 -6
  112. package/dist/lib/operations/graph-commands.js +1 -2
  113. package/dist/lib/operations/graph-commands.js.map +1 -1
  114. package/dist/lib/operations/index.d.ts +0 -2
  115. package/dist/lib/operations/index.js +0 -2
  116. package/dist/lib/operations/index.js.map +1 -1
  117. package/dist/lib/operations/session-hydration.d.ts +14 -24
  118. package/dist/lib/operations/session-hydration.js +15 -34
  119. package/dist/lib/operations/session-hydration.js.map +1 -1
  120. package/dist/lib/persistence/convex-adapter.d.ts +1 -3
  121. package/dist/lib/persistence/convex-adapter.js +1 -154
  122. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  123. package/dist/lib/persona/labels.js +0 -2
  124. package/dist/lib/persona/labels.js.map +1 -1
  125. package/dist/lib/references/reference-model.d.ts +0 -16
  126. package/dist/lib/references/reference-model.js +2 -54
  127. package/dist/lib/references/reference-model.js.map +1 -1
  128. package/dist/lib/references/references-store.js +3 -4
  129. package/dist/lib/references/references-store.js.map +1 -1
  130. package/dist/lib/registry/index.d.ts +0 -16
  131. package/dist/lib/registry/index.js +0 -15
  132. package/dist/lib/registry/index.js.map +1 -1
  133. package/dist/lib/webhooks/tags.js +2 -16
  134. package/dist/lib/webhooks/tags.js.map +1 -1
  135. package/dist/lib/workspace/index.d.ts +27 -1
  136. package/dist/lib/workspace/index.js +34 -0
  137. package/dist/lib/workspace/index.js.map +1 -1
  138. package/dist/router/match.d.ts +22 -34
  139. package/dist/router/match.js +22 -40
  140. package/dist/router/match.js.map +1 -1
  141. package/package.json +3 -2
  142. package/convex/graphs.ts +0 -55
  143. package/convex/publishing.ts +0 -76
@@ -1 +1 @@
1
- {"version":3,"file":"section-nav.js","sourceRoot":"","sources":["../../src/components/section-nav.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/router'\nimport { PanelLeftClose, PanelLeftOpen } from 'lucide-react'\nimport type { ProjectId, SectionAvailabilityMap } from '@elytracms/core/studio-core'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { SECTION_NAV_ITEMS, activeSectionFromPath, navAvailabilityOf } from './sections'\nimport type { StudioNavId } from './sections'\n\nexport interface SectionNavProps {\n projectId: ProjectId\n activeSection: StudioNavId | null\n availability: SectionAvailabilityMap\n /** User-toggled collapse (EC-214) — icons only when collapsed, persisted by the shell. */\n collapsed: boolean\n onToggleCollapsed: () => void\n}\n\n/**\n * Vertical section rail (brief §8.1). Availability-aware: `available`/`planned`\n * are navigable; `unavailable` is rendered disabled. User-collapsible to an\n * icon rail (EC-214) — the preference persists, and the active section carries a\n * strong highlight (accent fill + a leading marker bar).\n */\nexport function SectionNav({\n projectId,\n activeSection,\n availability,\n collapsed,\n onToggleCollapsed,\n}: SectionNavProps) {\n // Recover the active id from the path if the shell didn't supply one — the\n // shared parser is the single source of truth for the section segment index.\n const { pathname } = useLocation()\n const active = activeSection ?? activeSectionFromPath(pathname)\n return (\n <nav\n aria-label=\"Sections\"\n className={cn(\n 'flex shrink-0 flex-col gap-0.5 border-r border-border bg-card/40 p-2 transition-[width] duration-150',\n collapsed ? 'w-[3.5rem]' : 'w-56',\n )}\n >\n {SECTION_NAV_ITEMS.map((item) => {\n const state = navAvailabilityOf(availability, item.id)\n const isActive = item.id === active\n const Icon = item.icon\n\n const body = (\n <>\n {/* Strong active marker: a leading accent bar (EC-214). */}\n {isActive ? (\n <span\n className=\"absolute inset-y-1.5 left-0 w-0.5 rounded-full bg-primary\"\n aria-hidden\n />\n ) : null}\n <Icon className=\"size-4 shrink-0\" />\n {collapsed ? null : (\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n )}\n {state === 'planned' && !collapsed ? (\n <span\n className=\"size-1.5 shrink-0 rounded-full bg-warning\"\n aria-hidden\n title=\"Planned\"\n />\n ) : null}\n </>\n )\n\n const base = cn(\n 'group relative flex h-9 items-center gap-2.5 rounded-md text-sm transition-colors',\n collapsed ? 'justify-center px-0' : 'px-3',\n )\n\n // EC-221: an unavailable section is absent from the rail (not greyed) —\n // e.g. Extensions, cut from the v1 nav (vision §5). It stays reachable by\n // URL for Milestone E revival; the rail simply doesn't advertise it.\n if (state === 'unavailable') return null\n\n return (\n <Link\n key={item.id}\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: item.id }}\n title={item.label}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n base,\n isActive\n ? 'bg-accent font-medium text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {body}\n </Link>\n )\n })}\n\n {/* Collapse toggle pinned to the rail foot (EC-214). */}\n <button\n type=\"button\"\n onClick={onToggleCollapsed}\n aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n aria-pressed={collapsed}\n title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n className={cn(\n 'mt-auto flex h-9 items-center gap-2.5 rounded-md text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',\n collapsed ? 'justify-center px-0' : 'px-3',\n )}\n >\n {collapsed ? (\n <PanelLeftOpen className=\"size-4 shrink-0\" />\n ) : (\n <>\n <PanelLeftClose className=\"size-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate text-left\">Collapse</span>\n </>\n )}\n </button>\n </nav>\n )\n}\n"]}
1
+ {"version":3,"file":"section-nav.js","sourceRoot":"","sources":["../../src/components/section-nav.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/router'\nimport { AlertTriangle, PanelLeftClose, PanelLeftOpen } from 'lucide-react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { Text } from '@elytracms/studio/ui'\nimport { useAppViews } from './app-views/default-modules'\nimport { activeModuleFromPath, useSidebar } from './app-views/sidebar'\nimport type { ResolvedSidebarItem } from './app-views/sidebar'\n\nexport interface SectionNavProps {\n project: Project\n /** User-toggled collapse (EC-214) — icons only when collapsed, persisted by the shell. */\n collapsed: boolean\n onToggleCollapsed: () => void\n}\n\n/**\n * Vertical section rail (brief §8.1), now driven by the configurable sidebar\n * (EC-237): a consumer-extendable tree of items / groups / separators resolved\n * over the app-view module registry (EC-239). Availability-aware (`unavailable`\n * items are absent per EC-221); items deep-link to a module + resource; the\n * preference-persisted collapse and the strong active marker (EC-214) are\n * preserved. Customization issues (duplicate ids, unknown modules, unresolvable\n * deep-links) render at the rail foot — visible, never silent.\n */\nexport function SectionNav({ project, collapsed, onToggleCollapsed }: SectionNavProps) {\n const { pathname } = useLocation()\n const active = activeModuleFromPath(pathname)\n const { nodes, issues: sidebarIssues } = useSidebar(project)\n const { issues: moduleIssues } = useAppViews()\n const issues = [...moduleIssues, ...sidebarIssues]\n const projectId = project.id\n\n return (\n <nav\n aria-label=\"Sections\"\n className={cn(\n 'flex shrink-0 flex-col gap-0.5 border-r border-border bg-card/40 p-2 transition-[width] duration-150',\n collapsed ? 'w-[3.5rem]' : 'w-56',\n )}\n >\n {nodes.map((node) => {\n if (node.kind === 'separator') {\n return <div key={node.id} className=\"my-1.5 border-t border-border/60\" aria-hidden />\n }\n if (node.kind === 'group') {\n const items = node.items.filter((item) => item.availability !== 'unavailable')\n if (items.length === 0) return null\n return (\n <div key={node.id} className=\"mt-2 first:mt-0\">\n {collapsed ? (\n <div className=\"my-1 border-t border-border/60\" aria-hidden />\n ) : (\n <Text\n size=\"xs\"\n tone=\"subtle\"\n className=\"px-3 pb-1 pt-1 font-medium uppercase tracking-wide\"\n >\n {node.label}\n </Text>\n )}\n {items.map((item) => (\n <NavItemLink\n key={item.id}\n projectId={projectId}\n item={item}\n active={active}\n collapsed={collapsed}\n nested\n />\n ))}\n </div>\n )\n }\n // EC-221: an unavailable item is absent from the rail (not greyed) — it\n // stays reachable by URL, the rail simply doesn't advertise it.\n if (node.availability === 'unavailable') return null\n return (\n <NavItemLink\n key={node.id}\n projectId={projectId}\n item={node}\n active={active}\n collapsed={collapsed}\n />\n )\n })}\n\n <div className=\"mt-auto flex flex-col gap-0.5\">\n {issues.length > 0 ? <NavIssues issues={issues} collapsed={collapsed} /> : null}\n\n {/* Collapse toggle pinned to the rail foot (EC-214). */}\n <button\n type=\"button\"\n onClick={onToggleCollapsed}\n aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n aria-pressed={collapsed}\n title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n className={cn(\n 'flex h-9 items-center gap-2.5 rounded-md text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',\n collapsed ? 'justify-center px-0' : 'px-3',\n )}\n >\n {collapsed ? (\n <PanelLeftOpen className=\"size-4 shrink-0\" />\n ) : (\n <>\n <PanelLeftClose className=\"size-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate text-left\">Collapse</span>\n </>\n )}\n </button>\n </div>\n </nav>\n )\n}\n\n/** One rail item (top-level or nested) — a deep-linkable Link to its module. */\nfunction NavItemLink({\n projectId,\n item,\n active,\n collapsed,\n nested = false,\n}: {\n projectId: string\n item: ResolvedSidebarItem\n active: string | null\n collapsed: boolean\n nested?: boolean\n}) {\n const isActive = item.moduleId === active\n const Icon = item.icon\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: item.path }}\n title={item.label}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n 'group relative flex h-9 items-center gap-2.5 rounded-md text-sm transition-colors',\n collapsed ? 'justify-center px-0' : nested ? 'pl-7 pr-3' : 'px-3',\n isActive\n ? 'bg-accent font-medium text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {/* Strong active marker: a leading accent bar (EC-214). */}\n {isActive ? (\n <span className=\"absolute inset-y-1.5 left-0 w-0.5 rounded-full bg-primary\" aria-hidden />\n ) : null}\n <Icon className=\"size-4 shrink-0\" />\n {collapsed ? null : <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>}\n {item.availability === 'planned' && !collapsed ? (\n <span className=\"size-1.5 shrink-0 rounded-full bg-warning\" aria-hidden title=\"Planned\" />\n ) : null}\n </Link>\n )\n}\n\n/** Visible customization issues at the rail foot — the seam's \"never silent\" (EC-236). */\nfunction NavIssues({ issues, collapsed }: { issues: CustomizationIssue[]; collapsed: boolean }) {\n if (collapsed) {\n return (\n <div\n className=\"grid h-9 place-items-center text-amber-600 dark:text-amber-400\"\n title={issues.map((issue) => issue.message).join('\\n')}\n >\n <AlertTriangle className=\"size-4\" aria-label={`${issues.length} sidebar issues`} />\n </div>\n )\n }\n return (\n <div className=\"mb-1 flex flex-col gap-1 rounded-md border border-amber-500/30 bg-amber-500/5 p-2\">\n {issues.map((issue, index) => (\n <div\n key={`${issue.point}:${issue.id ?? index}`}\n className=\"flex items-start gap-1.5 text-amber-600 dark:text-amber-400\"\n title={issue.message}\n >\n <AlertTriangle className=\"mt-0.5 size-3.5 shrink-0\" aria-hidden />\n <Text size=\"xs\" className=\"min-w-0 leading-snug\">\n {issue.message}\n </Text>\n </div>\n ))}\n </div>\n )\n}\n"]}
@@ -20,5 +20,3 @@ export declare function SectionStateView({ state, title, description, action, cl
20
20
  export declare function SectionSkeleton({ className }: {
21
21
  className?: string;
22
22
  }): import("react").JSX.Element;
23
- /** The full set of forceable states, for fixtures and the state switcher. */
24
- export declare const FORCEABLE_STATES: readonly SectionState[];
@@ -66,13 +66,4 @@ export function SectionStateView({ state, title, description, action, className,
66
66
  export function SectionSkeleton({ className }) {
67
67
  return (_jsxs("div", { "data-section-state": "loading", className: cn('space-y-4 p-6', className), "aria-busy": true, children: [_jsx("div", { className: "h-7 w-48 animate-pulse rounded-md bg-muted" }), _jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsx("div", { className: "h-28 animate-pulse rounded-xl bg-muted/70" }, i))) })] }));
68
68
  }
69
- /** The full set of forceable states, for fixtures and the state switcher. */
70
- export const FORCEABLE_STATES = [
71
- 'working',
72
- 'empty',
73
- 'loading',
74
- 'error',
75
- 'unavailable',
76
- 'planned',
77
- ];
78
69
  //# sourceMappingURL=section-state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"section-state.js","sourceRoot":"","sources":["../../src/components/section-state.tsx"],"names":[],"mappings":"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","sourcesContent":["import { CircleAlert, Clock, Inbox, Loader2, Lock, type LucideIcon } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport type { SectionAvailability, SectionState } from '@elytracms/core/studio-core'\nimport { Badge, Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * Resolve the runtime state of a section (brief §8.1, §11). A `forced` value\n * (e.g. from a `?state=` param) wins, making every state reachable\n * deterministically; otherwise availability decides.\n */\nexport function resolveSectionState(\n availability: SectionAvailability,\n forced?: SectionState | null,\n): SectionState {\n if (forced) return forced\n if (availability === 'planned') return 'planned'\n if (availability === 'unavailable') return 'unavailable'\n return 'working'\n}\n\ninterface StatePreset {\n icon: LucideIcon\n title: string\n description: string\n badge: { label: string; variant: 'outline' | 'warning' | 'error' | 'info' }\n spin?: boolean\n}\n\nconst PRESETS: Record<Exclude<SectionState, 'working'>, StatePreset> = {\n empty: {\n icon: Inbox,\n title: 'Nothing here yet',\n description: 'This section has no content for the active project.',\n badge: { label: 'empty', variant: 'outline' },\n },\n loading: {\n icon: Loader2,\n title: 'Loading…',\n description: 'Fetching the latest state for this section.',\n badge: { label: 'loading', variant: 'info' },\n spin: true,\n },\n error: {\n icon: CircleAlert,\n title: 'Something went wrong',\n description: 'This section could not be loaded. Try again in a moment.',\n badge: { label: 'error', variant: 'error' },\n },\n unavailable: {\n icon: Lock,\n title: 'Not available',\n description: 'This section is not available for the active project.',\n badge: { label: 'unavailable', variant: 'outline' },\n },\n planned: {\n icon: Clock,\n title: 'Planned',\n description: 'This surface is planned and not yet enabled for this project.',\n badge: { label: 'planned', variant: 'warning' },\n },\n}\n\nexport interface SectionStateViewProps {\n state: Exclude<SectionState, 'working'>\n /** Section-specific heading, e.g. \"CMS\" — overrides the generic preset title. */\n title?: string\n description?: ReactNode\n action?: ReactNode\n className?: string\n}\n\n/** Loading uses a skeleton; the rest use a centered placeholder. Consistent across sections. */\nexport function SectionStateView({\n state,\n title,\n description,\n action,\n className,\n}: SectionStateViewProps) {\n if (state === 'loading') return <SectionSkeleton className={className} />\n\n const preset = PRESETS[state]\n const Icon = preset.icon\n const tone =\n state === 'error'\n ? 'border-destructive/30 bg-destructive/5'\n : state === 'planned'\n ? 'border-warning/30 bg-warning/5'\n : 'border-border bg-card/40'\n\n return (\n <div\n data-section-state={state}\n className={cn('grid min-h-[18rem] place-items-center p-8', className)}\n >\n <Stack\n gap=\"3\"\n align=\"center\"\n className={cn('max-w-sm rounded-xl border border-dashed px-8 py-10 text-center', tone)}\n >\n <span className=\"grid size-11 place-items-center rounded-lg border border-border bg-background text-muted-foreground\">\n <Icon className=\"size-5\" />\n </span>\n <Stack gap=\"1\" align=\"center\">\n <Heading level={2} size=\"h3\">\n {title ?? preset.title}\n </Heading>\n <Text size=\"sm\" tone=\"muted\">\n {description ?? preset.description}\n </Text>\n </Stack>\n <Badge variant={preset.badge.variant}>{preset.badge.label}</Badge>\n {action ? <div className=\"pt-1\">{action}</div> : null}\n </Stack>\n </div>\n )\n}\n\n/** Shimmer skeleton standing in for an in-flight section load. */\nexport function SectionSkeleton({ className }: { className?: string }) {\n return (\n <div data-section-state=\"loading\" className={cn('space-y-4 p-6', className)} aria-busy>\n <div className=\"h-7 w-48 animate-pulse rounded-md bg-muted\" />\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-28 animate-pulse rounded-xl bg-muted/70\" />\n ))}\n </div>\n </div>\n )\n}\n\n/** The full set of forceable states, for fixtures and the state switcher. */\nexport const FORCEABLE_STATES: readonly SectionState[] = [\n 'working',\n 'empty',\n 'loading',\n 'error',\n 'unavailable',\n 'planned',\n]\n"]}
1
+ {"version":3,"file":"section-state.js","sourceRoot":"","sources":["../../src/components/section-state.tsx"],"names":[],"mappings":"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","sourcesContent":["import { CircleAlert, Clock, Inbox, Loader2, Lock, type LucideIcon } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport type { SectionAvailability, SectionState } from '@elytracms/core/studio-core'\nimport { Badge, Heading, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * Resolve the runtime state of a section (brief §8.1, §11). A `forced` value\n * (e.g. from a `?state=` param) wins, making every state reachable\n * deterministically; otherwise availability decides.\n */\nexport function resolveSectionState(\n availability: SectionAvailability,\n forced?: SectionState | null,\n): SectionState {\n if (forced) return forced\n if (availability === 'planned') return 'planned'\n if (availability === 'unavailable') return 'unavailable'\n return 'working'\n}\n\ninterface StatePreset {\n icon: LucideIcon\n title: string\n description: string\n badge: { label: string; variant: 'outline' | 'warning' | 'error' | 'info' }\n spin?: boolean\n}\n\nconst PRESETS: Record<Exclude<SectionState, 'working'>, StatePreset> = {\n empty: {\n icon: Inbox,\n title: 'Nothing here yet',\n description: 'This section has no content for the active project.',\n badge: { label: 'empty', variant: 'outline' },\n },\n loading: {\n icon: Loader2,\n title: 'Loading…',\n description: 'Fetching the latest state for this section.',\n badge: { label: 'loading', variant: 'info' },\n spin: true,\n },\n error: {\n icon: CircleAlert,\n title: 'Something went wrong',\n description: 'This section could not be loaded. Try again in a moment.',\n badge: { label: 'error', variant: 'error' },\n },\n unavailable: {\n icon: Lock,\n title: 'Not available',\n description: 'This section is not available for the active project.',\n badge: { label: 'unavailable', variant: 'outline' },\n },\n planned: {\n icon: Clock,\n title: 'Planned',\n description: 'This surface is planned and not yet enabled for this project.',\n badge: { label: 'planned', variant: 'warning' },\n },\n}\n\nexport interface SectionStateViewProps {\n state: Exclude<SectionState, 'working'>\n /** Section-specific heading, e.g. \"CMS\" — overrides the generic preset title. */\n title?: string\n description?: ReactNode\n action?: ReactNode\n className?: string\n}\n\n/** Loading uses a skeleton; the rest use a centered placeholder. Consistent across sections. */\nexport function SectionStateView({\n state,\n title,\n description,\n action,\n className,\n}: SectionStateViewProps) {\n if (state === 'loading') return <SectionSkeleton className={className} />\n\n const preset = PRESETS[state]\n const Icon = preset.icon\n const tone =\n state === 'error'\n ? 'border-destructive/30 bg-destructive/5'\n : state === 'planned'\n ? 'border-warning/30 bg-warning/5'\n : 'border-border bg-card/40'\n\n return (\n <div\n data-section-state={state}\n className={cn('grid min-h-[18rem] place-items-center p-8', className)}\n >\n <Stack\n gap=\"3\"\n align=\"center\"\n className={cn('max-w-sm rounded-xl border border-dashed px-8 py-10 text-center', tone)}\n >\n <span className=\"grid size-11 place-items-center rounded-lg border border-border bg-background text-muted-foreground\">\n <Icon className=\"size-5\" />\n </span>\n <Stack gap=\"1\" align=\"center\">\n <Heading level={2} size=\"h3\">\n {title ?? preset.title}\n </Heading>\n <Text size=\"sm\" tone=\"muted\">\n {description ?? preset.description}\n </Text>\n </Stack>\n <Badge variant={preset.badge.variant}>{preset.badge.label}</Badge>\n {action ? <div className=\"pt-1\">{action}</div> : null}\n </Stack>\n </div>\n )\n}\n\n/** Shimmer skeleton standing in for an in-flight section load. */\nexport function SectionSkeleton({ className }: { className?: string }) {\n return (\n <div data-section-state=\"loading\" className={cn('space-y-4 p-6', className)} aria-busy>\n <div className=\"h-7 w-48 animate-pulse rounded-md bg-muted\" />\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-28 animate-pulse rounded-xl bg-muted/70\" />\n ))}\n </div>\n </div>\n )\n}\n"]}
@@ -7,19 +7,15 @@ export interface SectionSurfaceProps {
7
7
  section: SectionId;
8
8
  /** Forced state from `?state=`; overrides availability. */
9
9
  forcedState?: SectionState;
10
- /** Working-state content. When omitted, a phase placeholder is shown. */
10
+ /** Working-state content. */
11
11
  children?: ReactNode;
12
12
  }
13
13
  /**
14
14
  * Resolves a section's deterministic state (brief §8.1, §11) and renders it: the
15
- * working content (or a phase placeholder) when `working`, else a consistent
16
- * state view. A small state switcher makes every state reachable for inspection.
15
+ * working content when `working`, else a consistent state view. A small state
16
+ * switcher makes every state reachable for inspection.
17
17
  */
18
18
  export declare function SectionSurface({ project, section, forcedState, children }: SectionSurfaceProps): import("react").JSX.Element;
19
- /** Placeholder for an available section whose live surface lands in a later phase. */
20
- export declare function PhasePlaceholder({ section }: {
21
- section: SectionId;
22
- }): import("react").JSX.Element;
23
19
  /**
24
20
  * Deterministic state switcher (brief §11). Links to the same section with each
25
21
  * `?state=` value so agents/humans can reproduce every state by URL or click.
@@ -1,11 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router';
3
- import { ArrowUpRight } from 'lucide-react';
4
- import { availabilityOf, sectionStateSchema } from '@elytracms/core/studio-core';
5
- import { Badge, Button, Code, Row, Stack, Text } from '@elytracms/studio/ui';
3
+ import { availabilityOf, SECTIONS, sectionStateSchema } from '@elytracms/core/studio-core';
4
+ import { Code, Row, Stack, Text } from '@elytracms/studio/ui';
6
5
  import { cn } from '@elytracms/studio/lib/utils';
7
6
  import { resolveSectionState, SectionStateView } from './section-state';
8
- import { sectionNavItem } from './sections';
9
7
  /** Parse a `?state=` search param into a SectionState (undefined when absent/invalid). */
10
8
  export function parseSectionState(raw) {
11
9
  const parsed = sectionStateSchema.safeParse(raw);
@@ -13,19 +11,13 @@ export function parseSectionState(raw) {
13
11
  }
14
12
  /**
15
13
  * Resolves a section's deterministic state (brief §8.1, §11) and renders it: the
16
- * working content (or a phase placeholder) when `working`, else a consistent
17
- * state view. A small state switcher makes every state reachable for inspection.
14
+ * working content when `working`, else a consistent state view. A small state
15
+ * switcher makes every state reachable for inspection.
18
16
  */
19
17
  export function SectionSurface({ project, section, forcedState, children }) {
20
18
  const availability = availabilityOf(project.settings.sectionAvailability, section);
21
19
  const state = resolveSectionState(availability, forcedState);
22
- return (_jsxs(Stack, { gap: "none", className: "min-h-full", children: [_jsx(StateSwitcher, { projectId: project.id, section: section, current: forcedState }), state === 'working' ? ((children ?? _jsx(PhasePlaceholder, { section: section }))) : (_jsx(SectionStateView, { state: state, title: sectionNavItem(section).label }))] }));
23
- }
24
- /** Placeholder for an available section whose live surface lands in a later phase. */
25
- export function PhasePlaceholder({ section }) {
26
- const item = sectionNavItem(section);
27
- const Icon = item.icon;
28
- return (_jsx("div", { "data-section-state": "working", className: "grid flex-1 place-items-center p-8", children: _jsxs(Stack, { gap: "3", align: "center", className: "max-w-md text-center", children: [_jsx("span", { className: "grid size-12 place-items-center rounded-xl border border-border bg-card text-primary", children: _jsx(Icon, { className: "size-6" }) }), _jsxs(Stack, { gap: "1", align: "center", children: [_jsxs(Text, { weight: "semibold", children: [item.label, " is enabled"] }), _jsxs(Text, { size: "sm", tone: "muted", children: [item.description, " The full surface arrives in ", item.phase, "."] })] }), _jsx(Badge, { variant: "primary", children: item.phase })] }) }));
20
+ return (_jsxs(Stack, { gap: "none", className: "min-h-full", children: [_jsx(StateSwitcher, { projectId: project.id, section: section, current: forcedState }), state === 'working' ? (children) : (_jsx(SectionStateView, { state: state, title: SECTIONS[section].label }))] }));
29
21
  }
30
22
  /**
31
23
  * Deterministic state switcher (brief §11). Links to the same section with each
@@ -41,6 +33,6 @@ export function StateSwitcher({ projectId, section, current, }) {
41
33
  return (_jsx(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: section }, search: { state: s }, className: cn('rounded-md px-2 py-0.5 font-mono text-xs transition-colors', active
42
34
  ? 'bg-accent text-accent-foreground'
43
35
  : 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: s }, s));
44
- }), _jsx(Code, { tone: "muted", className: "ml-2 hidden sm:inline-flex", children: activeKey === 'default' ? 'auto' : `?state=${activeKey}` }), _jsx("span", { className: "flex-1" }), _jsx(Button, { variant: "ghost", size: "sm", asChild: true, className: "hidden md:inline-flex", children: _jsxs(Link, { to: "/", className: "gap-1", children: ["Docs ", _jsx(ArrowUpRight, {})] }) })] }));
36
+ }), _jsx(Code, { tone: "muted", className: "ml-2 hidden sm:inline-flex", children: activeKey === 'default' ? 'auto' : `?state=${activeKey}` })] }));
45
37
  }
46
38
  //# sourceMappingURL=section-surface.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"section-surface.js","sourceRoot":"","sources":["../../src/components/section-surface.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router'\nimport { ArrowUpRight } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport { availabilityOf, sectionStateSchema } from '@elytracms/core/studio-core'\nimport type { Project, ProjectId, SectionId, SectionState } from '@elytracms/core/studio-core'\nimport { Badge, Button, Code, Row, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { resolveSectionState, SectionStateView } from './section-state'\nimport { sectionNavItem } from './sections'\n\n/** Parse a `?state=` search param into a SectionState (undefined when absent/invalid). */\nexport function parseSectionState(raw: unknown): SectionState | undefined {\n const parsed = sectionStateSchema.safeParse(raw)\n return parsed.success ? parsed.data : undefined\n}\n\nexport interface SectionSurfaceProps {\n project: Project\n section: SectionId\n /** Forced state from `?state=`; overrides availability. */\n forcedState?: SectionState\n /** Working-state content. When omitted, a phase placeholder is shown. */\n children?: ReactNode\n}\n\n/**\n * Resolves a section's deterministic state (brief §8.1, §11) and renders it: the\n * working content (or a phase placeholder) when `working`, else a consistent\n * state view. A small state switcher makes every state reachable for inspection.\n */\nexport function SectionSurface({ project, section, forcedState, children }: SectionSurfaceProps) {\n const availability = availabilityOf(project.settings.sectionAvailability, section)\n const state = resolveSectionState(availability, forcedState)\n\n return (\n <Stack gap=\"none\" className=\"min-h-full\">\n <StateSwitcher projectId={project.id} section={section} current={forcedState} />\n {state === 'working' ? (\n (children ?? <PhasePlaceholder section={section} />)\n ) : (\n <SectionStateView state={state} title={sectionNavItem(section).label} />\n )}\n </Stack>\n )\n}\n\n/** Placeholder for an available section whose live surface lands in a later phase. */\nexport function PhasePlaceholder({ section }: { section: SectionId }) {\n const item = sectionNavItem(section)\n const Icon = item.icon\n return (\n <div data-section-state=\"working\" className=\"grid flex-1 place-items-center p-8\">\n <Stack gap=\"3\" align=\"center\" className=\"max-w-md text-center\">\n <span className=\"grid size-12 place-items-center rounded-xl border border-border bg-card text-primary\">\n <Icon className=\"size-6\" />\n </span>\n <Stack gap=\"1\" align=\"center\">\n <Text weight=\"semibold\">{item.label} is enabled</Text>\n <Text size=\"sm\" tone=\"muted\">\n {item.description} The full surface arrives in {item.phase}.\n </Text>\n </Stack>\n <Badge variant=\"primary\">{item.phase}</Badge>\n </Stack>\n </div>\n )\n}\n\n/**\n * Deterministic state switcher (brief §11). Links to the same section with each\n * `?state=` value so agents/humans can reproduce every state by URL or click.\n */\nexport function StateSwitcher({\n projectId,\n section,\n current,\n}: {\n projectId: ProjectId\n section: SectionId\n current?: SectionState\n}) {\n const states: SectionState[] = ['working', 'empty', 'loading', 'error', 'unavailable', 'planned']\n const activeKey = current ?? 'default'\n return (\n <Row\n gap=\"1\"\n wrap\n className=\"shrink-0 border-b border-dashed border-border/70 px-4 py-2 sm:px-6\"\n data-testid=\"state-switcher\"\n >\n <Text size=\"xs\" tone=\"subtle\" mono className=\"mr-1\">\n state\n </Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{}}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n activeKey === 'default'\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n default\n </Link>\n {states.map((s) => {\n const active = s === activeKey\n return (\n <Link\n key={s}\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{ state: s }}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n active\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {s}\n </Link>\n )\n })}\n <Code tone=\"muted\" className=\"ml-2 hidden sm:inline-flex\">\n {activeKey === 'default' ? 'auto' : `?state=${activeKey}`}\n </Code>\n <span className=\"flex-1\" />\n <Button variant=\"ghost\" size=\"sm\" asChild className=\"hidden md:inline-flex\">\n <Link to=\"/\" className=\"gap-1\">\n Docs <ArrowUpRight />\n </Link>\n </Button>\n </Row>\n )\n}\n"]}
1
+ {"version":3,"file":"section-surface.js","sourceRoot":"","sources":["../../src/components/section-surface.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router'\nimport type { ReactNode } from 'react'\nimport { availabilityOf, SECTIONS, sectionStateSchema } from '@elytracms/core/studio-core'\nimport type { Project, ProjectId, SectionId, SectionState } from '@elytracms/core/studio-core'\nimport { Code, Row, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { resolveSectionState, SectionStateView } from './section-state'\n\n/** Parse a `?state=` search param into a SectionState (undefined when absent/invalid). */\nexport function parseSectionState(raw: unknown): SectionState | undefined {\n const parsed = sectionStateSchema.safeParse(raw)\n return parsed.success ? parsed.data : undefined\n}\n\nexport interface SectionSurfaceProps {\n project: Project\n section: SectionId\n /** Forced state from `?state=`; overrides availability. */\n forcedState?: SectionState\n /** Working-state content. */\n children?: ReactNode\n}\n\n/**\n * Resolves a section's deterministic state (brief §8.1, §11) and renders it: the\n * working content when `working`, else a consistent state view. A small state\n * switcher makes every state reachable for inspection.\n */\nexport function SectionSurface({ project, section, forcedState, children }: SectionSurfaceProps) {\n const availability = availabilityOf(project.settings.sectionAvailability, section)\n const state = resolveSectionState(availability, forcedState)\n\n return (\n <Stack gap=\"none\" className=\"min-h-full\">\n <StateSwitcher projectId={project.id} section={section} current={forcedState} />\n {state === 'working' ? (\n children\n ) : (\n <SectionStateView state={state} title={SECTIONS[section].label} />\n )}\n </Stack>\n )\n}\n\n/**\n * Deterministic state switcher (brief §11). Links to the same section with each\n * `?state=` value so agents/humans can reproduce every state by URL or click.\n */\nexport function StateSwitcher({\n projectId,\n section,\n current,\n}: {\n projectId: ProjectId\n section: SectionId\n current?: SectionState\n}) {\n const states: SectionState[] = ['working', 'empty', 'loading', 'error', 'unavailable', 'planned']\n const activeKey = current ?? 'default'\n return (\n <Row\n gap=\"1\"\n wrap\n className=\"shrink-0 border-b border-dashed border-border/70 px-4 py-2 sm:px-6\"\n data-testid=\"state-switcher\"\n >\n <Text size=\"xs\" tone=\"subtle\" mono className=\"mr-1\">\n state\n </Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{}}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n activeKey === 'default'\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n default\n </Link>\n {states.map((s) => {\n const active = s === activeKey\n return (\n <Link\n key={s}\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{ state: s }}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n active\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {s}\n </Link>\n )\n })}\n <Code tone=\"muted\" className=\"ml-2 hidden sm:inline-flex\">\n {activeKey === 'default' ? 'auto' : `?state=${activeKey}`}\n </Code>\n </Row>\n )\n}\n"]}
@@ -12,7 +12,6 @@ export interface SectionNavItem {
12
12
  label: string;
13
13
  description: string;
14
14
  icon: LucideIcon;
15
- phase: string;
16
15
  }
17
16
  /** Navigation items in canonical order (brief §8.1 + EC-170). */
18
17
  export declare const SECTION_NAV_ITEMS: readonly SectionNavItem[];
@@ -1,14 +1,5 @@
1
1
  import { Blocks, Database, Image, Puzzle, Settings, Signpost } from 'lucide-react';
2
2
  import { SECTIONS, SECTION_IDS, availabilityOf, sectionIdSchema } from '@elytracms/core/studio-core';
3
- /** Which Studio phase delivers each section's live surface (for placeholders). */
4
- const SECTION_PHASE = {
5
- components: 'Phase 4',
6
- cms: 'Phase 6',
7
- routes: 'Milestone B',
8
- assets: 'Phase 6',
9
- extensions: 'Phase 11',
10
- settings: 'Phase 2',
11
- };
12
3
  const ICONS = {
13
4
  components: Blocks,
14
5
  cms: Database,
@@ -34,7 +25,6 @@ export const SECTION_NAV_ITEMS = NAV_IDS.map((id) => ({
34
25
  label: descriptorOf(id).label,
35
26
  description: descriptorOf(id).description,
36
27
  icon: ICONS[id],
37
- phase: SECTION_PHASE[id],
38
28
  }));
39
29
  export function sectionNavItem(id) {
40
30
  return SECTION_NAV_ITEMS.find((item) => item.id === id);
@@ -1 +1 @@
1
- {"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/components/sections.ts"],"names":[],"mappings":"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","sourcesContent":["import { Blocks, Database, Image, Puzzle, Settings, Signpost } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { SECTIONS, SECTION_IDS, availabilityOf, sectionIdSchema } from '@elytracms/core/studio-core'\nimport type {\n Project,\n SectionAvailability,\n SectionAvailabilityMap,\n SectionId,\n} from '@elytracms/core/studio-core'\n\n/**\n * App-level navigation ids: the studio-core sections plus surfaces registered\n * by the app itself. `routes` (EC-170) is the first app-registered surface —\n * it lives next to the CMS in the rail and rides on the CMS availability,\n * since routes/redirects are CMS routing concerns (`@elytracms/core/cms-core`).\n */\nexport type StudioNavId = SectionId | 'routes'\n\n/** Which Studio phase delivers each section's live surface (for placeholders). */\nconst SECTION_PHASE: Record<StudioNavId, string> = {\n components: 'Phase 4',\n cms: 'Phase 6',\n routes: 'Milestone B',\n assets: 'Phase 6',\n extensions: 'Phase 11',\n settings: 'Phase 2',\n}\n\nexport interface SectionNavItem {\n id: StudioNavId\n label: string\n description: string\n icon: LucideIcon\n phase: string\n}\n\nconst ICONS: Record<StudioNavId, LucideIcon> = {\n components: Blocks,\n cms: Database,\n routes: Signpost,\n assets: Image,\n extensions: Puzzle,\n settings: Settings,\n}\n\n/** Descriptor for the app-registered Routes surface (EC-170). */\nconst ROUTES_DESCRIPTOR = {\n id: 'routes',\n label: 'Routes',\n description: 'View the resolved URL routes & redirects (defined in elytra.config.ts).',\n} as const\n\n/** Canonical nav order: studio-core order with Routes slotted after CMS. */\nconst NAV_IDS: readonly StudioNavId[] = SECTION_IDS.flatMap((id) =>\n id === 'cms' ? ([id, 'routes'] as const) : [id],\n)\n\nfunction descriptorOf(id: StudioNavId): { label: string; description: string } {\n return id === 'routes' ? ROUTES_DESCRIPTOR : SECTIONS[id]\n}\n\n/** Navigation items in canonical order (brief §8.1 + EC-170). */\nexport const SECTION_NAV_ITEMS: readonly SectionNavItem[] = NAV_IDS.map((id) => ({\n id,\n label: descriptorOf(id).label,\n description: descriptorOf(id).description,\n icon: ICONS[id],\n phase: SECTION_PHASE[id],\n}))\n\nexport function sectionNavItem(id: StudioNavId): SectionNavItem {\n return SECTION_NAV_ITEMS.find((item) => item.id === id)!\n}\n\n/**\n * Availability for a nav id. App-registered surfaces map onto the section\n * whose data they manage: Routes follows the CMS availability.\n */\nexport function navAvailabilityOf(\n map: SectionAvailabilityMap,\n id: StudioNavId,\n): SectionAvailability {\n return availabilityOf(map, id === 'routes' ? 'cms' : id)\n}\n\n/**\n * The section a project should open to: the first non-`unavailable` section in\n * canonical order. Settings is always available, so this never returns null.\n */\nexport function firstAvailableSection(project: Project): SectionId {\n const found = SECTION_IDS.find(\n (id) => availabilityOf(project.settings.sectionAvailability, id) !== 'unavailable',\n )\n return found ?? 'settings'\n}\n\n/**\n * Parse the active nav id out of a studio pathname `/$projectId/<section>[/...]`\n * (incl. the app-registered Routes surface, EC-170). Returns null for the bare\n * project URL or an unknown segment. The section is the SECOND path segment —\n * directly after the projectId; there is no `/p` prefix (EC-220). The single\n * source of truth for active-section detection: the shell breadcrumb and the\n * nav rail both read it, so the segment index lives in exactly one place.\n */\nexport function activeSectionFromPath(pathname: string): StudioNavId | null {\n const segment = pathname.split('/')[2] ?? ''\n if (segment === 'routes') return 'routes'\n const parsed = sectionIdSchema.safeParse(segment)\n return parsed.success ? parsed.data : null\n}\n"]}
1
+ {"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/components/sections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAElF,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAuBpG,MAAM,KAAK,GAAoC;IAC7C,UAAU,EAAE,MAAM;IAClB,GAAG,EAAE,QAAQ;IACb,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,KAAK;IACb,UAAU,EAAE,MAAM;IAClB,QAAQ,EAAE,QAAQ;CACnB,CAAA;AAED,iEAAiE;AACjE,MAAM,iBAAiB,GAAG;IACxB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,QAAQ;IACf,WAAW,EAAE,yEAAyE;CAC9E,CAAA;AAEV,4EAA4E;AAC5E,MAAM,OAAO,GAA2B,WAAW,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CACjE,EAAE,KAAK,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,EAAE,QAAQ,CAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAChD,CAAA;AAED,SAAS,YAAY,CAAC,EAAe;IACnC,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AAC3D,CAAC;AAED,iEAAiE;AACjE,MAAM,CAAC,MAAM,iBAAiB,GAA8B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/E,EAAE;IACF,KAAK,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,KAAK;IAC7B,WAAW,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,WAAW;IACzC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;CAChB,CAAC,CAAC,CAAA;AAEH,MAAM,UAAU,cAAc,CAAC,EAAe;IAC5C,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAE,CAAA;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAA2B,EAC3B,EAAe;IAEf,OAAO,cAAc,CAAC,GAAG,EAAE,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAC5B,CAAC,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,EAAE,EAAE,CAAC,KAAK,aAAa,CACnF,CAAA;IACD,OAAO,KAAK,IAAI,UAAU,CAAA;AAC5B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAC5C,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAA;IACzC,MAAM,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACjD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5C,CAAC","sourcesContent":["import { Blocks, Database, Image, Puzzle, Settings, Signpost } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { SECTIONS, SECTION_IDS, availabilityOf, sectionIdSchema } from '@elytracms/core/studio-core'\nimport type {\n Project,\n SectionAvailability,\n SectionAvailabilityMap,\n SectionId,\n} from '@elytracms/core/studio-core'\n\n/**\n * App-level navigation ids: the studio-core sections plus surfaces registered\n * by the app itself. `routes` (EC-170) is the first app-registered surface —\n * it lives next to the CMS in the rail and rides on the CMS availability,\n * since routes/redirects are CMS routing concerns (`@elytracms/core/cms-core`).\n */\nexport type StudioNavId = SectionId | 'routes'\n\nexport interface SectionNavItem {\n id: StudioNavId\n label: string\n description: string\n icon: LucideIcon\n}\n\nconst ICONS: Record<StudioNavId, LucideIcon> = {\n components: Blocks,\n cms: Database,\n routes: Signpost,\n assets: Image,\n extensions: Puzzle,\n settings: Settings,\n}\n\n/** Descriptor for the app-registered Routes surface (EC-170). */\nconst ROUTES_DESCRIPTOR = {\n id: 'routes',\n label: 'Routes',\n description: 'View the resolved URL routes & redirects (defined in elytra.config.ts).',\n} as const\n\n/** Canonical nav order: studio-core order with Routes slotted after CMS. */\nconst NAV_IDS: readonly StudioNavId[] = SECTION_IDS.flatMap((id) =>\n id === 'cms' ? ([id, 'routes'] as const) : [id],\n)\n\nfunction descriptorOf(id: StudioNavId): { label: string; description: string } {\n return id === 'routes' ? ROUTES_DESCRIPTOR : SECTIONS[id]\n}\n\n/** Navigation items in canonical order (brief §8.1 + EC-170). */\nexport const SECTION_NAV_ITEMS: readonly SectionNavItem[] = NAV_IDS.map((id) => ({\n id,\n label: descriptorOf(id).label,\n description: descriptorOf(id).description,\n icon: ICONS[id],\n}))\n\nexport function sectionNavItem(id: StudioNavId): SectionNavItem {\n return SECTION_NAV_ITEMS.find((item) => item.id === id)!\n}\n\n/**\n * Availability for a nav id. App-registered surfaces map onto the section\n * whose data they manage: Routes follows the CMS availability.\n */\nexport function navAvailabilityOf(\n map: SectionAvailabilityMap,\n id: StudioNavId,\n): SectionAvailability {\n return availabilityOf(map, id === 'routes' ? 'cms' : id)\n}\n\n/**\n * The section a project should open to: the first non-`unavailable` section in\n * canonical order. Settings is always available, so this never returns null.\n */\nexport function firstAvailableSection(project: Project): SectionId {\n const found = SECTION_IDS.find(\n (id) => availabilityOf(project.settings.sectionAvailability, id) !== 'unavailable',\n )\n return found ?? 'settings'\n}\n\n/**\n * Parse the active nav id out of a studio pathname `/$projectId/<section>[/...]`\n * (incl. the app-registered Routes surface, EC-170). Returns null for the bare\n * project URL or an unknown segment. The section is the SECOND path segment —\n * directly after the projectId; there is no `/p` prefix (EC-220). The single\n * source of truth for active-section detection: the shell breadcrumb and the\n * nav rail both read it, so the segment index lives in exactly one place.\n */\nexport function activeSectionFromPath(pathname: string): StudioNavId | null {\n const segment = pathname.split('/')[2] ?? ''\n if (segment === 'routes') return 'routes'\n const parsed = sectionIdSchema.safeParse(segment)\n return parsed.success ? parsed.data : null\n}\n"]}
@@ -11,9 +11,8 @@
11
11
  *
12
12
  * `projectId` stays a real route param (the `$projectId` segment); the trailing
13
13
  * pathname (`_splat`) is matched against the engine route table
14
- * (`matchStudioRoute`) and the deeper ids (collection / document / component)
15
- * flow down as props. Every screen already takes its ids as props, so this
16
- * collapse is mechanical — the package never read deep params through the router;
17
- * the disappearing route-file wrappers did.
14
+ * (`matchStudioRoute`) into a module id + segments, and the active module (EC-239)
15
+ * resolves the screen. Every surface already takes its ids as props, so the
16
+ * resolver just hands the module its parsed deep-link.
18
17
  */
19
18
  export declare function StudioProjectScreen(): import("react").JSX.Element;
@@ -1,35 +1,17 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect } from 'react';
3
- import { isSingleton, resolveSingletonDocument } from '@elytracms/core/cms-core';
4
- import { validSampleGraph } from '@elytracms/core/project-graph';
5
- import { Link, Navigate, STUDIO_PROJECT_ROUTE, studioPath, useParams, useSearch, } from '@elytracms/studio/router';
3
+ import { Link, Navigate, STUDIO_PROJECT_ROUTE, useParams, useSearch } from '@elytracms/studio/router';
6
4
  import { matchStudioRoute } from '@elytracms/studio/router/match';
7
5
  import { studioSearch } from '@elytracms/studio/router/search';
8
6
  import { Button, Heading, Stack, Text } from '@elytracms/studio/ui';
9
7
  import { DataProvider } from '@elytracms/studio/lib/data';
10
- import { getInstantiatedKit } from '@elytracms/studio/lib/starters/kit-session';
11
8
  import { useStudio } from './studio-context';
12
9
  import { StudioShell } from './studio-shell';
13
- import { firstAvailableSection } from './sections';
14
- import { SectionSurface } from './section-surface';
15
- import { SectionStateView } from './section-state';
16
- import { SettingsForm } from './settings-form';
17
- import { AssetLibrary } from './assets/asset-library';
18
10
  import { AssetsProvider } from './assets/assets-store';
19
- import { BuilderProvider } from './builder/builder-store';
20
11
  import { CollabProvider } from './collab/collab-context';
21
- import { RoutesShell } from './routes/routes-shell';
22
- import { ExtensionsView } from './extensions/extensions-view';
23
- import { RegistryBrowser } from './components/registry-browser';
24
- import { ComponentsIndex } from './components/components-index';
25
- import { ComponentDetail } from './components/component-detail';
26
- import { CmsSessionGate, ProjectSessionProvider, useProjectSession, } from './project-session';
27
- import { CmsProvider, useCms } from './cms/cms-store';
28
- import { CollectionsSidebar } from './cms/collections-sidebar';
29
- import { CmsIndex } from './cms/cms-index';
30
- import { CollectionTable } from './cms/collection-table';
31
- import { DetailEditor } from './cms/detail-editor';
32
- import { OperationErrorBanner } from './operation-error';
12
+ import { ProjectSessionProvider } from './project-session';
13
+ import { firstAvailableModule, flatPath } from './app-views/module';
14
+ import { useAppViews } from './app-views/default-modules';
33
15
  /**
34
16
  * EC-223 Stage 6 (catch-all routing): the studio's per-project route component.
35
17
  *
@@ -43,10 +25,9 @@ import { OperationErrorBanner } from './operation-error';
43
25
  *
44
26
  * `projectId` stays a real route param (the `$projectId` segment); the trailing
45
27
  * pathname (`_splat`) is matched against the engine route table
46
- * (`matchStudioRoute`) and the deeper ids (collection / document / component)
47
- * flow down as props. Every screen already takes its ids as props, so this
48
- * collapse is mechanical — the package never read deep params through the router;
49
- * the disappearing route-file wrappers did.
28
+ * (`matchStudioRoute`) into a module id + segments, and the active module (EC-239)
29
+ * resolves the screen. Every surface already takes its ids as props, so the
30
+ * resolver just hands the module its parsed deep-link.
50
31
  */
51
32
  export function StudioProjectScreen() {
52
33
  const params = useParams({ strict: false });
@@ -71,147 +52,47 @@ export function StudioProjectScreen() {
71
52
  const search = studioSearch(rawSearch);
72
53
  return (_jsx(StudioShell, { project: project, children: _jsx(ProjectSessionProvider, { projectId: project.id, children: _jsx(ProjectProviders, { project: project, children: _jsx(StudioSectionResolver, { project: project, match: match, search: search }) }) }, project.id) }));
73
54
  }
74
- /** The per-project provider stack, mounted once the session source is known. */
55
+ /**
56
+ * The per-project provider stack, mounted once the session source is known.
57
+ *
58
+ * EC-287: there is no project-level builder graph anymore — layouts are code and
59
+ * the DB holds content only. The block-field canvas mounts its OWN scoped
60
+ * `BuilderProvider` per document (`composition-field-editor`), so no graph is
61
+ * threaded here. `CollabProvider` stays as the canvas's session-scoped
62
+ * presence/perspective store, seeded graph-lessly.
63
+ */
75
64
  function ProjectProviders({ project, children }) {
76
- const { session } = useProjectSession();
77
- // A project created from a starter kit (EC-119) opens on the kit's graph —
78
- // but only until something is persisted: the hydrated revision wins.
79
- const kit = getInstantiatedKit(project.id);
80
- const graph = session?.graph ?? kit?.graph ?? validSampleGraph;
81
65
  return (
82
- // Key by project id so the session data sources + graph reset when switching projects.
83
- // One canonical graph per project session: the Builder canvas and its Content tab
84
- // (bindings, repeating sections, template pages) read the same live graph. The data
85
- // store hydrates/persists this project's `cms-data-sources` records.
86
- _jsx(DataProvider, { projectId: project.id, children: _jsx(BuilderProvider, { graph: graph, projectId: project.id, children: _jsx(AssetsProvider, { projectId: project.id, children: _jsx(CollabProvider, { projectId: project.id, graph: graph, publish: session?.publish, children: children }, project.id) }, project.id) }, project.id) }, project.id));
66
+ // Key by project id so the session data sources reset when switching projects.
67
+ // The data store hydrates/persists this project's `cms-data-sources` records.
68
+ _jsx(DataProvider, { projectId: project.id, children: _jsx(AssetsProvider, { projectId: project.id, children: _jsx(CollabProvider, { projectId: project.id, children: children }, project.id) }, project.id) }, project.id));
87
69
  }
88
70
  /**
89
- * The URL→screen switch (EC-223 Stage 6) — what the per-section `<Outlet />`s
90
- * used to resolve. Renders inside the shell + provider stack, so every screen
91
- * sees the same live graph, CMS store, and breadcrumb context the file routes
92
- * gave it. `index` and `builder` are redirect-only screens, kept here (rather
93
- * than short-circuited before the shell) because the retired routes nested under
94
- * the project layout too — they mounted the stack and then navigated, exact
95
- * parity.
71
+ * The URL→screen switch (EC-223 Stage 6), now driven by the app-view module
72
+ * registry (EC-239). The bare project URL redirects to the first available
73
+ * module; otherwise the matched module id is resolved from the registry (defaults
74
+ * + `config.appViews`) and its `parsePath` interprets the trailing segments into a
75
+ * deep-link the module renders. An unknown module id or an unparseable path is a
76
+ * not-found; module CONFIG problems (duplicate/invalid `appViews`) surface as
77
+ * visible issues in the shell, never silently. Renders inside the shell + provider
78
+ * stack, so every surface sees the same live CMS store and breadcrumb context.
96
79
  */
97
80
  function StudioSectionResolver({ project, match, search, }) {
98
81
  const projectId = project.id;
99
- switch (match.section) {
100
- case 'index':
101
- // Bare `/$projectId` → the project's first available section.
102
- return (_jsx(Navigate, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: firstAvailableSection(project) }, replace: true }));
103
- case 'settings':
104
- return (_jsx(SectionSurface, { project: project, section: "settings", forcedState: search.state, children: _jsx(SettingsForm, { project: project }) }));
105
- case 'assets':
106
- return (_jsx(SectionSurface, { project: project, section: "assets", forcedState: search.state, children: _jsx(AssetLibrary, { projectId: projectId, focusAssetId: search.asset }) }));
107
- case 'routes':
108
- // Deterministic forced states (?state=) for the surface (EC-036).
109
- return search.state && search.state !== 'working' ? (_jsx(SectionStateView, { state: search.state, title: "Routes" })) : (_jsx(RoutesShell, { project: project }));
110
- case 'extensions':
111
- return search.state && search.state !== 'working' ? (_jsx(SectionStateView, { state: search.state, title: "Extensions" })) : (_jsx(ExtensionsView, {}));
112
- case 'builder':
113
- // Retired surface (EC-187 slice 6): a page is a document in the `page`
114
- // collection. A `?page=<id>` deep link lands on that document's editor;
115
- // otherwise the page-collection list. The page-graph-only params have no
116
- // home here and are dropped.
117
- return (_jsx(Navigate, { to: STUDIO_PROJECT_ROUTE, params: {
118
- projectId,
119
- _splat: search.page
120
- ? studioPath.cmsDocument('page', search.page)
121
- : studioPath.cmsCollection('page'),
122
- }, replace: true }));
123
- case 'components':
124
- return (_jsx(ComponentsScreen, { projectId: projectId, componentId: match.componentId, state: search.state }));
125
- case 'cms':
126
- return (_jsx(CmsScreen, { project: project, collectionId: match.collectionId, documentId: match.documentId, state: search.state, field: search.field }));
127
- case 'not-found':
128
- default:
129
- return _jsx(StudioRouteNotFound, {});
130
- }
131
- }
132
- /**
133
- * Components section (EC-050): a registry-browser sidebar beside the active
134
- * screen — the index (no selection) or a component's detail. Forced states
135
- * (?state=) replace the whole surface.
136
- */
137
- function ComponentsScreen({ projectId, componentId, state, }) {
138
- if (state && state !== 'working') {
139
- return _jsx(SectionStateView, { state: state, title: "Components" });
140
- }
141
- 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 })) })] }));
142
- }
143
- /**
144
- * CMS section: hydrates the persisted (or kit-seeded) collections + documents,
145
- * then renders the collections sidebar beside the active screen (index, a
146
- * collection table, or a document editor). Forced states (?state=) replace the
147
- * whole surface before the provider mounts.
148
- */
149
- function CmsScreen({ project, collectionId, documentId, state, field, }) {
150
- const projectId = project.id;
151
- if (state && state !== 'working') {
152
- return _jsx(SectionStateView, { state: state, title: "CMS" });
153
- }
154
- // A project created from a starter kit (EC-119) opens the CMS on the kit's schemas + content.
155
- const kit = getInstantiatedKit(projectId);
156
- return (
157
- // EC-177 gap 1: in Convex mode every CMS mount loads the CURRENT persisted
158
- // schema + documents (fixtures only when nothing is persisted — first run).
159
- // Playground renders synchronously with `null` → fixture seeds, as before.
160
- _jsx(CmsSessionGate, { children: (cms) => (_jsx(CmsProvider, { projectId: projectId, initialCollections: cms?.collections ?? kit?.collections, initialDocuments: cms?.documents ?? kit?.documents, initialPublishedKeys: cms?.publishedKeys, initialLocaleConfig: cms
161
- ? // Hydrated sessions follow the project's persisted locale settings.
162
- { default: project.settings.defaultLocale, locales: [...project.settings.locales] }
163
- : 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 }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: _jsx(CmsContent, { projectId: projectId, collectionId: collectionId, documentId: documentId, field: field }) })] })] }) }, projectId)) }));
164
- }
165
- /** Resolves the active CMS screen inside the provider (needs the live store). */
166
- function CmsContent({ projectId, collectionId, documentId, field, }) {
167
- const cms = useCms();
168
- if (collectionId === null)
169
- return _jsx(CmsIndex, { projectId: projectId });
170
- if (documentId === null) {
171
- const collection = cms.getCollection(collectionId);
172
- // EC-274: a singleton is a UI lens — at the collection URL it opens its one
173
- // document directly *while in its expected shape*, but degrades to the list
174
- // (with a "for review" lane) on drift. SingletonGate decides per the data.
175
- if (collection && isSingleton(collection)) {
176
- return _jsx(SingletonGate, { projectId: projectId, collectionId: collectionId });
177
- }
178
- return _jsx(CollectionTable, { projectId: projectId, collectionId: collectionId });
82
+ const appViews = useAppViews();
83
+ if (match.section === 'index') {
84
+ // Bare `/$projectId` → the project's first available module.
85
+ return (_jsx(Navigate, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: firstAvailableModule(appViews.modules, project) }, replace: true }));
179
86
  }
180
- return (_jsx(DetailEditor, { projectId: projectId, collectionId: collectionId, documentId: documentId, focusPath: field }));
181
- }
182
- /**
183
- * EC-274 — the singleton lens gate. At a singleton collection's URL, resolve its
184
- * lone document and open it directly; on drift (> 1 document) fall back to the
185
- * list with the "for review" lane so an editor can resolve the conflict. The one
186
- * document keeps a normal id — nothing here names a literal document id.
187
- */
188
- function SingletonGate({ projectId, collectionId }) {
189
- const cms = useCms();
190
- const collection = cms.getCollection(collectionId);
191
- const docs = cms.documentsOf(collectionId);
192
- const resolution = collection
193
- ? resolveSingletonDocument(collection, docs)
194
- : { kind: 'empty' };
195
- // Materialize the lone document on first visit (an empty draft, persisted like
196
- // any create). Idempotent in the store; only fires while genuinely empty.
197
- useEffect(() => {
198
- if (resolution.kind === 'empty')
199
- cms.ensureSingletonDocument(collectionId);
200
- }, [resolution.kind, cms, collectionId]);
201
- if (resolution.kind === 'ready') {
202
- return (_jsx(DetailEditor, { projectId: projectId, collectionId: collectionId, documentId: resolution.doc.id }));
203
- }
204
- if (resolution.kind === 'drift') {
205
- return _jsx(CollectionTable, { projectId: projectId, collectionId: collectionId });
206
- }
207
- return (_jsx("div", { className: "grid h-full place-items-center p-8", children: _jsxs(Text, { size: "sm", tone: "muted", children: ["Opening ", collection?.form?.label ?? collectionId, "\u2026"] }) }));
208
- }
209
- /** Failed/denied persisted CMS mutations are always visible (EC-141). */
210
- function CmsPersistErrorBanner() {
211
- const cms = useCms();
212
- if (!cms.persistError)
213
- return null;
214
- return (_jsx("div", { className: "shrink-0 border-b border-border p-2", children: _jsx(OperationErrorBanner, { error: cms.persistError, onDismiss: cms.clearPersistError }) }));
87
+ const module = appViews.get(match.moduleId);
88
+ if (!module)
89
+ return _jsx(StudioRouteNotFound, {});
90
+ // A flat module (no `parsePath`) accepts only the bare path; a nested one
91
+ // (CMS/components) interprets its segments. `null` → an over-deep path → not-found.
92
+ const deepLink = (module.parsePath ?? flatPath)(match.segments);
93
+ if (deepLink === null)
94
+ return _jsx(StudioRouteNotFound, {});
95
+ return _jsx(_Fragment, { children: module.render({ project, projectId, deepLink, search }) });
215
96
  }
216
97
  function ProjectNotFound() {
217
98
  return (_jsx("div", { className: "grid min-h-dvh place-items-center p-8", children: _jsxs(Stack, { gap: "3", align: "center", className: "max-w-sm text-center", children: [_jsx(Heading, { size: "display", children: "Project not found" }), _jsx(Text, { tone: "muted", children: "This project doesn\u2019t exist or was deleted. It may only have existed in this session." }), _jsx(Button, { asChild: true, children: _jsx(Link, { to: "/", children: "Back to projects" }) })] }) }));