@elytracms/studio 0.0.86 → 0.0.87

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 (203) hide show
  1. package/convex/asset-revalidate.test.ts +186 -0
  2. package/convex/assets.ts +53 -19
  3. package/convex/cms.ts +22 -0
  4. package/convex/delivery.ts +33 -116
  5. package/convex/functions.ts +3 -3
  6. package/convex/rail-write-parity.test.ts +180 -0
  7. package/convex/redirects.ts +33 -11
  8. package/convex/revalidate.ts +7 -5
  9. package/convex/triggers.ts +53 -3
  10. package/dist/components/app-views/default-modules.d.ts +20 -20
  11. package/dist/components/app-views/default-modules.js +27 -48
  12. package/dist/components/app-views/default-modules.js.map +1 -1
  13. package/dist/components/app-views/studio-config.d.ts +5 -6
  14. package/dist/components/app-views/studio-config.js.map +1 -1
  15. package/dist/components/assets/asset-metadata.d.ts +28 -19
  16. package/dist/components/assets/asset-metadata.js +24 -31
  17. package/dist/components/assets/asset-metadata.js.map +1 -1
  18. package/dist/components/assets/asset-selection.d.ts +24 -5
  19. package/dist/components/assets/asset-selection.js +39 -9
  20. package/dist/components/assets/asset-selection.js.map +1 -1
  21. package/dist/components/assets/assets-collection-view-source.d.ts +17 -14
  22. package/dist/components/assets/assets-collection-view-source.js +25 -31
  23. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  24. package/dist/components/builder/blocks-panel.js +3 -3
  25. package/dist/components/builder/blocks-panel.js.map +1 -1
  26. package/dist/components/builder/builder-store.d.ts +25 -1
  27. package/dist/components/builder/builder-store.js +3 -2
  28. package/dist/components/builder/builder-store.js.map +1 -1
  29. package/dist/components/builder/canvas-chrome-css.js +0 -17
  30. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  31. package/dist/components/builder/canvas-chrome.d.ts +10 -12
  32. package/dist/components/builder/canvas-chrome.js +12 -13
  33. package/dist/components/builder/canvas-chrome.js.map +1 -1
  34. package/dist/components/builder/canvas-dnd.d.ts +8 -0
  35. package/dist/components/builder/canvas-dnd.js +5 -3
  36. package/dist/components/builder/canvas-dnd.js.map +1 -1
  37. package/dist/components/builder/canvas-render-context.d.ts +12 -24
  38. package/dist/components/builder/canvas-render-context.js +15 -44
  39. package/dist/components/builder/canvas-render-context.js.map +1 -1
  40. package/dist/components/builder/canvas.js +5 -11
  41. package/dist/components/builder/canvas.js.map +1 -1
  42. package/dist/components/builder/clipboard-actions.d.ts +14 -6
  43. package/dist/components/builder/clipboard-actions.js +29 -20
  44. package/dist/components/builder/clipboard-actions.js.map +1 -1
  45. package/dist/components/builder/composition-field-editor.d.ts +19 -26
  46. package/dist/components/builder/composition-field-editor.js +30 -52
  47. package/dist/components/builder/composition-field-editor.js.map +1 -1
  48. package/dist/components/builder/insert-context.d.ts +3 -1
  49. package/dist/components/builder/insert-context.js +15 -10
  50. package/dist/components/builder/insert-context.js.map +1 -1
  51. package/dist/components/builder/page-tree.d.ts +29 -0
  52. package/dist/components/builder/page-tree.js +56 -58
  53. package/dist/components/builder/page-tree.js.map +1 -1
  54. package/dist/components/builder/reuse.js +5 -2
  55. package/dist/components/builder/reuse.js.map +1 -1
  56. package/dist/components/cms/asset-field.js +20 -32
  57. package/dist/components/cms/asset-field.js.map +1 -1
  58. package/dist/components/cms/cms-composition-validators.js +11 -2
  59. package/dist/components/cms/cms-composition-validators.js.map +1 -1
  60. package/dist/components/cms/editable-cell.js +5 -2
  61. package/dist/components/cms/editable-cell.js.map +1 -1
  62. package/dist/components/cms/use-document-versions.js +1 -0
  63. package/dist/components/cms/use-document-versions.js.map +1 -1
  64. package/dist/components/cms/versions-model.d.ts +0 -12
  65. package/dist/components/cms/versions-model.js +3 -15
  66. package/dist/components/cms/versions-model.js.map +1 -1
  67. package/dist/components/components/component-detail.js +1 -1
  68. package/dist/components/components/component-detail.js.map +1 -1
  69. package/dist/components/components/transfer-dialog.js +2 -4
  70. package/dist/components/components/transfer-dialog.js.map +1 -1
  71. package/dist/components/components/transfer.d.ts +13 -10
  72. package/dist/components/components/transfer.js +21 -19
  73. package/dist/components/components/transfer.js.map +1 -1
  74. package/dist/components/project-session.d.ts +9 -0
  75. package/dist/components/project-session.js +1 -1
  76. package/dist/components/project-session.js.map +1 -1
  77. package/dist/components/redirects/redirects-collection-view-source.d.ts +17 -2
  78. package/dist/components/redirects/redirects-collection-view-source.js +20 -6
  79. package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
  80. package/dist/components/section-surface.js +3 -2
  81. package/dist/components/section-surface.js.map +1 -1
  82. package/dist/components/settings-form.js +1 -2
  83. package/dist/components/settings-form.js.map +1 -1
  84. package/dist/components/studio-context.js +1 -18
  85. package/dist/components/studio-context.js.map +1 -1
  86. package/dist/components/studio-project-screen.js +3 -5
  87. package/dist/components/studio-project-screen.js.map +1 -1
  88. package/dist/lib/ai/apply-content.js +73 -29
  89. package/dist/lib/ai/apply-content.js.map +1 -1
  90. package/dist/lib/ai/constraints.d.ts +35 -0
  91. package/dist/lib/ai/constraints.js +104 -4
  92. package/dist/lib/ai/constraints.js.map +1 -1
  93. package/dist/lib/ai/route.js +4 -1
  94. package/dist/lib/ai/route.js.map +1 -1
  95. package/dist/lib/ai/shape.d.ts +3 -2
  96. package/dist/lib/ai/shape.js +16 -24
  97. package/dist/lib/ai/shape.js.map +1 -1
  98. package/dist/lib/ai/types.d.ts +8 -0
  99. package/dist/lib/ai/types.js.map +1 -1
  100. package/dist/lib/ai/update-fields.js +33 -12
  101. package/dist/lib/ai/update-fields.js.map +1 -1
  102. package/dist/lib/assets/asset-placements.d.ts +35 -0
  103. package/dist/lib/assets/asset-placements.js +74 -0
  104. package/dist/lib/assets/asset-placements.js.map +1 -0
  105. package/dist/lib/assets/backfill-thumbs.d.ts +9 -8
  106. package/dist/lib/assets/backfill-thumbs.js +13 -25
  107. package/dist/lib/assets/backfill-thumbs.js.map +1 -1
  108. package/dist/lib/clipboard/paste-issues.d.ts +17 -15
  109. package/dist/lib/clipboard/paste-issues.js +8 -45
  110. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  111. package/dist/lib/clipboard/paste-target.d.ts +25 -1
  112. package/dist/lib/clipboard/paste-target.js +13 -6
  113. package/dist/lib/clipboard/paste-target.js.map +1 -1
  114. package/dist/lib/cms/published-documents.js +1 -0
  115. package/dist/lib/cms/published-documents.js.map +1 -1
  116. package/dist/lib/cms-views/view-model.d.ts +3 -3
  117. package/dist/lib/data/index.d.ts +0 -3
  118. package/dist/lib/data/index.js +4 -6
  119. package/dist/lib/data/index.js.map +1 -1
  120. package/dist/lib/dnd/drop-rules.d.ts +45 -2
  121. package/dist/lib/dnd/drop-rules.js +64 -9
  122. package/dist/lib/dnd/drop-rules.js.map +1 -1
  123. package/dist/lib/engine-version.d.ts +1 -1
  124. package/dist/lib/engine-version.js +1 -1
  125. package/dist/lib/engine-version.js.map +1 -1
  126. package/dist/lib/i18n/messages.de.js +4 -56
  127. package/dist/lib/i18n/messages.de.js.map +1 -1
  128. package/dist/lib/i18n/messages.en.d.ts +4 -53
  129. package/dist/lib/i18n/messages.en.js +8 -57
  130. package/dist/lib/i18n/messages.en.js.map +1 -1
  131. package/dist/lib/locale/fallback.js +8 -2
  132. package/dist/lib/locale/fallback.js.map +1 -1
  133. package/dist/lib/locale/index.d.ts +0 -1
  134. package/dist/lib/locale/index.js +0 -1
  135. package/dist/lib/locale/index.js.map +1 -1
  136. package/dist/lib/locale/locale-store.d.ts +10 -3
  137. package/dist/lib/locale/locale-store.js +12 -5
  138. package/dist/lib/locale/locale-store.js.map +1 -1
  139. package/dist/lib/operations/session-hydration.d.ts +9 -5
  140. package/dist/lib/operations/session-hydration.js +10 -10
  141. package/dist/lib/operations/session-hydration.js.map +1 -1
  142. package/dist/lib/persistence/convex-adapter.js +63 -75
  143. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  144. package/dist/lib/persona/labels.d.ts +0 -2
  145. package/dist/lib/persona/labels.js +1 -7
  146. package/dist/lib/persona/labels.js.map +1 -1
  147. package/dist/lib/registry/index.d.ts +4 -2
  148. package/dist/lib/registry/index.js +26 -21
  149. package/dist/lib/registry/index.js.map +1 -1
  150. package/dist/lib/registry/project-components.js +0 -3
  151. package/dist/lib/registry/project-components.js.map +1 -1
  152. package/dist/lib/routes/route-issues.js +4 -5
  153. package/dist/lib/routes/route-issues.js.map +1 -1
  154. package/dist/lib/theme.js +3 -2
  155. package/dist/lib/theme.js.map +1 -1
  156. package/dist/lib/webhooks/tags.js +11 -14
  157. package/dist/lib/webhooks/tags.js.map +1 -1
  158. package/dist/lib/workspace/index.d.ts +1 -1
  159. package/dist/lib/workspace/index.js +1 -1
  160. package/dist/lib/workspace/index.js.map +1 -1
  161. package/dist/previews/enumerate.d.ts +27 -9
  162. package/dist/previews/enumerate.js +27 -6
  163. package/dist/previews/enumerate.js.map +1 -1
  164. package/dist/previews/manifest-format.d.ts +9 -0
  165. package/dist/previews/manifest-format.js.map +1 -1
  166. package/dist/router/match.d.ts +0 -2
  167. package/dist/router/match.js +0 -2
  168. package/dist/router/match.js.map +1 -1
  169. package/dist/ui/badge.d.ts +1 -1
  170. package/package.json +2 -2
  171. package/src/styles/studio.css +5 -4
  172. package/src/styles/tokens.css +72 -55
  173. package/convex/content.ts +0 -291
  174. package/dist/components/collab/collab-context.d.ts +0 -27
  175. package/dist/components/collab/collab-context.js +0 -39
  176. package/dist/components/collab/collab-context.js.map +0 -1
  177. package/dist/components/collab/collab-seed.d.ts +0 -15
  178. package/dist/components/collab/collab-seed.js +0 -20
  179. package/dist/components/collab/collab-seed.js.map +0 -1
  180. package/dist/components/extensions/extensions-view.d.ts +0 -7
  181. package/dist/components/extensions/extensions-view.js +0 -127
  182. package/dist/components/extensions/extensions-view.js.map +0 -1
  183. package/dist/lib/bindings/section-bindings.d.ts +0 -24
  184. package/dist/lib/bindings/section-bindings.js +0 -9
  185. package/dist/lib/bindings/section-bindings.js.map +0 -1
  186. package/dist/lib/data/data-store.d.ts +0 -98
  187. package/dist/lib/data/data-store.js +0 -211
  188. package/dist/lib/data/data-store.js.map +0 -1
  189. package/dist/lib/data/providers.d.ts +0 -143
  190. package/dist/lib/data/providers.js +0 -255
  191. package/dist/lib/data/providers.js.map +0 -1
  192. package/dist/lib/data/sample-capture.d.ts +0 -30
  193. package/dist/lib/data/sample-capture.js +0 -70
  194. package/dist/lib/data/sample-capture.js.map +0 -1
  195. package/dist/lib/data/sources-store.d.ts +0 -42
  196. package/dist/lib/data/sources-store.js +0 -120
  197. package/dist/lib/data/sources-store.js.map +0 -1
  198. package/dist/lib/locale/binding-fallback.d.ts +0 -35
  199. package/dist/lib/locale/binding-fallback.js +0 -73
  200. package/dist/lib/locale/binding-fallback.js.map +0 -1
  201. package/dist/lib/starters/kit-session.d.ts +0 -5
  202. package/dist/lib/starters/kit-session.js +0 -17
  203. package/dist/lib/starters/kit-session.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/registry/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { ComponentRegistry, manifestPropFields } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport type { PropFieldLookup } from '@elytracms/core/cms-core'\nimport { basePrimitives, getImplementation } from '@elytracms/core/runtime-renderer'\nimport type { ComponentImplementations, RenderContext } from '@elytracms/core/runtime-renderer'\nimport { getActiveProjectSlug, resolveDeploymentComponents } from '../workspace'\nimport { projectComponentImplementations, projectComponentManifests } from './project-components'\n\n/**\n * The Studio's view of a project's component registry (brief §7.2, §8.3): the\n * platform primitives plus the project's own components, with their\n * implementations for live previews. Deterministic singleton, built in-process\n * from code-owned manifests — component manifests live in TS next to the\n * components, so local/CI/browser runs need no backend.\n */\nexport interface StudioRegistry {\n registry: ComponentRegistry\n implementations: ComponentImplementations\n}\n\n/**\n * EC-194/EC-208/EC-223: the consuming repo's OWN real components, INJECTED through\n * config-as-code (the mount contract). The active project's `components` field holds\n * the live (manifest + implementation) pairs the consumer's `elytra.config.ts` handed\n * in by dependency injection — for Aurora these live in\n * `apps/example-site/components/marketing` (Next-free, so they render identically in\n * the studio canvas and the host). No `@site` alias and no static codegen seam: the\n * registry reads the live set off the resolved config. Absent any, the studio falls\n * back to the built-in demo fixtures (golden path).\n */\nfunction projectComponents(): {\n manifests: ComponentManifest[]\n implementations: ComponentImplementations\n} {\n const components = resolveDeploymentComponents()\n if (components && components.length > 0) {\n return {\n manifests: components.map((c) => c.manifest),\n implementations: Object.fromEntries(\n components.map((c) => [c.manifest.id, c.implementation]),\n ) as ComponentImplementations,\n }\n }\n return { manifests: projectComponentManifests, implementations: projectComponentImplementations }\n}\n\n/** Compose a StudioRegistry from the base primitives plus a project component set. */\nfunction buildStudioRegistry(project: {\n manifests: ComponentManifest[]\n implementations: ComponentImplementations\n}): StudioRegistry {\n const base = basePrimitives()\n // Inline-editable opt-ins (EC-158) live directly on the base-primitive\n // manifests (`Heading.text`, `Text.value`) in `@elytracms/core/runtime-renderer`.\n return {\n registry: new ComponentRegistry([...base.manifests, ...project.manifests]),\n implementations: { ...base.implementations, ...project.implementations },\n }\n}\n\nlet cached: StudioRegistry | null = null\nlet cachedSlug: string | null = null\n\nexport function getStudioRegistry(): StudioRegistry {\n // Re-derive when the active project changes — different projects may register\n // different component libraries (per-project registry, AD-8).\n const slug = getActiveProjectSlug()\n if (cached && cachedSlug === slug) return cached\n cached = buildStudioRegistry(projectComponents())\n cachedSlug = slug\n return cached\n}\n\n/**\n * The built-in demo fixtures as a StudioRegistry, independent of the active\n * project's `components` config ref. This is the golden-path component set\n * (`project.MarketingHero`, `FeatureCard`, `PricingCallout`, `LegacyBanner`) used\n * when a project declares no ref — and the deterministic registry that blocks /\n * clipboard / thumbnail tests pin to, so they assert against a known component set\n * regardless of which library `elytra.config.ts` happens to select (EC-194).\n */\nexport function fixtureStudioRegistry(): StudioRegistry {\n return buildStudioRegistry({\n manifests: projectComponentManifests,\n implementations: projectComponentImplementations,\n })\n}\n\n/**\n * Adapt the live registry to the cms-core {@link PropFieldLookup} contract (EC-277):\n * a component id → its prop field-defs, name-injected. Threaded into the studio's\n * write-time self-typing lens so relation/asset props inside compositions are\n * self-typed on write (\"schema leads, component adopts\"). Manifests are code (AD-11);\n * persistence/cms-core never depend on the registry — the studio supplies this shape.\n */\nexport function studioManifestLookup(): PropFieldLookup {\n const { registry } = getStudioRegistry()\n return (componentId) => {\n const manifest = registry.getManifest(componentId)\n if (!manifest) return undefined\n return Object.fromEntries(manifestPropFields(manifest).map((field) => [field.name, field]))\n }\n}\n\n/** A RenderContext for fixture previews (bindings resolve to undefined). */\nexport function previewContext(studio: StudioRegistry): RenderContext {\n return {\n registry: studio.registry,\n implementations: studio.implementations,\n resolveBinding: () => undefined,\n }\n}\n\n/**\n * Per-component status (brief §4.11, §8.3, EC-055):\n * - `invalid`: the registry reported a structured issue for this id.\n * - `incompatible`: a manifest exists but no implementation is wired up.\n * - `valid`: healthy.\n */\nexport type ComponentStatus = 'valid' | 'incompatible' | 'invalid'\n\nexport function componentStatus(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n): ComponentStatus {\n if (studio.registry.issues.some((i) => i.componentId === manifest.id)) return 'invalid'\n if (!getImplementation(studio.implementations, manifest.id)) return 'incompatible'\n return 'valid'\n}\n\nexport interface RegistryHealth {\n total: number\n valid: number\n incompatible: number\n invalid: number\n}\n\nexport function registryHealth(studio: StudioRegistry): RegistryHealth {\n const all = studio.registry.list()\n const health: RegistryHealth = { total: all.length, valid: 0, incompatible: 0, invalid: 0 }\n for (const m of all) health[componentStatus(studio, m)] += 1\n return health\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/registry/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { ComponentRegistry, manifestPropFields } from '@elytracms/core/component-registry'\nimport type { ComponentIssue, ComponentManifest } from '@elytracms/core/component-registry'\nimport type { PropFieldLookup } from '@elytracms/core/cms-core'\nimport { basePrimitives, getImplementation, registerImplementations } from '@elytracms/core/runtime-renderer'\nimport type { ComponentImplementations, RenderContext } from '@elytracms/core/runtime-renderer'\nimport { getActiveProjectSlug, resolveDeploymentComponents } from '../workspace'\nimport { projectComponentImplementations, projectComponentManifests } from './project-components'\n\n/**\n * The Studio's view of a project's component registry (brief §7.2, §8.3): the\n * platform primitives plus the project's own components, with their\n * implementations for live previews. Deterministic singleton, built in-process\n * from code-owned manifests — component manifests live in TS next to the\n * components, so local/CI/browser runs need no backend.\n */\nexport interface StudioRegistry {\n registry: ComponentRegistry\n implementations: ComponentImplementations\n}\n\n/**\n * EC-194/EC-208/EC-223: the consuming repo's OWN real components, INJECTED through\n * config-as-code (the mount contract). The active project's `components` field holds\n * the live (manifest + implementation) pairs the consumer's `elytra.config.ts` handed\n * in by dependency injection — for Aurora these live in\n * `apps/example-site/components/marketing` (Next-free, so they render identically in\n * the studio canvas and the host). No `@site` alias and no static codegen seam: the\n * registry reads the live set off the resolved config. Absent any, the studio falls\n * back to the built-in demo fixtures (golden path).\n */\n/** A project component set, ready to layer over the base primitives. */\ninterface ProjectComponentSet {\n manifests: ComponentManifest[]\n implementations: ComponentImplementations\n /** Registration issues the registry itself cannot see (`invalid-implementation`, EC-555). */\n issues: readonly ComponentIssue[]\n}\n\n/**\n * EC-555: the config's implementations are `unknown` (`HostComponentEntry`), so\n * they go through core's one registration step — the same one the Next host\n * uses (EC-541) — instead of a cast: a non-component is withheld (its nodes\n * take the renderer's missing-component path, never `createElement`) and\n * recorded as an `invalid-implementation` issue. The fixture set below is\n * engine-owned and typed as components, so it needs no runtime check.\n */\nfunction projectComponents(): ProjectComponentSet {\n const components = resolveDeploymentComponents()\n if (components && components.length > 0) {\n const { implementations, issues } = registerImplementations(components)\n return { manifests: components.map((c) => c.manifest), implementations, issues }\n }\n return {\n manifests: projectComponentManifests,\n implementations: projectComponentImplementations,\n issues: [],\n }\n}\n\n/** Compose a StudioRegistry from the base primitives plus a project component set. */\nfunction buildStudioRegistry(project: ProjectComponentSet): StudioRegistry {\n const base = basePrimitives()\n // Inline-editable opt-ins (EC-158) live directly on the base-primitive\n // manifests (`Heading.text`, `Text.value`) in `@elytracms/core/runtime-renderer`.\n const registry = new ComponentRegistry([...base.manifests, ...project.manifests])\n // EC-555: implementation issues join the registry's OWN list, because that is\n // the one list every studio status reader consults — `componentStatus` below\n // (→ 'invalid'), the Components surface, and the blocks panel tile, which\n // prints each issue's message. A second list would be a second reader.\n registry.issues.push(...project.issues)\n return {\n registry,\n // Project wins over base by spread (a project entry under a `base.*` id\n // replaces the primitive; a WITHHELD one leaves the primitive in place).\n implementations: { ...base.implementations, ...project.implementations },\n }\n}\n\nlet cached: StudioRegistry | null = null\nlet cachedSlug: string | null = null\n\nexport function getStudioRegistry(): StudioRegistry {\n // Re-derive when the active project changes — different projects may register\n // different component libraries (per-project registry, AD-8).\n const slug = getActiveProjectSlug()\n if (cached && cachedSlug === slug) return cached\n cached = buildStudioRegistry(projectComponents())\n cachedSlug = slug\n return cached\n}\n\n/**\n * The built-in demo fixtures as a StudioRegistry, independent of the active\n * project's `components` config ref. This is the golden-path component set\n * (`project.MarketingHero`, `FeatureCard`, `PricingCallout`, `LegacyBanner`) used\n * when a project declares no ref — and the deterministic registry that blocks /\n * clipboard / thumbnail tests pin to, so they assert against a known component set\n * regardless of which library `elytra.config.ts` happens to select (EC-194).\n */\nexport function fixtureStudioRegistry(): StudioRegistry {\n return buildStudioRegistry({\n manifests: projectComponentManifests,\n implementations: projectComponentImplementations,\n issues: [],\n })\n}\n\n/**\n * Adapt the live registry to the cms-core {@link PropFieldLookup} contract (EC-277):\n * a component id → its prop field-defs, name-injected. Threaded into the studio's\n * write-time self-typing lens so relation/asset props inside compositions are\n * self-typed on write (\"schema leads, component adopts\"). Manifests are code (AD-11);\n * persistence/cms-core never depend on the registry — the studio supplies this shape.\n */\nexport function studioManifestLookup(): PropFieldLookup {\n const { registry } = getStudioRegistry()\n return (componentId) => {\n const manifest = registry.getManifest(componentId)\n if (!manifest) return undefined\n return Object.fromEntries(manifestPropFields(manifest).map((field) => [field.name, field]))\n }\n}\n\n/** A RenderContext for fixture previews (static props only; no asset/relation resolvers). */\nexport function previewContext(studio: StudioRegistry): RenderContext {\n return { registry: studio.registry, implementations: studio.implementations }\n}\n\n/**\n * Per-component status (brief §4.11, §8.3, EC-055):\n * - `invalid`: the registry reported a structured issue for this id — including\n * an `invalid-implementation` (EC-555: the entry HAS an implementation, it is\n * just not a component, so it is not merely missing).\n * - `incompatible`: a manifest exists but no implementation is wired up.\n * - `valid`: healthy.\n */\nexport type ComponentStatus = 'valid' | 'incompatible' | 'invalid'\n\nexport function componentStatus(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n): ComponentStatus {\n if (studio.registry.issues.some((i) => i.componentId === manifest.id)) return 'invalid'\n if (!getImplementation(studio.implementations, manifest.id)) return 'incompatible'\n return 'valid'\n}\n\nexport interface RegistryHealth {\n total: number\n valid: number\n incompatible: number\n invalid: number\n}\n\nexport function registryHealth(studio: StudioRegistry): RegistryHealth {\n const all = studio.registry.list()\n const health: RegistryHealth = { total: all.length, valid: 0, incompatible: 0, invalid: 0 }\n for (const m of all) health[componentStatus(studio, m)] += 1\n return health\n}\n"]}
@@ -39,7 +39,6 @@ const MarketingHeroManifest = defineComponent({
39
39
  },
40
40
  },
41
41
  slots: [],
42
- designTokens: ['color.primary', 'space.section', 'font.display'],
43
42
  fixtures: [
44
43
  { name: 'Default' },
45
44
  {
@@ -83,7 +82,6 @@ const FeatureCardManifest = defineComponent({
83
82
  },
84
83
  },
85
84
  slots: [],
86
- designTokens: ['color.surface', 'color.primary'],
87
85
  fixtures: [{ name: 'Default' }, { name: 'Accent', props: { tone: 'accent', title: 'AI inspectable' } }],
88
86
  });
89
87
  // --- project.PricingCallout ------------------------------------------------
@@ -109,7 +107,6 @@ const PricingCalloutManifest = defineComponent({
109
107
  featured: { type: 'boolean', default: false, form: { label: 'Featured' } },
110
108
  },
111
109
  slots: [],
112
- designTokens: ['color.primary', 'space.card'],
113
110
  fixtures: [{ name: 'Standard' }, { name: 'Featured', props: { featured: true, plan: 'Team', price: '$79' } }],
114
111
  });
115
112
  // --- project.LegacyBanner (intentionally has NO implementation) -------------
@@ -1 +1 @@
1
- {"version":3,"file":"project-components.js","sourceRoot":"","sources":["../../../src/lib/registry/project-components.tsx"],"names":[],"mappings":"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","sourcesContent":["import { defineComponent } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport type { ComponentType, ReactNode } from 'react'\n\n/**\n * Sample `project.*` components for the Studio Components surface (brief §4.2,\n * §8.3). These stand in for the project-specific components that are the center\n * of the product: each declares a full manifest (prop schema metadata, defaults,\n * inspector controls, binding permissions, slot rules, design-token hints, and\n * preview fixtures) and ships a self-contained implementation the fixture runner\n * renders. Deterministic — no time/random.\n */\n\ntype Props = Record<string, unknown>\nfunction str(value: unknown, fallback = ''): string {\n return typeof value === 'string' && value.length > 0 ? value : fallback\n}\n\n// --- project.MarketingHero -------------------------------------------------\n\nfunction MarketingHero(props: Props): ReactNode {\n const align = props.align === 'center' ? 'items-center text-center' : 'items-start text-left'\n return (\n <div className={`flex flex-col gap-3 rounded-xl border border-border bg-card p-8 ${align}`}>\n {str(props.eyebrow) ? (\n <span className=\"text-[0.7rem] font-semibold uppercase tracking-[0.14em] text-primary\">\n {str(props.eyebrow)}\n </span>\n ) : null}\n <h2 className=\"text-2xl font-semibold tracking-tight text-foreground\">\n {str(props.heading, 'Headline goes here')}\n </h2>\n {str(props.subheading) ? (\n <p className=\"max-w-prose text-sm text-muted-foreground\">{str(props.subheading)}</p>\n ) : null}\n {str(props.ctaLabel) ? (\n <span className=\"mt-1 inline-flex w-fit items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground\">\n {str(props.ctaLabel)}\n </span>\n ) : null}\n </div>\n )\n}\n\nconst MarketingHeroManifest = defineComponent({\n id: 'project.MarketingHero',\n namespace: 'project',\n title: 'Marketing Hero',\n description: 'A headline hero section with eyebrow, subheading, and a call to action.',\n category: 'marketing',\n props: {\n eyebrow: { type: 'text', default: 'New', form: { label: 'Eyebrow' } },\n heading: {\n type: 'text',\n default: 'Build code-first websites',\n validation: { min: 1 },\n form: { label: 'Heading' },\n },\n subheading: {\n type: 'text',\n default: 'One canonical graph powers editing, content, and export.',\n form: { label: 'Subheading', control: 'textarea' },\n },\n ctaLabel: { type: 'text', default: 'Get started', form: { label: 'CTA label' } },\n align: {\n type: 'select',\n default: 'left',\n options: [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n ],\n form: { label: 'Alignment' },\n },\n },\n slots: [],\n designTokens: ['color.primary', 'space.section', 'font.display'],\n fixtures: [\n { name: 'Default' },\n {\n name: 'Centered',\n props: { align: 'center', eyebrow: 'Launch', heading: 'Ship faster', ctaLabel: 'Try it' },\n },\n { name: 'Minimal', props: { eyebrow: '', subheading: '', ctaLabel: '', heading: 'Just a headline' } },\n ],\n})\n\n// --- project.FeatureCard ---------------------------------------------------\n\nfunction FeatureCard(props: Props): ReactNode {\n const accent = props.tone === 'accent'\n return (\n <div\n className={`flex flex-col gap-2 rounded-lg border p-5 ${\n accent ? 'border-primary/30 bg-primary/5' : 'border-border bg-card'\n }`}\n >\n <div className={`grid size-8 place-items-center rounded-md ${accent ? 'bg-primary text-primary-foreground' : 'bg-muted text-foreground'}`}>\n <span className=\"text-sm font-bold\">{str(props.title, 'F').charAt(0)}</span>\n </div>\n <h3 className=\"text-sm font-semibold text-foreground\">{str(props.title, 'Feature title')}</h3>\n <p className=\"text-xs text-muted-foreground\">{str(props.body, 'A short description of the feature.')}</p>\n </div>\n )\n}\n\nconst FeatureCardManifest = defineComponent({\n id: 'project.FeatureCard',\n namespace: 'project',\n title: 'Feature Card',\n description: 'A compact card highlighting a single feature.',\n category: 'marketing',\n props: {\n title: {\n type: 'text',\n default: 'Validation first',\n validation: { min: 1 },\n form: { label: 'Title' },\n },\n body: {\n type: 'text',\n default: 'Broken bindings and missing components are explicit, never silent.',\n form: { label: 'Body', control: 'textarea' },\n },\n tone: {\n type: 'select',\n default: 'default',\n options: [\n { value: 'default', label: 'Default' },\n { value: 'accent', label: 'Accent' },\n ],\n form: { label: 'Tone' },\n },\n },\n slots: [],\n designTokens: ['color.surface', 'color.primary'],\n fixtures: [{ name: 'Default' }, { name: 'Accent', props: { tone: 'accent', title: 'AI inspectable' } }],\n})\n\n// --- project.PricingCallout ------------------------------------------------\n\nfunction PricingCallout(props: Props): ReactNode {\n const featured = props.featured === true\n return (\n <div\n className={`flex flex-col gap-1 rounded-xl border p-6 ${\n featured ? 'border-primary bg-primary/5 shadow-md' : 'border-border bg-card'\n }`}\n >\n {featured ? (\n <span className=\"mb-1 w-fit rounded-full bg-primary px-2 py-0.5 text-[0.65rem] font-semibold uppercase tracking-wide text-primary-foreground\">\n Popular\n </span>\n ) : null}\n <span className=\"text-sm font-medium text-muted-foreground\">{str(props.plan, 'Plan')}</span>\n <div className=\"flex items-baseline gap-1\">\n <span className=\"text-3xl font-semibold tracking-tight text-foreground\">{str(props.price, '$0')}</span>\n <span className=\"text-sm text-muted-foreground\">/{str(props.period, 'mo')}</span>\n </div>\n </div>\n )\n}\n\nconst PricingCalloutManifest = defineComponent({\n id: 'project.PricingCallout',\n namespace: 'project',\n title: 'Pricing Callout',\n description: 'A pricing plan callout with an optional featured emphasis.',\n category: 'commerce',\n props: {\n plan: {\n type: 'text',\n default: 'Pro',\n validation: { min: 1 },\n form: { label: 'Plan' },\n },\n price: { type: 'text', default: '$29', form: { label: 'Price' } },\n period: { type: 'text', default: 'mo', form: { label: 'Period' } },\n featured: { type: 'boolean', default: false, form: { label: 'Featured' } },\n },\n slots: [],\n designTokens: ['color.primary', 'space.card'],\n fixtures: [{ name: 'Standard' }, { name: 'Featured', props: { featured: true, plan: 'Team', price: '$79' } }],\n})\n\n// --- project.LegacyBanner (intentionally has NO implementation) -------------\n//\n// Registered manifest with no implementation — exercises the \"incompatible /\n// missing implementation\" component status (EC-055) and the renderer's\n// missing-component fallback (brief §4.11).\n\nconst LegacyBannerManifest = defineComponent({\n id: 'project.LegacyBanner',\n namespace: 'project',\n title: 'Legacy Banner',\n description: 'A deprecated banner whose implementation has not been wired up.',\n category: 'marketing',\n props: {\n message: { type: 'text', default: 'Deprecated', form: { label: 'Message' } },\n },\n slots: [],\n fixtures: [{ name: 'Default' }],\n})\n\n/** Project component manifests (including the deliberately-unimplemented one). */\nexport const projectComponentManifests: ComponentManifest[] = [\n MarketingHeroManifest,\n FeatureCardManifest,\n PricingCalloutManifest,\n LegacyBannerManifest,\n]\n\n/** Implementations for the project components — note `LegacyBanner` is absent. */\nexport const projectComponentImplementations: Record<string, ComponentType<Props>> = {\n 'project.MarketingHero': MarketingHero,\n 'project.FeatureCard': FeatureCard,\n 'project.PricingCallout': PricingCallout,\n}\n"]}
1
+ {"version":3,"file":"project-components.js","sourceRoot":"","sources":["../../../src/lib/registry/project-components.tsx"],"names":[],"mappings":"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","sourcesContent":["import { defineComponent } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport type { ComponentType, ReactNode } from 'react'\n\n/**\n * Sample `project.*` components for the Studio Components surface (brief §4.2,\n * §8.3). These stand in for the project-specific components that are the center\n * of the product: each declares a full manifest (prop schema metadata, defaults,\n * inspector controls, binding permissions, slot rules, design-token hints, and\n * preview fixtures) and ships a self-contained implementation the fixture runner\n * renders. Deterministic — no time/random.\n */\n\ntype Props = Record<string, unknown>\nfunction str(value: unknown, fallback = ''): string {\n return typeof value === 'string' && value.length > 0 ? value : fallback\n}\n\n// --- project.MarketingHero -------------------------------------------------\n\nfunction MarketingHero(props: Props): ReactNode {\n const align = props.align === 'center' ? 'items-center text-center' : 'items-start text-left'\n return (\n <div className={`flex flex-col gap-3 rounded-xl border border-border bg-card p-8 ${align}`}>\n {str(props.eyebrow) ? (\n <span className=\"text-[0.7rem] font-semibold uppercase tracking-[0.14em] text-primary\">\n {str(props.eyebrow)}\n </span>\n ) : null}\n <h2 className=\"text-2xl font-semibold tracking-tight text-foreground\">\n {str(props.heading, 'Headline goes here')}\n </h2>\n {str(props.subheading) ? (\n <p className=\"max-w-prose text-sm text-muted-foreground\">{str(props.subheading)}</p>\n ) : null}\n {str(props.ctaLabel) ? (\n <span className=\"mt-1 inline-flex w-fit items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground\">\n {str(props.ctaLabel)}\n </span>\n ) : null}\n </div>\n )\n}\n\nconst MarketingHeroManifest = defineComponent({\n id: 'project.MarketingHero',\n namespace: 'project',\n title: 'Marketing Hero',\n description: 'A headline hero section with eyebrow, subheading, and a call to action.',\n category: 'marketing',\n props: {\n eyebrow: { type: 'text', default: 'New', form: { label: 'Eyebrow' } },\n heading: {\n type: 'text',\n default: 'Build code-first websites',\n validation: { min: 1 },\n form: { label: 'Heading' },\n },\n subheading: {\n type: 'text',\n default: 'One canonical graph powers editing, content, and export.',\n form: { label: 'Subheading', control: 'textarea' },\n },\n ctaLabel: { type: 'text', default: 'Get started', form: { label: 'CTA label' } },\n align: {\n type: 'select',\n default: 'left',\n options: [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n ],\n form: { label: 'Alignment' },\n },\n },\n slots: [],\n fixtures: [\n { name: 'Default' },\n {\n name: 'Centered',\n props: { align: 'center', eyebrow: 'Launch', heading: 'Ship faster', ctaLabel: 'Try it' },\n },\n { name: 'Minimal', props: { eyebrow: '', subheading: '', ctaLabel: '', heading: 'Just a headline' } },\n ],\n})\n\n// --- project.FeatureCard ---------------------------------------------------\n\nfunction FeatureCard(props: Props): ReactNode {\n const accent = props.tone === 'accent'\n return (\n <div\n className={`flex flex-col gap-2 rounded-lg border p-5 ${\n accent ? 'border-primary/30 bg-primary/5' : 'border-border bg-card'\n }`}\n >\n <div className={`grid size-8 place-items-center rounded-md ${accent ? 'bg-primary text-primary-foreground' : 'bg-muted text-foreground'}`}>\n <span className=\"text-sm font-bold\">{str(props.title, 'F').charAt(0)}</span>\n </div>\n <h3 className=\"text-sm font-semibold text-foreground\">{str(props.title, 'Feature title')}</h3>\n <p className=\"text-xs text-muted-foreground\">{str(props.body, 'A short description of the feature.')}</p>\n </div>\n )\n}\n\nconst FeatureCardManifest = defineComponent({\n id: 'project.FeatureCard',\n namespace: 'project',\n title: 'Feature Card',\n description: 'A compact card highlighting a single feature.',\n category: 'marketing',\n props: {\n title: {\n type: 'text',\n default: 'Validation first',\n validation: { min: 1 },\n form: { label: 'Title' },\n },\n body: {\n type: 'text',\n default: 'Broken bindings and missing components are explicit, never silent.',\n form: { label: 'Body', control: 'textarea' },\n },\n tone: {\n type: 'select',\n default: 'default',\n options: [\n { value: 'default', label: 'Default' },\n { value: 'accent', label: 'Accent' },\n ],\n form: { label: 'Tone' },\n },\n },\n slots: [],\n fixtures: [{ name: 'Default' }, { name: 'Accent', props: { tone: 'accent', title: 'AI inspectable' } }],\n})\n\n// --- project.PricingCallout ------------------------------------------------\n\nfunction PricingCallout(props: Props): ReactNode {\n const featured = props.featured === true\n return (\n <div\n className={`flex flex-col gap-1 rounded-xl border p-6 ${\n featured ? 'border-primary bg-primary/5 shadow-md' : 'border-border bg-card'\n }`}\n >\n {featured ? (\n <span className=\"mb-1 w-fit rounded-full bg-primary px-2 py-0.5 text-[0.65rem] font-semibold uppercase tracking-wide text-primary-foreground\">\n Popular\n </span>\n ) : null}\n <span className=\"text-sm font-medium text-muted-foreground\">{str(props.plan, 'Plan')}</span>\n <div className=\"flex items-baseline gap-1\">\n <span className=\"text-3xl font-semibold tracking-tight text-foreground\">{str(props.price, '$0')}</span>\n <span className=\"text-sm text-muted-foreground\">/{str(props.period, 'mo')}</span>\n </div>\n </div>\n )\n}\n\nconst PricingCalloutManifest = defineComponent({\n id: 'project.PricingCallout',\n namespace: 'project',\n title: 'Pricing Callout',\n description: 'A pricing plan callout with an optional featured emphasis.',\n category: 'commerce',\n props: {\n plan: {\n type: 'text',\n default: 'Pro',\n validation: { min: 1 },\n form: { label: 'Plan' },\n },\n price: { type: 'text', default: '$29', form: { label: 'Price' } },\n period: { type: 'text', default: 'mo', form: { label: 'Period' } },\n featured: { type: 'boolean', default: false, form: { label: 'Featured' } },\n },\n slots: [],\n fixtures: [{ name: 'Standard' }, { name: 'Featured', props: { featured: true, plan: 'Team', price: '$79' } }],\n})\n\n// --- project.LegacyBanner (intentionally has NO implementation) -------------\n//\n// Registered manifest with no implementation — exercises the \"incompatible /\n// missing implementation\" component status (EC-055) and the renderer's\n// missing-component fallback (brief §4.11).\n\nconst LegacyBannerManifest = defineComponent({\n id: 'project.LegacyBanner',\n namespace: 'project',\n title: 'Legacy Banner',\n description: 'A deprecated banner whose implementation has not been wired up.',\n category: 'marketing',\n props: {\n message: { type: 'text', default: 'Deprecated', form: { label: 'Message' } },\n },\n slots: [],\n fixtures: [{ name: 'Default' }],\n})\n\n/** Project component manifests (including the deliberately-unimplemented one). */\nexport const projectComponentManifests: ComponentManifest[] = [\n MarketingHeroManifest,\n FeatureCardManifest,\n PricingCalloutManifest,\n LegacyBannerManifest,\n]\n\n/** Implementations for the project components — note `LegacyBanner` is absent. */\nexport const projectComponentImplementations: Record<string, ComponentType<Props>> = {\n 'project.MarketingHero': MarketingHero,\n 'project.FeatureCard': FeatureCard,\n 'project.PricingCallout': PricingCallout,\n}\n"]}
@@ -5,10 +5,9 @@ import { createRouter, isHierarchyTemplate, routeReachabilityIssues } from '@ely
5
5
  * `createRouter` has always computed structural issues (**route conflicts** and
6
6
  * **redirect loops**) and, before this, no studio surface showed them. The delivery
7
7
  * content client exposes them as `routerIssues`, but that path never reaches the
8
- * studio: its only producer, the Convex `createContent` kit, has no studio caller —
9
- * it serves the delivery host. So the studio computes them from the two inputs it
10
- * already holds, which is also the only way it can: routes come from config
11
- * (`resolveDeploymentRoutes`), redirects come from the editorial rail.
8
+ * studio: it serves the delivery host. So the studio computes them from the two
9
+ * inputs it already holds, which is also the only way it can: routes come from
10
+ * config (`resolveDeploymentRoutes`), redirects come from the editorial rail.
12
11
  *
13
12
  * This is NOT a revived Routes view (EC-303 retired that, correctly — routes are
14
13
  * code). It is validation of the code + data the studio already reads.
@@ -22,7 +21,7 @@ import { createRouter, isHierarchyTemplate, routeReachabilityIssues } from '@ely
22
21
  * makes `/:slug` + `/:path*` look like a conflict — measured — which the runtime
23
22
  * does not consider one. A false alarm here would be worse than no alarm: it would
24
23
  * teach developers to ignore the surface.
25
- * - **Only ENABLED redirects count** (the Convex content kit filters `row.enabled`).
24
+ * - **Only ENABLED redirects count** (`delivery:snapshot` filters `row.enabled`).
26
25
  * `enabled` is the switch for *pausing* a redirect, so a paused chain is not live
27
26
  * and must not be reported as a loop.
28
27
  *
@@ -1 +1 @@
1
- {"version":3,"file":"route-issues.js","sourceRoot":"","sources":["../../../src/lib/routes/route-issues.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAIrG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH;;;;;;;;;;GAUG;AACH,SAAS,aAAa,CAAC,MAAqC;IAC1D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAyB,EAAE,CAAA;IACpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACnC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACf,SAAQ;QACV,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,CAAA;QAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAC9B,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;YAClD,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACxB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAQ;QAC3B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACb,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACjB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,qFAAqF;AACrF,SAAS,aAAa,CAAC,SAAmC;IACxD,OAAO,SAAS;SACb,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC;SAChC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACf,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,SAAS,EAAE,KAAK,CAAC,SAAS;KAC3B,CAAC,CAAC,CAAA;AACP,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAA8B,EAC9B,YAAsC,EAAE;IAExC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAA;IACtE,OAAO;QACL,GAAG,aAAa,CAAC,YAAY,CAAC,QAAQ,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;QACzE,8EAA8E;QAC9E,iFAAiF;QACjF,uCAAuC;QACvC,GAAG,uBAAuB,CAAC,MAAM,CAAC;KACnC,CAAA;AACH,CAAC","sourcesContent":["import { createRouter, isHierarchyTemplate, routeReachabilityIssues } from '@elytracms/core/cms-core'\nimport type { CmsValidationIssue, RedirectRecord, RouteRecord } from '@elytracms/core/cms-core'\nimport type { RedirectEntry } from '@elytracms/core/persistence'\n\n/**\n * The studio's URL-map validation (EC-469) — the pure core.\n *\n * `createRouter` has always computed structural issues (**route conflicts** and\n * **redirect loops**) and, before this, no studio surface showed them. The delivery\n * content client exposes them as `routerIssues`, but that path never reaches the\n * studio: its only producer, the Convex `createContent` kit, has no studio caller —\n * it serves the delivery host. So the studio computes them from the two inputs it\n * already holds, which is also the only way it can: routes come from config\n * (`resolveDeploymentRoutes`), redirects come from the editorial rail.\n *\n * This is NOT a revived Routes view (EC-303 retired that, correctly — routes are\n * code). It is validation of the code + data the studio already reads.\n *\n * **Fidelity is the whole point**, so both of delivery's input rules are applied\n * here, not approximated:\n *\n * - **Hierarchy mounts are filtered out** (`content/client.ts` does exactly this\n * before constructing its router). A mount (`/:path*`, EC-218) is resolved\n * per-request against the parent chain, not by the router. Counting it as a route\n * makes `/:slug` + `/:path*` look like a conflict — measured — which the runtime\n * does not consider one. A false alarm here would be worse than no alarm: it would\n * teach developers to ignore the surface.\n * - **Only ENABLED redirects count** (the Convex content kit filters `row.enabled`).\n * `enabled` is the switch for *pausing* a redirect, so a paused chain is not live\n * and must not be reported as a loop.\n *\n * `unknown-route-target` is deliberately NOT produced by the ROUTER here. Despite the\n * ticket's framing it is not a router issue: the *resolver* raises it per resolved URL\n * against the documents that exist (`content/client.ts`), so it needs a URL and a\n * document pool, neither of which a project-level check has.\n *\n * ## Unreachable declarations (EC-484)\n *\n * Beside the two router codes, this reports declarations that serve NOTHING —\n * measured, previously silent everywhere: an all-dynamic route (`/:slug`) matching\n * every URL at a depth a hierarchy mount serves; and a catch-all with a dynamic prefix\n * (`/:lang/:path*`), which every router drops and no mount is built from.\n *\n * **EC-488 narrowed the first one.** It used to mean the mount's root pages 404 though\n * they exist in the studio; the resolver now falls through to the mounts when a matched\n * route resolves no document, so those pages serve and the finding is a `warning` about\n * content-decided precedence (a page whose composed path collides with the route\n * collection's slug is still silently shadowed). The severity comes from the shared\n * `routeReachabilityIssues`, so this surface tracks it without a change here.\n *\n * This one belongs in the studio, unlike the mount-vs-mount conflicts EC-482 left to\n * `elytra doctor`, because of who meets the symptom: a 404 on a page the editor can\n * see in the studio is what an editor reports and a developer cannot reproduce from a\n * document. Naming it at the rail foot turns \"the About page is broken\" into a route\n * id. It is also not a new precedent — this function has surfaced the code-side\n * `route-conflict` since EC-469.\n *\n * The COLLECTION-dependent half of EC-484 (a mount whose collection has no `parent`\n * self-relation, so it can never compose a path) is not here: this core is given\n * routes and redirects, and `elytra doctor` — which loads the collections anyway —\n * reports it at build time, where a developer fixes it.\n */\n\n/**\n * Collapse the several reports of ONE redirect cycle into one finding.\n *\n * `createRouter` walks every redirect and reports a loop per distinct loop *entry\n * point* (`routes.ts`, EC-020) — correct for a machine, but a 2-hop cycle then reads\n * as two separate bugs at the rail foot, and fixing one makes both vanish. Every\n * rotation of a cycle visits the same set of paths, so the sorted member set is a\n * stable identity for the cycle: dedupe on it, keep the first, and two genuinely\n * different cycles still report separately. Presentation only — nothing is hidden, and\n * `createRouter`'s shared behaviour is untouched.\n */\nfunction collapseLoops(issues: readonly CmsValidationIssue[]): CmsValidationIssue[] {\n const seen = new Set<string>()\n const out: CmsValidationIssue[] = []\n for (const issue of issues) {\n if (issue.code !== 'redirect-loop') {\n out.push(issue)\n continue\n }\n const chain = issue.meta?.chain\n const key = Array.isArray(chain)\n ? [...new Set(chain.map(String))].sort().join('|')\n : issue.path.join('/')\n if (seen.has(key)) continue\n seen.add(key)\n out.push(issue)\n }\n return out\n}\n\n/** Project the stored editorial records onto the routing shape delivery resolves. */\nfunction liveRedirects(redirects: readonly RedirectEntry[]): RedirectRecord[] {\n return redirects\n .filter((entry) => entry.enabled)\n .map((entry) => ({\n id: entry.id,\n from: entry.from,\n to: entry.to,\n permanent: entry.permanent,\n }))\n}\n\n/**\n * The project's structural URL-map issues: route conflicts (from config routes),\n * redirect loops (from the enabled editorial redirects), and declarations that cannot\n * serve content at all (EC-484). Pure and total — it never throws, and an empty result\n * means \"no structural problem\", not \"not checked\".\n */\nexport function routeValidationIssues(\n routes: readonly RouteRecord[],\n redirects: readonly RedirectEntry[] = [],\n): readonly CmsValidationIssue[] {\n const routable = routes.filter((route) => !isHierarchyTemplate(route))\n return [\n ...collapseLoops(createRouter(routable, liveRedirects(redirects)).issues),\n // Routes only: the reachability checks that need the collection set stay with\n // `elytra doctor`, which has it. Passing no collections is the documented way to\n // ask for exactly the route-side half.\n ...routeReachabilityIssues(routes),\n ]\n}\n"]}
1
+ {"version":3,"file":"route-issues.js","sourceRoot":"","sources":["../../../src/lib/routes/route-issues.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAIrG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH;;;;;;;;;;GAUG;AACH,SAAS,aAAa,CAAC,MAAqC;IAC1D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAyB,EAAE,CAAA;IACpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACnC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACf,SAAQ;QACV,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,CAAA;QAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAC9B,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;YAClD,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACxB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAQ;QAC3B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACb,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACjB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,qFAAqF;AACrF,SAAS,aAAa,CAAC,SAAmC;IACxD,OAAO,SAAS;SACb,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC;SAChC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACf,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,SAAS,EAAE,KAAK,CAAC,SAAS;KAC3B,CAAC,CAAC,CAAA;AACP,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAA8B,EAC9B,YAAsC,EAAE;IAExC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAA;IACtE,OAAO;QACL,GAAG,aAAa,CAAC,YAAY,CAAC,QAAQ,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;QACzE,8EAA8E;QAC9E,iFAAiF;QACjF,uCAAuC;QACvC,GAAG,uBAAuB,CAAC,MAAM,CAAC;KACnC,CAAA;AACH,CAAC","sourcesContent":["import { createRouter, isHierarchyTemplate, routeReachabilityIssues } from '@elytracms/core/cms-core'\nimport type { CmsValidationIssue, RedirectRecord, RouteRecord } from '@elytracms/core/cms-core'\nimport type { RedirectEntry } from '@elytracms/core/persistence'\n\n/**\n * The studio's URL-map validation (EC-469) — the pure core.\n *\n * `createRouter` has always computed structural issues (**route conflicts** and\n * **redirect loops**) and, before this, no studio surface showed them. The delivery\n * content client exposes them as `routerIssues`, but that path never reaches the\n * studio: it serves the delivery host. So the studio computes them from the two\n * inputs it already holds, which is also the only way it can: routes come from\n * config (`resolveDeploymentRoutes`), redirects come from the editorial rail.\n *\n * This is NOT a revived Routes view (EC-303 retired that, correctly — routes are\n * code). It is validation of the code + data the studio already reads.\n *\n * **Fidelity is the whole point**, so both of delivery's input rules are applied\n * here, not approximated:\n *\n * - **Hierarchy mounts are filtered out** (`content/client.ts` does exactly this\n * before constructing its router). A mount (`/:path*`, EC-218) is resolved\n * per-request against the parent chain, not by the router. Counting it as a route\n * makes `/:slug` + `/:path*` look like a conflict — measured — which the runtime\n * does not consider one. A false alarm here would be worse than no alarm: it would\n * teach developers to ignore the surface.\n * - **Only ENABLED redirects count** (`delivery:snapshot` filters `row.enabled`).\n * `enabled` is the switch for *pausing* a redirect, so a paused chain is not live\n * and must not be reported as a loop.\n *\n * `unknown-route-target` is deliberately NOT produced by the ROUTER here. Despite the\n * ticket's framing it is not a router issue: the *resolver* raises it per resolved URL\n * against the documents that exist (`content/client.ts`), so it needs a URL and a\n * document pool, neither of which a project-level check has.\n *\n * ## Unreachable declarations (EC-484)\n *\n * Beside the two router codes, this reports declarations that serve NOTHING —\n * measured, previously silent everywhere: an all-dynamic route (`/:slug`) matching\n * every URL at a depth a hierarchy mount serves; and a catch-all with a dynamic prefix\n * (`/:lang/:path*`), which every router drops and no mount is built from.\n *\n * **EC-488 narrowed the first one.** It used to mean the mount's root pages 404 though\n * they exist in the studio; the resolver now falls through to the mounts when a matched\n * route resolves no document, so those pages serve and the finding is a `warning` about\n * content-decided precedence (a page whose composed path collides with the route\n * collection's slug is still silently shadowed). The severity comes from the shared\n * `routeReachabilityIssues`, so this surface tracks it without a change here.\n *\n * This one belongs in the studio, unlike the mount-vs-mount conflicts EC-482 left to\n * `elytra doctor`, because of who meets the symptom: a 404 on a page the editor can\n * see in the studio is what an editor reports and a developer cannot reproduce from a\n * document. Naming it at the rail foot turns \"the About page is broken\" into a route\n * id. It is also not a new precedent — this function has surfaced the code-side\n * `route-conflict` since EC-469.\n *\n * The COLLECTION-dependent half of EC-484 (a mount whose collection has no `parent`\n * self-relation, so it can never compose a path) is not here: this core is given\n * routes and redirects, and `elytra doctor` — which loads the collections anyway —\n * reports it at build time, where a developer fixes it.\n */\n\n/**\n * Collapse the several reports of ONE redirect cycle into one finding.\n *\n * `createRouter` walks every redirect and reports a loop per distinct loop *entry\n * point* (`routes.ts`, EC-020) — correct for a machine, but a 2-hop cycle then reads\n * as two separate bugs at the rail foot, and fixing one makes both vanish. Every\n * rotation of a cycle visits the same set of paths, so the sorted member set is a\n * stable identity for the cycle: dedupe on it, keep the first, and two genuinely\n * different cycles still report separately. Presentation only — nothing is hidden, and\n * `createRouter`'s shared behaviour is untouched.\n */\nfunction collapseLoops(issues: readonly CmsValidationIssue[]): CmsValidationIssue[] {\n const seen = new Set<string>()\n const out: CmsValidationIssue[] = []\n for (const issue of issues) {\n if (issue.code !== 'redirect-loop') {\n out.push(issue)\n continue\n }\n const chain = issue.meta?.chain\n const key = Array.isArray(chain)\n ? [...new Set(chain.map(String))].sort().join('|')\n : issue.path.join('/')\n if (seen.has(key)) continue\n seen.add(key)\n out.push(issue)\n }\n return out\n}\n\n/** Project the stored editorial records onto the routing shape delivery resolves. */\nfunction liveRedirects(redirects: readonly RedirectEntry[]): RedirectRecord[] {\n return redirects\n .filter((entry) => entry.enabled)\n .map((entry) => ({\n id: entry.id,\n from: entry.from,\n to: entry.to,\n permanent: entry.permanent,\n }))\n}\n\n/**\n * The project's structural URL-map issues: route conflicts (from config routes),\n * redirect loops (from the enabled editorial redirects), and declarations that cannot\n * serve content at all (EC-484). Pure and total — it never throws, and an empty result\n * means \"no structural problem\", not \"not checked\".\n */\nexport function routeValidationIssues(\n routes: readonly RouteRecord[],\n redirects: readonly RedirectEntry[] = [],\n): readonly CmsValidationIssue[] {\n const routable = routes.filter((route) => !isHierarchyTemplate(route))\n return [\n ...collapseLoops(createRouter(routable, liveRedirects(redirects)).issues),\n // Routes only: the reachability checks that need the collection set stay with\n // `elytra doctor`, which has it. Passing no collections is the documented way to\n // ask for exactly the route-side half.\n ...routeReachabilityIssues(routes),\n ]\n}\n"]}
package/dist/lib/theme.js CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Theme controller (light/dark). The studio renders dark-first (the SSR default
3
3
  * and the `<html class="dark">` baseline); a stored preference flips the `dark`
4
- * class on the document element. Light mode is the absence of that class — the
5
- * design tokens define `:root` (light) and `.dark` overrides (see tokens.css).
4
+ * class on the document element. Light mode is the absence of that class; the
5
+ * class flips `color-scheme`, which picks each token's `light-dark()` value
6
+ * (see tokens.css).
6
7
  */
7
8
  import { createLocalPreference } from './local-preference.js';
8
9
  export const THEME_STORAGE_KEY = 'elytra-theme';
@@ -1 +1 @@
1
- {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/lib/theme.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAI1D,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAA;AAC/C,MAAM,CAAC,MAAM,aAAa,GAAU,MAAM,CAAA;AAE1C,uEAAuE;AACvE,MAAM,UAAU,UAAU,CAAC,KAAY,EAAE,IAAiB;IACxD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,KAAK,MAAM,CAAC,CAAA;AACjD,CAAC;AAED,+EAA+E;AAC/E,4EAA4E;AAC5E,4EAA4E;AAC5E,gFAAgF;AAChF,MAAM,UAAU,GAAG,qBAAqB,CAAQ,iBAAiB,EAAE;IACjE,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IACtE,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;CACzB,CAAC,CAAA;AAEF,qEAAqE;AACrE,MAAM,UAAU,eAAe;IAC7B,OAAO,UAAU,CAAC,IAAI,EAAE,CAAA;AAC1B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,KAAY;IACrC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,+CAA+C,iBAAiB,mFAAmF,CAAA","sourcesContent":["/**\n * Theme controller (light/dark). The studio renders dark-first (the SSR default\n * and the `<html class=\"dark\">` baseline); a stored preference flips the `dark`\n * class on the document element. Light mode is the absence of that class — the\n * design tokens define `:root` (light) and `.dark` overrides (see tokens.css).\n */\nimport { createLocalPreference } from './local-preference'\n\nexport type Theme = 'light' | 'dark'\n\nexport const THEME_STORAGE_KEY = 'elytra-theme'\nexport const DEFAULT_THEME: Theme = 'dark'\n\n/** Apply a theme to the document root by toggling the `dark` class. */\nexport function applyTheme(theme: Theme, root: HTMLElement): void {\n root.classList.toggle('dark', theme === 'dark')\n}\n\n// No `default` (EC-329): `readStoredTheme` must return `null` — not a fallback\n// theme — when nothing is stored, so `theme-context.tsx`'s mount effect can\n// fall back to whatever the pre-hydration script already applied to the DOM\n// (FOUC prevention) instead of silently overriding it with a hardcoded default.\nconst preference = createLocalPreference<Theme>(THEME_STORAGE_KEY, {\n decode: (raw) => (raw === 'light' || raw === 'dark' ? raw : undefined),\n encode: (theme) => theme,\n})\n\n/** Read the persisted preference, or null when unset/unavailable. */\nexport function readStoredTheme(): Theme | null {\n return preference.read()\n}\n\n/** Persist the preference (best-effort; storage may be unavailable). */\nexport function storeTheme(theme: Theme): void {\n preference.store(theme)\n}\n\n/**\n * Inline script body that runs before hydration to apply the stored theme,\n * preventing a flash of the wrong theme. Injected as a raw <script> in the\n * document head; defaults to dark when no preference is stored.\n */\nexport const THEME_INIT_SCRIPT = `(function(){try{var t=localStorage.getItem('${THEME_STORAGE_KEY}');document.documentElement.classList.toggle('dark',t!=='light');}catch(e){}})();`\n"]}
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/lib/theme.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAI1D,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAA;AAC/C,MAAM,CAAC,MAAM,aAAa,GAAU,MAAM,CAAA;AAE1C,uEAAuE;AACvE,MAAM,UAAU,UAAU,CAAC,KAAY,EAAE,IAAiB;IACxD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,KAAK,MAAM,CAAC,CAAA;AACjD,CAAC;AAED,+EAA+E;AAC/E,4EAA4E;AAC5E,4EAA4E;AAC5E,gFAAgF;AAChF,MAAM,UAAU,GAAG,qBAAqB,CAAQ,iBAAiB,EAAE;IACjE,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IACtE,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;CACzB,CAAC,CAAA;AAEF,qEAAqE;AACrE,MAAM,UAAU,eAAe;IAC7B,OAAO,UAAU,CAAC,IAAI,EAAE,CAAA;AAC1B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,KAAY;IACrC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,+CAA+C,iBAAiB,mFAAmF,CAAA","sourcesContent":["/**\n * Theme controller (light/dark). The studio renders dark-first (the SSR default\n * and the `<html class=\"dark\">` baseline); a stored preference flips the `dark`\n * class on the document element. Light mode is the absence of that class; the\n * class flips `color-scheme`, which picks each token's `light-dark()` value\n * (see tokens.css).\n */\nimport { createLocalPreference } from './local-preference'\n\nexport type Theme = 'light' | 'dark'\n\nexport const THEME_STORAGE_KEY = 'elytra-theme'\nexport const DEFAULT_THEME: Theme = 'dark'\n\n/** Apply a theme to the document root by toggling the `dark` class. */\nexport function applyTheme(theme: Theme, root: HTMLElement): void {\n root.classList.toggle('dark', theme === 'dark')\n}\n\n// No `default` (EC-329): `readStoredTheme` must return `null` — not a fallback\n// theme — when nothing is stored, so `theme-context.tsx`'s mount effect can\n// fall back to whatever the pre-hydration script already applied to the DOM\n// (FOUC prevention) instead of silently overriding it with a hardcoded default.\nconst preference = createLocalPreference<Theme>(THEME_STORAGE_KEY, {\n decode: (raw) => (raw === 'light' || raw === 'dark' ? raw : undefined),\n encode: (theme) => theme,\n})\n\n/** Read the persisted preference, or null when unset/unavailable. */\nexport function readStoredTheme(): Theme | null {\n return preference.read()\n}\n\n/** Persist the preference (best-effort; storage may be unavailable). */\nexport function storeTheme(theme: Theme): void {\n preference.store(theme)\n}\n\n/**\n * Inline script body that runs before hydration to apply the stored theme,\n * preventing a flash of the wrong theme. Injected as a raw <script> in the\n * document head; defaults to dark when no preference is stored.\n */\nexport const THEME_INIT_SCRIPT = `(function(){try{var t=localStorage.getItem('${THEME_STORAGE_KEY}');document.documentElement.classList.toggle('dark',t!=='light');}catch(e){}})();`\n"]}
@@ -108,24 +108,21 @@ export function projectSweepWebhookTag(projectId) {
108
108
  * A single keystroke-batch on a live page was dropping every cached page *and*
109
109
  * every cached 404 on the site (EC-489 widened 404s to carry collection tags).
110
110
  *
111
- * ## The one documented exception (deliberate, not an oversight)
111
+ * ## No exceptions (EC-574)
112
112
  *
113
- * `cms-data-sources` rows are the sole document class the published delivery
114
- * snapshot reads from the **draft** side, on purpose — data sources are project
115
- * config, so published bindings must resolve even when the defining row is not
116
- * itself published (`studio/convex/delivery.ts`). A save to that system
117
- * collection therefore *can* change published output and is now silent.
118
- * Accepted because: (a) it is a retired-builder remnant (EC-166/167) with
119
- * **zero writers** left in the studio — `provisionSource`/`removeSource` have
120
- * no call sites; (b) it only ever invalidated *by accident*, through the
121
- * truncated-summary sweep fallback, never through a precise tag; and (c) a
122
- * collection-scoped carve-out cannot work here — the collection name is exactly
123
- * what the truncated summary loses. If that data layer is ever revived, the fix
124
- * belongs at the delivery trust boundary (serve sources published-only), not by
125
- * re-broadening this set.
113
+ * This set used to carry one documented exception: `cms-data-sources` rows, the
114
+ * one document class the published delivery snapshot read from the **draft**
115
+ * side, so a draft save there could change published output silently (the
116
+ * wave-0089 finding). EC-574 deleted the data-binding subsystem and the
117
+ * snapshot's `sources` strand with it (`studio/convex/delivery.ts`), so every
118
+ * document class now reaches delivery through its published pin only and this
119
+ * set is complete as written.
126
120
  */
127
121
  const PUBLISHED_STATE_OPERATIONS = new Set([
128
122
  'documents.delete',
123
+ // EC-576: the drop takes every pinned version with it; its input carries no `ref`,
124
+ // so it resolves to the whole-project sweep below — the only correct tag for it.
125
+ 'documents.clear',
129
126
  'documents.publish',
130
127
  'documents.unpublish',
131
128
  'documents.pinVersion',
@@ -1 +1 @@
1
- {"version":3,"file":"tags.js","sourceRoot":"","sources":["../../../src/lib/webhooks/tags.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OperationEvent } from '@elytracms/core/operations'\n\n/**\n * Pure tag computation for the EC-146 revalidation webhooks: map a succeeded\n * publish-shaped operation event onto the Next cache tags it invalidates.\n *\n * ## Tag format (mirror of `packages/next/src/cache.ts`)\n *\n * The canonical tag format lives in `@elytracms/next` (`src/cache.ts`). The\n * builder cannot depend on that package — it would drag the `next` peer\n * dependency into the studio bundle — so the three formatters are mirrored\n * here. Both sides pin the exact literal strings in their tests\n * (`tags.test.ts` here, `cache.test.ts` there) to keep them in lock-step.\n *\n * Every emitted tag is scoped `p:<projectId>:…` — the dispatcher always knows\n * the project id, and the scope keeps multi-project hosts from\n * cross-invalidating. Hosts must configure the same id as `cache.scope`.\n *\n * ## Which events produce which tags\n *\n * The gate is **\"can this operation change PUBLISHED output?\"** — not \"did this\n * operation touch a document\" (EC-501). Live delivery is published-only, so an\n * operation that cannot move published state has no cache entry to drop and\n * every tag it emits is waste by construction.\n *\n * - **Document publish/unpublish/pin/delete** (`documents.publish`,\n * `documents.unpublish`, `documents.pinVersion`, `documents.delete`): the\n * document's own\n * `doc:<collection>:<id>` tag — dropping its detail page *and every cached\n * list that actually contained it* (fetch-time tagging) — plus the coarse\n * `collection:<name>:<locale>` membership tag for **all** configured\n * locales, sweeping cached lists whose membership may have changed\n * (new/removed/newly-matching documents).\n * - **Document SAVE** (`documents.upsert`) fires **nothing** — see\n * {@link PUBLISHED_STATE_OPERATIONS}.\n * - **Editorial redirect write** (`redirects.save`, `redirects.remove`,\n * EC-510): the whole-project sweep tag — deliberately coarse, see\n * {@link ROUTING_OPERATIONS}.\n * - **Asset edit** (`assets.upsert` of an existing asset, `assets.remove`,\n * EC-227): the `asset:<id>` tag, dropping exactly the pages that *baked* that\n * asset's delivery shape (alt/dimensions/url). An `assets.upsert` CREATE (no\n * id in the summary — minted server-side) fires nothing: a brand-new asset is\n * in no cached page yet.\n * - **Fallback**: when a `documents` (or `assets.remove`) event's input summary\n * was truncated past the identity (EC-139 caps summaries at 160 chars), the\n * project sweep tag is emitted — coarse, always correct, mirroring the EC-155\n * reference-index fallback. Since EC-501 the surviving document operations\n * all carry a small `{ projectId, ref }` input, so this is a defensive path\n * rather than a routine one — the routine truncation was the document-bearing\n * `documents.upsert`, which no longer reaches here at all.\n *\n * Every branch needs only the `projectId`, which serializes FIRST in every\n * operation input (measured 2026-08-01 across the real `redirects.save`\n * shapes), so {@link projectIdOfSummary}'s regex survives truncation even when\n * the rest of the summary is cut mid-string.\n */\n\nfunction scoped(projectId: string, tag: string): string {\n return `p:${projectId}:${tag}`\n}\n\n/** `p:<projectId>:doc:<collection>:<id>` — mirror of `docCacheTag`. */\nexport function docWebhookTag(projectId: string, collection: string, id: string): string {\n return scoped(projectId, `doc:${collection}:${id}`)\n}\n\n/** `p:<projectId>:collection:<name>:<locale>` — mirror of `collectionCacheTag`. */\nexport function collectionWebhookTag(\n projectId: string,\n collection: string,\n locale: string,\n): string {\n return scoped(projectId, `collection:${collection}:${locale}`)\n}\n\n/** `p:<projectId>:asset:<id>` — mirror of `assetCacheTag` (EC-227). */\nexport function assetWebhookTag(projectId: string, assetId: string): string {\n return scoped(projectId, `asset:${assetId}`)\n}\n\n/** `p:<projectId>:project` — mirror of `projectSweepTag`. */\nexport function projectSweepWebhookTag(projectId: string): string {\n return scoped(projectId, 'project')\n}\n\n/**\n * The document operations that can change **published** output, and therefore\n * the ones whose success must reach the host's revalidate endpoint.\n *\n * EC-224: publish/unpublish/pinVersion move the version pointer; their\n * `{ projectId, ref }` input drives the doc + per-locale collection tag set\n * (the flat `documents.setState` op is gone). `documents.delete` removes the\n * row outright, taking any pinned version with it.\n *\n * ## Why `documents.upsert` (a draft SAVE) is NOT here — EC-501\n *\n * A save writes the **draft row only**; published readers serve the *pinned\n * version snapshot*, never the draft. That is structural, not a heuristic:\n *\n * - the operation hands the document straight to `cms.upsertDocument`\n * (`core/operations/documents.ts`), whose Convex arg list has no\n * `publishedVersion` field at all (`studio/convex/cms.ts` `upsertDocument`),\n * and whose in-memory twin re-attaches the previous pin verbatim with the\n * comment \"only publish/unpublish move the pointer\"\n * (`core/persistence/cms.ts`);\n * - the published delivery snapshot skips every row without a pin and serves\n * the pinned snapshot, \"never falling back to the draft\"\n * (`studio/convex/delivery.ts`);\n * - the Convex write-chokepoint trigger already encodes exactly this verdict\n * server-side — it schedules a revalidation only when `publishedVersion`\n * actually changed, \"a plain draft save must not invalidate the published\n * host cache\" (`studio/convex/triggers.ts`, EC-286/EC-146). This emitter was\n * simply the one path that never asked.\n *\n * Measured before the change (connected studio → capture endpoint, one short\n * editing session): 12 `documents.upsert` deliveries to 1 `documents.publish`,\n * and 7 of those 12 carried the **whole-project sweep** tag rather than the\n * document's own — because a real document's `inputSummary` exceeds the EC-139\n * 160-char cap, so the identity parse below fails and falls back to the sweep.\n * A single keystroke-batch on a live page was dropping every cached page *and*\n * every cached 404 on the site (EC-489 widened 404s to carry collection tags).\n *\n * ## The one documented exception (deliberate, not an oversight)\n *\n * `cms-data-sources` rows are the sole document class the published delivery\n * snapshot reads from the **draft** side, on purpose — data sources are project\n * config, so published bindings must resolve even when the defining row is not\n * itself published (`studio/convex/delivery.ts`). A save to that system\n * collection therefore *can* change published output and is now silent.\n * Accepted because: (a) it is a retired-builder remnant (EC-166/167) with\n * **zero writers** left in the studio — `provisionSource`/`removeSource` have\n * no call sites; (b) it only ever invalidated *by accident*, through the\n * truncated-summary sweep fallback, never through a precise tag; and (c) a\n * collection-scoped carve-out cannot work here — the collection name is exactly\n * what the truncated summary loses. If that data layer is ever revived, the fix\n * belongs at the delivery trust boundary (serve sources published-only), not by\n * re-broadening this set.\n */\nconst PUBLISHED_STATE_OPERATIONS = new Set([\n 'documents.delete',\n 'documents.publish',\n 'documents.unpublish',\n 'documents.pinVersion',\n])\n/**\n * The **routing** operations (EC-510): editorial redirects, which EC-303 moved\n * out of config and into the database. Every *enabled* redirect row is\n * projected into the delivery snapshot the router and `resolvePage` consume\n * (`studio/convex/delivery.ts`), in BOTH perspectives — so saving, retargeting,\n * disabling or deleting one changes published output, exactly the question\n * {@link PUBLISHED_STATE_OPERATIONS} asks. They belong to that question, not a\n * parallel one; they are a separate set only because their tag is different.\n *\n * ## Why the coarse project sweep and NOT a targeted `route:<from>:<locale>`\n *\n * The tag vocabulary has `route:<pattern>:<locale>` and a redirect's `from` IS\n * a route path, so a targeted tag looks available. It is not — it would be\n * silently wrong in four independent ways, and a silent wrong invalidation is\n * the exact failure family EC-489/EC-501/this ticket exist to remove:\n *\n * 1. **`redirects.remove` has no `from` at all.** Its input is\n * `{ projectId, id }` (`core/operations/redirects.ts`), so the path whose\n * cache entry must drop is not in the event. Precision is unobtainable, and\n * a delete is the single most important case to get right.\n * 2. **A save can RENAME `from`.** The event carries only the NEW path; the OLD\n * one keeps serving a redirect that no longer exists. `OperationEvent` has\n * `operation`/`kind`/`status`/`inputSummary` and nothing else — no result,\n * no previous row (EC-501 rejected widening it for the same reason) — so the\n * emitter cannot even detect that a rename happened.\n * 3. **Normalization would have to be mirrored.** The cached entry is tagged\n * with the resolver's `normalizePath(url)`; `from` is editor-typed free text\n * (`z.string().min(1)`), stored verbatim and normalized only at match time.\n * A trailing slash or a query string would produce a tag that matches\n * nothing — no error, no invalidation.\n * 4. **The identity truncates.** MEASURED 2026-08-01 on the real schema: a\n * Konditorei-shaped migration redirect (legacy permalink → new path)\n * serializes to 226 chars and an EC-311 `meta` bag to 216, both past the\n * EC-139 160-char cap. A `from`-based tag would work on a demo-sized\n * redirect and degrade on the real one — EC-501's pathology in reverse.\n *\n * The sweep has none of those failure modes: it needs only `projectId`, which\n * serializes first and therefore survives truncation, so **the truncation\n * fallback and the primary answer are the same tag** — correct by design rather\n * than by accident. `nextCacheTags` stamps `project` on every cached entry, so\n * it is guaranteed to reach the affected URL whatever the redirect changed.\n *\n * ## The cost, and why it is affordable here (unlike EC-501)\n *\n * A sweep drops every cached entry, lazily on next read. That was intolerable\n * for `documents.upsert` because it fired on every ~1s keystroke batch and\n * *none* of those invalidations were necessary. A redirect write is an\n * explicit form save on a rail with `inlineEdit: false` and no autosave\n * (`redirects-collection-view-source.ts`), and every one of them IS a routing\n * change. Measured on a connected studio: a session that deliberately exercised\n * every mutation kind — create, retarget, disable, rename, delete — produced 5\n * writes. The Convex-side path already sweeps the project on every document\n * publish (`convex/revalidate.ts`, \"the honest, reliable v1\"), so this is\n * strictly rarer than a cost the engine already pays.\n *\n * If a precise redirect tag is ever wanted, the place to get it is the Convex\n * write chokepoint, where `change.oldDoc` holds the previous `from` — not here,\n * where the information does not exist.\n */\nconst ROUTING_OPERATIONS = new Set(['redirects.save', 'redirects.remove'])\n\n/**\n * Asset-record mutations (EC-227): editing an asset (alt/dimensions/re-upload)\n * must drop the published pages that *baked* its delivery shape, which carry the\n * `asset:<id>` tag. `assets.remove` always carries `assetId`; `assets.upsert`\n * carries `asset.id` only on an EDIT (a CREATE mints the id server-side).\n */\nconst ASSET_OPERATIONS = new Set(['assets.upsert', 'assets.remove'])\n\n/** One computed webhook emission: which project, why, and which tags. */\nexport interface WebhookTagSet {\n projectId: string\n /** The operation name, carried verbatim as the webhook `event`. */\n event: string\n /** Sorted, deduplicated, fully scoped Next tag strings. */\n tags: string[]\n}\n\n/** Tolerant parse of an EC-139 input summary (may be truncated past 160 chars). */\nfunction parseSummary(summary: string): Record<string, unknown> | undefined {\n try {\n const parsed: unknown = JSON.parse(summary)\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : undefined\n } catch {\n return undefined\n }\n}\n\n/** Extract the projectId even from a truncated summary (it serializes first). */\nfunction projectIdOfSummary(summary: string): string | undefined {\n const match = /\"projectId\":\"([^\"]+)\"/.exec(summary)\n return match?.[1]\n}\n\nfunction documentRefOf(value: unknown): { collection: string; id: string } | undefined {\n if (typeof value !== 'object' || value === null) return undefined\n const candidate = value as { collection?: unknown; id?: unknown }\n return typeof candidate.collection === 'string' &&\n candidate.collection.length > 0 &&\n typeof candidate.id === 'string' &&\n candidate.id.length > 0\n ? { collection: candidate.collection, id: candidate.id }\n : undefined\n}\n\n/**\n * Compute the webhook tag set for one lifecycle event, or `null` when the\n * event is not a successful publish-shaped command (queries, failures, and\n * unrelated domains never fire webhooks).\n */\nexport function webhookTagsForEvent(\n event: Pick<OperationEvent, 'operation' | 'kind' | 'status' | 'inputSummary'>,\n options: { locales: readonly string[] },\n): WebhookTagSet | null {\n if (event.status !== 'succeeded' || event.kind !== 'command') return null\n\n const isDocument = PUBLISHED_STATE_OPERATIONS.has(event.operation)\n const isAsset = ASSET_OPERATIONS.has(event.operation)\n const isRouting = ROUTING_OPERATIONS.has(event.operation)\n if (!isDocument && !isAsset && !isRouting) return null\n\n const projectId = projectIdOfSummary(event.inputSummary)\n if (projectId === undefined) return null\n\n // EC-510: one answer for every redirect write — see `ROUTING_OPERATIONS` for\n // why the targeted `route:` tag is unobtainable and the sweep is the honest one.\n if (isRouting) {\n return { projectId, event: event.operation, tags: [projectSweepWebhookTag(projectId)] }\n }\n\n if (isAsset) return assetTagsForEvent(projectId, event)\n\n const parsed = parseSummary(event.inputSummary)\n const ref = documentRefOf(parsed?.['ref']) ?? documentRefOf(parsed?.['document'])\n if (!ref) {\n // Truncated summary (large upsert payload): honest coarse fallback.\n return { projectId, event: event.operation, tags: [projectSweepWebhookTag(projectId)] }\n }\n\n const tags = new Set<string>([docWebhookTag(projectId, ref.collection, ref.id)])\n for (const locale of options.locales) {\n tags.add(collectionWebhookTag(projectId, ref.collection, locale))\n }\n // EC-207: ROUTES are config-owned (a code deploy, which rebuilds the host), so\n // a document event never needs a `route:` tag. Redirects are NOT — EC-303 made\n // them editorial data and EC-510 gives their own operations the project sweep\n // (see `ROUTING_OPERATIONS`); a *document* write still cannot change them.\n return { projectId, event: event.operation, tags: [...tags].sort() }\n}\n\n/** A non-empty string, else `undefined` (the asset id out of a summary). */\nfunction stringOf(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\n/**\n * Tags for an asset mutation (EC-227). The edit cases — `assets.remove` (carries\n * `assetId`) and `assets.upsert` of an existing asset (carries `asset.id`) —\n * emit exactly `asset:<id>`, dropping the pages that baked that asset. An\n * `assets.upsert` with **no** id is a CREATE: the id is minted server-side and a\n * brand-new asset cannot be baked into any existing page, so it invalidates\n * nothing (`null`, no webhook). A `remove` missing its id means a truncated\n * summary — sweep the project (coarse, always correct — the document fallback).\n */\nfunction assetTagsForEvent(\n projectId: string,\n event: Pick<OperationEvent, 'operation' | 'inputSummary'>,\n): WebhookTagSet | null {\n const parsed = parseSummary(event.inputSummary)\n const assetId =\n event.operation === 'assets.remove'\n ? stringOf(parsed?.['assetId'])\n : stringOf((parsed?.['asset'] as { id?: unknown } | undefined)?.id)\n if (assetId) {\n return { projectId, event: event.operation, tags: [assetWebhookTag(projectId, assetId)] }\n }\n return event.operation === 'assets.remove'\n ? { projectId, event: event.operation, tags: [projectSweepWebhookTag(projectId)] }\n : null\n}\n"]}
1
+ {"version":3,"file":"tags.js","sourceRoot":"","sources":["../../../src/lib/webhooks/tags.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OperationEvent } from '@elytracms/core/operations'\n\n/**\n * Pure tag computation for the EC-146 revalidation webhooks: map a succeeded\n * publish-shaped operation event onto the Next cache tags it invalidates.\n *\n * ## Tag format (mirror of `packages/next/src/cache.ts`)\n *\n * The canonical tag format lives in `@elytracms/next` (`src/cache.ts`). The\n * builder cannot depend on that package — it would drag the `next` peer\n * dependency into the studio bundle — so the three formatters are mirrored\n * here. Both sides pin the exact literal strings in their tests\n * (`tags.test.ts` here, `cache.test.ts` there) to keep them in lock-step.\n *\n * Every emitted tag is scoped `p:<projectId>:…` — the dispatcher always knows\n * the project id, and the scope keeps multi-project hosts from\n * cross-invalidating. Hosts must configure the same id as `cache.scope`.\n *\n * ## Which events produce which tags\n *\n * The gate is **\"can this operation change PUBLISHED output?\"** — not \"did this\n * operation touch a document\" (EC-501). Live delivery is published-only, so an\n * operation that cannot move published state has no cache entry to drop and\n * every tag it emits is waste by construction.\n *\n * - **Document publish/unpublish/pin/delete** (`documents.publish`,\n * `documents.unpublish`, `documents.pinVersion`, `documents.delete`): the\n * document's own\n * `doc:<collection>:<id>` tag — dropping its detail page *and every cached\n * list that actually contained it* (fetch-time tagging) — plus the coarse\n * `collection:<name>:<locale>` membership tag for **all** configured\n * locales, sweeping cached lists whose membership may have changed\n * (new/removed/newly-matching documents).\n * - **Document SAVE** (`documents.upsert`) fires **nothing** — see\n * {@link PUBLISHED_STATE_OPERATIONS}.\n * - **Editorial redirect write** (`redirects.save`, `redirects.remove`,\n * EC-510): the whole-project sweep tag — deliberately coarse, see\n * {@link ROUTING_OPERATIONS}.\n * - **Asset edit** (`assets.upsert` of an existing asset, `assets.remove`,\n * EC-227): the `asset:<id>` tag, dropping exactly the pages that *baked* that\n * asset's delivery shape (alt/dimensions/url). An `assets.upsert` CREATE (no\n * id in the summary — minted server-side) fires nothing: a brand-new asset is\n * in no cached page yet.\n * - **Fallback**: when a `documents` (or `assets.remove`) event's input summary\n * was truncated past the identity (EC-139 caps summaries at 160 chars), the\n * project sweep tag is emitted — coarse, always correct, mirroring the EC-155\n * reference-index fallback. Since EC-501 the surviving document operations\n * all carry a small `{ projectId, ref }` input, so this is a defensive path\n * rather than a routine one — the routine truncation was the document-bearing\n * `documents.upsert`, which no longer reaches here at all.\n *\n * Every branch needs only the `projectId`, which serializes FIRST in every\n * operation input (measured 2026-08-01 across the real `redirects.save`\n * shapes), so {@link projectIdOfSummary}'s regex survives truncation even when\n * the rest of the summary is cut mid-string.\n */\n\nfunction scoped(projectId: string, tag: string): string {\n return `p:${projectId}:${tag}`\n}\n\n/** `p:<projectId>:doc:<collection>:<id>` — mirror of `docCacheTag`. */\nexport function docWebhookTag(projectId: string, collection: string, id: string): string {\n return scoped(projectId, `doc:${collection}:${id}`)\n}\n\n/** `p:<projectId>:collection:<name>:<locale>` — mirror of `collectionCacheTag`. */\nexport function collectionWebhookTag(\n projectId: string,\n collection: string,\n locale: string,\n): string {\n return scoped(projectId, `collection:${collection}:${locale}`)\n}\n\n/** `p:<projectId>:asset:<id>` — mirror of `assetCacheTag` (EC-227). */\nexport function assetWebhookTag(projectId: string, assetId: string): string {\n return scoped(projectId, `asset:${assetId}`)\n}\n\n/** `p:<projectId>:project` — mirror of `projectSweepTag`. */\nexport function projectSweepWebhookTag(projectId: string): string {\n return scoped(projectId, 'project')\n}\n\n/**\n * The document operations that can change **published** output, and therefore\n * the ones whose success must reach the host's revalidate endpoint.\n *\n * EC-224: publish/unpublish/pinVersion move the version pointer; their\n * `{ projectId, ref }` input drives the doc + per-locale collection tag set\n * (the flat `documents.setState` op is gone). `documents.delete` removes the\n * row outright, taking any pinned version with it.\n *\n * ## Why `documents.upsert` (a draft SAVE) is NOT here — EC-501\n *\n * A save writes the **draft row only**; published readers serve the *pinned\n * version snapshot*, never the draft. That is structural, not a heuristic:\n *\n * - the operation hands the document straight to `cms.upsertDocument`\n * (`core/operations/documents.ts`), whose Convex arg list has no\n * `publishedVersion` field at all (`studio/convex/cms.ts` `upsertDocument`),\n * and whose in-memory twin re-attaches the previous pin verbatim with the\n * comment \"only publish/unpublish move the pointer\"\n * (`core/persistence/cms.ts`);\n * - the published delivery snapshot skips every row without a pin and serves\n * the pinned snapshot, \"never falling back to the draft\"\n * (`studio/convex/delivery.ts`);\n * - the Convex write-chokepoint trigger already encodes exactly this verdict\n * server-side — it schedules a revalidation only when `publishedVersion`\n * actually changed, \"a plain draft save must not invalidate the published\n * host cache\" (`studio/convex/triggers.ts`, EC-286/EC-146). This emitter was\n * simply the one path that never asked.\n *\n * Measured before the change (connected studio → capture endpoint, one short\n * editing session): 12 `documents.upsert` deliveries to 1 `documents.publish`,\n * and 7 of those 12 carried the **whole-project sweep** tag rather than the\n * document's own — because a real document's `inputSummary` exceeds the EC-139\n * 160-char cap, so the identity parse below fails and falls back to the sweep.\n * A single keystroke-batch on a live page was dropping every cached page *and*\n * every cached 404 on the site (EC-489 widened 404s to carry collection tags).\n *\n * ## No exceptions (EC-574)\n *\n * This set used to carry one documented exception: `cms-data-sources` rows, the\n * one document class the published delivery snapshot read from the **draft**\n * side, so a draft save there could change published output silently (the\n * wave-0089 finding). EC-574 deleted the data-binding subsystem and the\n * snapshot's `sources` strand with it (`studio/convex/delivery.ts`), so every\n * document class now reaches delivery through its published pin only and this\n * set is complete as written.\n */\nconst PUBLISHED_STATE_OPERATIONS = new Set([\n 'documents.delete',\n // EC-576: the drop takes every pinned version with it; its input carries no `ref`,\n // so it resolves to the whole-project sweep below — the only correct tag for it.\n 'documents.clear',\n 'documents.publish',\n 'documents.unpublish',\n 'documents.pinVersion',\n])\n/**\n * The **routing** operations (EC-510): editorial redirects, which EC-303 moved\n * out of config and into the database. Every *enabled* redirect row is\n * projected into the delivery snapshot the router and `resolvePage` consume\n * (`studio/convex/delivery.ts`), in BOTH perspectives — so saving, retargeting,\n * disabling or deleting one changes published output, exactly the question\n * {@link PUBLISHED_STATE_OPERATIONS} asks. They belong to that question, not a\n * parallel one; they are a separate set only because their tag is different.\n *\n * ## Why the coarse project sweep and NOT a targeted `route:<from>:<locale>`\n *\n * The tag vocabulary has `route:<pattern>:<locale>` and a redirect's `from` IS\n * a route path, so a targeted tag looks available. It is not — it would be\n * silently wrong in four independent ways, and a silent wrong invalidation is\n * the exact failure family EC-489/EC-501/this ticket exist to remove:\n *\n * 1. **`redirects.remove` has no `from` at all.** Its input is\n * `{ projectId, id }` (`core/operations/redirects.ts`), so the path whose\n * cache entry must drop is not in the event. Precision is unobtainable, and\n * a delete is the single most important case to get right.\n * 2. **A save can RENAME `from`.** The event carries only the NEW path; the OLD\n * one keeps serving a redirect that no longer exists. `OperationEvent` has\n * `operation`/`kind`/`status`/`inputSummary` and nothing else — no result,\n * no previous row (EC-501 rejected widening it for the same reason) — so the\n * emitter cannot even detect that a rename happened.\n * 3. **Normalization would have to be mirrored.** The cached entry is tagged\n * with the resolver's `normalizePath(url)`; `from` is editor-typed free text\n * (`z.string().min(1)`), stored verbatim and normalized only at match time.\n * A trailing slash or a query string would produce a tag that matches\n * nothing — no error, no invalidation.\n * 4. **The identity truncates.** MEASURED 2026-08-01 on the real schema: a\n * Konditorei-shaped migration redirect (legacy permalink → new path)\n * serializes to 226 chars and an EC-311 `meta` bag to 216, both past the\n * EC-139 160-char cap. A `from`-based tag would work on a demo-sized\n * redirect and degrade on the real one — EC-501's pathology in reverse.\n *\n * The sweep has none of those failure modes: it needs only `projectId`, which\n * serializes first and therefore survives truncation, so **the truncation\n * fallback and the primary answer are the same tag** — correct by design rather\n * than by accident. `nextCacheTags` stamps `project` on every cached entry, so\n * it is guaranteed to reach the affected URL whatever the redirect changed.\n *\n * ## The cost, and why it is affordable here (unlike EC-501)\n *\n * A sweep drops every cached entry, lazily on next read. That was intolerable\n * for `documents.upsert` because it fired on every ~1s keystroke batch and\n * *none* of those invalidations were necessary. A redirect write is an\n * explicit form save on a rail with `inlineEdit: false` and no autosave\n * (`redirects-collection-view-source.ts`), and every one of them IS a routing\n * change. Measured on a connected studio: a session that deliberately exercised\n * every mutation kind — create, retarget, disable, rename, delete — produced 5\n * writes. The Convex-side path already sweeps the project on every document\n * publish (`convex/revalidate.ts`, \"the honest, reliable v1\"), so this is\n * strictly rarer than a cost the engine already pays.\n *\n * If a precise redirect tag is ever wanted, the place to get it is the Convex\n * write chokepoint, where `change.oldDoc` holds the previous `from` — not here,\n * where the information does not exist.\n */\nconst ROUTING_OPERATIONS = new Set(['redirects.save', 'redirects.remove'])\n\n/**\n * Asset-record mutations (EC-227): editing an asset (alt/dimensions/re-upload)\n * must drop the published pages that *baked* its delivery shape, which carry the\n * `asset:<id>` tag. `assets.remove` always carries `assetId`; `assets.upsert`\n * carries `asset.id` only on an EDIT (a CREATE mints the id server-side).\n */\nconst ASSET_OPERATIONS = new Set(['assets.upsert', 'assets.remove'])\n\n/** One computed webhook emission: which project, why, and which tags. */\nexport interface WebhookTagSet {\n projectId: string\n /** The operation name, carried verbatim as the webhook `event`. */\n event: string\n /** Sorted, deduplicated, fully scoped Next tag strings. */\n tags: string[]\n}\n\n/** Tolerant parse of an EC-139 input summary (may be truncated past 160 chars). */\nfunction parseSummary(summary: string): Record<string, unknown> | undefined {\n try {\n const parsed: unknown = JSON.parse(summary)\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : undefined\n } catch {\n return undefined\n }\n}\n\n/** Extract the projectId even from a truncated summary (it serializes first). */\nfunction projectIdOfSummary(summary: string): string | undefined {\n const match = /\"projectId\":\"([^\"]+)\"/.exec(summary)\n return match?.[1]\n}\n\nfunction documentRefOf(value: unknown): { collection: string; id: string } | undefined {\n if (typeof value !== 'object' || value === null) return undefined\n const candidate = value as { collection?: unknown; id?: unknown }\n return typeof candidate.collection === 'string' &&\n candidate.collection.length > 0 &&\n typeof candidate.id === 'string' &&\n candidate.id.length > 0\n ? { collection: candidate.collection, id: candidate.id }\n : undefined\n}\n\n/**\n * Compute the webhook tag set for one lifecycle event, or `null` when the\n * event is not a successful publish-shaped command (queries, failures, and\n * unrelated domains never fire webhooks).\n */\nexport function webhookTagsForEvent(\n event: Pick<OperationEvent, 'operation' | 'kind' | 'status' | 'inputSummary'>,\n options: { locales: readonly string[] },\n): WebhookTagSet | null {\n if (event.status !== 'succeeded' || event.kind !== 'command') return null\n\n const isDocument = PUBLISHED_STATE_OPERATIONS.has(event.operation)\n const isAsset = ASSET_OPERATIONS.has(event.operation)\n const isRouting = ROUTING_OPERATIONS.has(event.operation)\n if (!isDocument && !isAsset && !isRouting) return null\n\n const projectId = projectIdOfSummary(event.inputSummary)\n if (projectId === undefined) return null\n\n // EC-510: one answer for every redirect write — see `ROUTING_OPERATIONS` for\n // why the targeted `route:` tag is unobtainable and the sweep is the honest one.\n if (isRouting) {\n return { projectId, event: event.operation, tags: [projectSweepWebhookTag(projectId)] }\n }\n\n if (isAsset) return assetTagsForEvent(projectId, event)\n\n const parsed = parseSummary(event.inputSummary)\n const ref = documentRefOf(parsed?.['ref']) ?? documentRefOf(parsed?.['document'])\n if (!ref) {\n // Truncated summary (large upsert payload): honest coarse fallback.\n return { projectId, event: event.operation, tags: [projectSweepWebhookTag(projectId)] }\n }\n\n const tags = new Set<string>([docWebhookTag(projectId, ref.collection, ref.id)])\n for (const locale of options.locales) {\n tags.add(collectionWebhookTag(projectId, ref.collection, locale))\n }\n // EC-207: ROUTES are config-owned (a code deploy, which rebuilds the host), so\n // a document event never needs a `route:` tag. Redirects are NOT — EC-303 made\n // them editorial data and EC-510 gives their own operations the project sweep\n // (see `ROUTING_OPERATIONS`); a *document* write still cannot change them.\n return { projectId, event: event.operation, tags: [...tags].sort() }\n}\n\n/** A non-empty string, else `undefined` (the asset id out of a summary). */\nfunction stringOf(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\n/**\n * Tags for an asset mutation (EC-227). The edit cases — `assets.remove` (carries\n * `assetId`) and `assets.upsert` of an existing asset (carries `asset.id`) —\n * emit exactly `asset:<id>`, dropping the pages that baked that asset. An\n * `assets.upsert` with **no** id is a CREATE: the id is minted server-side and a\n * brand-new asset cannot be baked into any existing page, so it invalidates\n * nothing (`null`, no webhook). A `remove` missing its id means a truncated\n * summary — sweep the project (coarse, always correct — the document fallback).\n */\nfunction assetTagsForEvent(\n projectId: string,\n event: Pick<OperationEvent, 'operation' | 'inputSummary'>,\n): WebhookTagSet | null {\n const parsed = parseSummary(event.inputSummary)\n const assetId =\n event.operation === 'assets.remove'\n ? stringOf(parsed?.['assetId'])\n : stringOf((parsed?.['asset'] as { id?: unknown } | undefined)?.id)\n if (assetId) {\n return { projectId, event: event.operation, tags: [assetWebhookTag(projectId, assetId)] }\n }\n return event.operation === 'assets.remove'\n ? { projectId, event: event.operation, tags: [projectSweepWebhookTag(projectId)] }\n : null\n}\n"]}
@@ -143,7 +143,7 @@ export declare function resolveDeploymentSidebar(): SidebarNode[] | undefined;
143
143
  * raw CSS for the preview iframe, so the canvas wears the SITE's chrome. Studio-only
144
144
  * (from `defineStudio`, never the delivery bundle); `undefined` when none — the
145
145
  * canvas keeps its built-in defaults (`bg-background text-foreground font-sans`
146
- * body, kit `tokensCss` only). Studio-wide, like the rail and field renderers.
146
+ * body). Studio-wide, like the rail and field renderers.
147
147
  */
148
148
  export declare function resolveDeploymentCanvasTheme(): StudioConfig['canvas'];
149
149
  /**
@@ -213,7 +213,7 @@ export function resolveDeploymentSidebar() {
213
213
  * raw CSS for the preview iframe, so the canvas wears the SITE's chrome. Studio-only
214
214
  * (from `defineStudio`, never the delivery bundle); `undefined` when none — the
215
215
  * canvas keeps its built-in defaults (`bg-background text-foreground font-sans`
216
- * body, kit `tokensCss` only). Studio-wide, like the rail and field renderers.
216
+ * body). Studio-wide, like the rail and field renderers.
217
217
  */
218
218
  export function resolveDeploymentCanvasTheme() {
219
219
  return studioConfig?.canvas;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/workspace/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { frontendUrlForEnvironment } from '@elytracms/core/studio-core'\nimport type {\n BreakpointConfig,\n ElytraConfigInput,\n ResolvedWorkspaceProject,\n} from '@elytracms/core/studio-core'\nimport type { CollectionDef, LocaleConfig, RouteRecord } from '@elytracms/core/cms-core'\nimport type { SidebarNode } from '../../components/app-views/module'\nimport type { CustomFieldRenderer, StudioConfig } from '../../components/app-views/studio-config'\nimport type { GeneratedPreviewLookup } from '../../components/builder/blocks-model'\nimport { provideStudioEnv } from '../persistence/backend-mode'\nimport { resolveWorkspaceState } from './workspace-source'\nimport type { WorkspaceState } from './workspace-source'\n\nexport {\n PROJECT_CONFIG_ISSUE_KEYS,\n PROJECT_CONFIG_POINT,\n projectConfigIssues,\n} from './config-issues'\nexport { resolveWorkspaceState } from './workspace-source'\nexport type { WorkspaceSources, WorkspaceState } from './workspace-source'\nexport {\n isSingleProjectWorkspace,\n launcherAutoForwardTarget,\n persistedProjects,\n selectSwitcherProjects,\n soleWorkspaceProject,\n unprovisionedProjects,\n} from './switcher-source'\nexport type { SwitcherEntry, SwitcherSourceInput } from './switcher-source'\n\nlet cached: WorkspaceState | null = null\nlet studioConfig: StudioConfig | null = null\n\n/**\n * Mount the studio against a project config (EC-223 mount contract) and an\n * optional studio-only customization config (EC-236). The consumer's entry point\n * does `import config from './elytra.config'` + `import studio from\n * './studio.config'` and calls this — an explicit init, NOT an `import.meta.glob`\n * autoload that reaches into the consumer's filesystem (Oli, 2026-06-19: \"there's\n * always an entry point that can trigger a function call\").\n *\n * Two configs, deliberately: `config` (`elytra.config.ts`) is the project\n * definition, shared with the `frontend/` delivery bundle and projected to data;\n * `studio` (`studio.config.tsx` via `defineStudio`) is live builder UI code — the\n * section rail, custom field renderers, branding — that must NEVER reach delivery.\n * Keeping them separate is the customization boundary principle in the mount\n * signature. Seeds the workspace state + the studio-config holder every later\n * query (`getActiveProjectSlug`, `resolveDeployment*`) reads.\n *\n * EC-357 — `options.env`: the consumer's `import.meta.env`, captured HERE because\n * the entry module is code Vite always transforms live. The engine's own\n * `import.meta.env` reads are unreliable inside dep-optimized package code (Vite ≥8\n * omits custom `VITE_*` values there; Vite 7 froze them at optimize time), which\n * silently broke backend URL / mode / dev-login resolution in real consumers.\n * Always pass it: `createStudio(config, studioConfig, { env: import.meta.env })`.\n */\nexport interface StudioMountOptions {\n /** The consumer entry's `import.meta.env` (see `provideStudioEnv` for the contract). */\n env?: Record<string, unknown>\n}\n\nexport function createStudio(\n config: ElytraConfigInput,\n studio?: StudioConfig,\n options?: StudioMountOptions,\n): void {\n provideStudioEnv(options?.env)\n cached = resolveWorkspaceState({\n fileModule: { default: config },\n fileName: 'elytra.config.ts',\n })\n studioConfig = studio ?? null\n}\n\n/** Test seam: set/clear the studio customization config. Never used by app code. */\nexport function setStudioConfigForTests(next: StudioConfig | null): void {\n studioConfig = next\n}\n\n/**\n * The active project slug (EC-179, vision AD-8). One project per backend, so the\n * active project selects which Convex deployment the studio talks to. The root\n * layout sets this from the URL (`/<slug>`) — or, on the sign-in screen, from\n * the `redirect` target's slug — before the auth/persistence adapters are\n * selected, so the *initial* sign-in authenticates against the project the user\n * is heading for (Sanity-style: authenticate to one workspace, then the full\n * config-based switcher is available). `null` at the launcher → the default\n * (first) configured project's deployment.\n */\nlet activeProjectSlug: string | null = null\n\n/** Set the active project slug; returns whether the resolved deployment changed. */\nexport function setActiveProjectSlug(slug: string | null): boolean {\n const before = resolveDeploymentUrl('')\n activeProjectSlug = slug && slug.trim() !== '' ? slug : null\n return resolveDeploymentUrl('') !== before\n}\n\nexport function getActiveProjectSlug(): string | null {\n return activeProjectSlug\n}\n\n/** Test seam: reset the active project slug. Never used by app code. */\nexport function setActiveProjectSlugForTests(slug: string | null): void {\n activeProjectSlug = slug\n}\n\n/**\n * Reserved first-path segments that are NOT project slugs: the studio's own\n * root routes (EC-220). Project surfaces now live at `/<slug>/...` (no `/p/`\n * prefix), so the studio's root namespace is shared between project slugs and\n * these fixed routes — they must be excluded from slug extraction. Keep in\n * sync with the non-`$projectId` files in `src/routes/`; the launcher (`/`) is\n * the empty segment, excluded by the regex requiring one path char.\n */\nconst RESERVED_ROOT_SEGMENTS: ReadonlySet<string> = new Set(['sign-in', 'styleguide'])\n\n/** Extract a project slug from a studio path (`/<slug>/...`), else `null`. */\nexport function projectSlugFromPath(pathname: string): string | null {\n const match = /^\\/([^/?#]+)/.exec(pathname)\n if (!match) return null\n const segment = decodeURIComponent(match[1]!)\n return RESERVED_ROOT_SEGMENTS.has(segment) ? null : segment\n}\n\n/**\n * The project whose backend the studio should bind for a given location\n * (EC-179). A project path (`/<slug>`) binds that project; the sign-in screen\n * binds the project from its `redirect` target so the *initial* sign-in\n * authenticates against the backend the user is heading for (Sanity-style).\n * Anywhere else (the launcher) → `null`, i.e. the default/first project.\n */\nexport function activeSlugForLocation(pathname: string, redirect?: string): string | null {\n const direct = projectSlugFromPath(pathname)\n if (direct) return direct\n if (pathname === '/sign-in' && redirect) return projectSlugFromPath(redirect)\n return null\n}\n\n/** The active config project (by slug), or the first configured project. */\nfunction activeConfigProject() {\n const state = getWorkspaceState()\n if (state.kind !== 'config') return undefined\n if (activeProjectSlug) {\n const match = state.projects.find((p) => p.slug === activeProjectSlug)\n if (match) return match\n }\n return state.projects[0]\n}\n\n/**\n * The studio's resolved workspace state. Deterministic per boot: the config is\n * static input (bundled file or deployment env), so it is resolved once and\n * cached — every consumer (gate, switcher, launcher) sees the same state.\n */\nexport function getWorkspaceState(): WorkspaceState {\n // Seeded by `createStudio(config)` at the entry point. Until then — or when the\n // consumer wires no config — resolve to the empty `none` state (the single-project\n // golden path), rather than reaching for the filesystem.\n if (!cached) cached = resolveWorkspaceState({})\n return cached\n}\n\n/** Test seam: clear/replace the cached state. Never used by app code. */\nexport function setWorkspaceStateForTests(next: WorkspaceState | null): void {\n cached = next\n}\n\n/**\n * Resolve the Convex deployment URL the studio should connect to (vision AD-8:\n * the backend is wired in config). One project per backend, so the **active**\n * project (see {@link setActiveProjectSlug}) selects the deployment via its\n * `convex.deployment` (or its production environment's). At the launcher (no\n * active project) the first configured project is the default backend. Absent\n * config — or a project without a wired deployment — falls back to\n * `VITE_CONVEX_URL` (`fallbackUrl`) for local single-deployment development.\n */\nexport function resolveDeploymentUrl(fallbackUrl: string): string {\n return projectDeploymentUrl(activeConfigProject(), fallbackUrl)\n}\n\n/** The deployment URL for a specific project slug (per-backend auth checks). */\nexport function deploymentUrlForSlug(slug: string, fallbackUrl: string): string {\n const state = getWorkspaceState()\n const project = state.kind === 'config' ? state.projects.find((p) => p.slug === slug) : undefined\n return projectDeploymentUrl(project, fallbackUrl)\n}\n\n/** A project's wired deployment: per-environment ref → project ref → env fallback. */\nfunction projectDeploymentUrl(\n project: ResolvedWorkspaceProject | undefined,\n fallbackUrl: string,\n): string {\n if (!project) return fallbackUrl\n const envDeployment = project.environments.find((e) => e.convex?.deployment)?.convex?.deployment\n return envDeployment ?? project.convex?.deployment ?? fallbackUrl\n}\n\n/**\n * The slug of the project bound to the active deployment (EC-179). One project\n * per backend, so the active config project's slug identifies the deployment's\n * one project; the Convex adapter stamps it onto deployment-scoped records.\n * Absent config falls back to `fallbackId` (used only when wired by env alone).\n */\nexport function resolveDeploymentProjectId(fallbackId: string): string {\n return activeConfigProject()?.slug ?? fallbackId\n}\n\n/**\n * The CMS collections of the active config project (AD-11, EC-183). CMS\n * collection schemas are config-owned: the studio reads the model from\n * `elytra.config.ts` (served through the persistence port — the backend has no\n * `cmsSchemas` table). `[]` when the active project declares none, or when the\n * studio is wired by env alone with no config.\n */\nexport function resolveDeploymentCollections(): CollectionDef[] {\n return activeConfigProject()?.collections ?? []\n}\n\n/**\n * The canvas preview breakpoints of the active config project (EC-250).\n * Config-sourced (config-as-code, derived from the site's Tailwind `screens`);\n * `undefined` when the project declares none — the canvas then falls back to\n * Tailwind's default screens (see `resolveCanvasViewports`), never a baked-in triple.\n */\nexport function resolveDeploymentBreakpoints(): BreakpointConfig[] | undefined {\n return activeConfigProject()?.breakpoints\n}\n\n/**\n * The custom field renderers from the studio config (EC-238): control name →\n * component. Studio-only live code (from `defineStudio`, never the project schema\n * or the delivery bundle); `undefined` when none — the document editor then uses\n * its built-in control for every field. Studio-wide (not per-project): the rail and\n * renderers customize the studio, not one backend.\n */\nexport function resolveDeploymentFieldRenderers(): Record<string, CustomFieldRenderer> | undefined {\n return studioConfig?.fieldRenderers\n}\n\n/**\n * The studio-UI localization config from the studio config (EC-241): the default\n * locale + imported translation catalogs. Studio-only live code (from `defineStudio`,\n * never the project schema or the delivery bundle); `undefined` when none — the\n * studio then renders its built-in English chrome. Studio-wide, like the rail and\n * field renderers (localizes the studio, not one backend).\n */\nexport function resolveDeploymentI18n(): StudioConfig['i18n'] {\n return studioConfig?.i18n\n}\n\n/**\n * The AI platform config from the studio config (EC-331): provider, knowledge, and\n * limits. Studio-only live code (from `defineStudio`, never the delivery bundle);\n * `undefined` when no `ai` block is configured — the feature is then fully absent\n * (the AI subsystem never boots, so `@elytracms/studio/lib/ai`'s runtime — and the\n * `ai`/`@ai-sdk/*` SDK behind it — is never imported). Studio-wide, like the rail\n * and field renderers.\n */\nexport function resolveDeploymentAi(): StudioConfig['ai'] {\n return studioConfig?.ai\n}\n\n/**\n * The generated-preview lookup from the studio config (EC-439 AD-2, the seam\n * EC-440 fills): a pure `componentId + fixtureName → URL` function the blocks\n * panel threads into its tile model. Studio-only live code (from `defineStudio`,\n * never the delivery bundle); `undefined` when none — tiles then fall back from\n * a curated `manifest.previewImage` straight to the icon+name placeholder.\n * Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentGeneratedPreviews(): GeneratedPreviewLookup | undefined {\n return studioConfig?.previews?.generated\n}\n\n/**\n * The configurable section rail from the studio config (EC-237): an ordered list of\n * `nav.*` nodes (views / groups / separators). Studio-only live code (carries view\n * objects + icons); `undefined` when none — the studio then derives the default\n * rail from its built-in views. Invalid entries surface as visible issues at\n * resolution, never silent.\n */\nexport function resolveDeploymentSidebar(): SidebarNode[] | undefined {\n return studioConfig?.sidebar\n}\n\n/**\n * The canvas page-surface theme from the studio config (EC-389): body classes +\n * raw CSS for the preview iframe, so the canvas wears the SITE's chrome. Studio-only\n * (from `defineStudio`, never the delivery bundle); `undefined` when none — the\n * canvas keeps its built-in defaults (`bg-background text-foreground font-sans`\n * body, kit `tokensCss` only). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentCanvasTheme(): StudioConfig['canvas'] {\n return studioConfig?.canvas\n}\n\n/**\n * The reusable-elements declaration from the studio config (EC-467): WHICH\n * component is the project's reuse embed. Studio-only (from `defineStudio`, never\n * the delivery bundle); `undefined` when none — the studio then INFERS the embed\n * from the manifests + collections exactly as it always has (absence means infer,\n * not off). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentReuse(): StudioConfig['reuse'] {\n return studioConfig?.reuse\n}\n\n/**\n * The resolved URL routes of the active config project (EC-207, \"routes as\n * code\"). Routes are config-sourced: the studio reads the materialized\n * `RouteRecord[]` from the active `elytra.config.ts` project — never from the\n * backend (the `routes` system collection is retired). The Routes surface and\n * the per-page URL preview both read through here. `[]` when the active project\n * declares none, or when the studio is wired by env alone with no config.\n */\nexport function resolveDeploymentRoutes(): RouteRecord[] {\n return activeConfigProject()?.routes ?? []\n}\n\n// Redirects are editorial DATA now (EC-303), managed in the studio Redirects rail\n// and served dynamically — no longer resolved from config. `resolveDeploymentRedirects`\n// is retired; static/infra redirects are the dev's job via native next.config/vercel.\n\n/**\n * The configured locales of the active config project (AD-8). The same locale\n * set the host stamps onto its per-locale cache tags — used by the revalidation\n * webhook layer (EC-192) so a publish always invalidates EVERY configured locale\n * (e.g. `/de`, `/fr`), never just the default. `[]` when wired by env alone.\n */\nexport function resolveDeploymentLocales(): string[] {\n return activeConfigProject()?.locales.locales ?? []\n}\n\n/**\n * The active config project's `LocaleConfig` (EC-357 finding #3). Locales are\n * structure and structure is code: when `elytra.config.ts` declares them, they\n * drive the studio's ambient locale switcher in EVERY mode — the CMS surface must\n * not fall back to the sample `en/de` fixture (playground, or a connected first\n * run with nothing persisted) when the config says otherwise. `undefined` when no\n * config project is active (env-only wiring keeps the existing kit/sample\n * fallbacks).\n *\n * EC-519: this used to REASSEMBLE the config's flat `locales` + `defaultLocale`\n * pair into a `LocaleConfig`, and had grown its own repair rule — an absent or\n * out-of-set default fell back to `locales[0]`. That is a fourth place a locale\n * could be invented, in the surface furthest from the author. The resolver now\n * hands over the same `LocaleConfig` the delivery host takes, so there is nothing\n * to reassemble and nothing to repair: a default outside the set is a config\n * ERROR (`default-locale-not-in-locales`) and the project never resolves.\n */\nexport function resolveDeploymentLocaleConfig(): LocaleConfig | undefined {\n return activeConfigProject()?.locales\n}\n\n/**\n * The active project's components reference (EC-194, AD-11): where the consuming\n * repo's OWN real components live (`{ package }` or `{ dir }`). When set, the\n * studio registry composes with the repo's components instead of the built-in\n * demo fixtures — so the page builder edits the repo's actual component library.\n * `undefined` → the project ships no code components; the studio falls back to the\n * golden-path fixtures.\n */\nexport function resolveDeploymentComponents() {\n return activeConfigProject()?.components\n}\n\n/**\n * The active project's public site URL (EC-215): the live site's base URL the\n * studio links to (a document's live URL + open-live button, EC-216). Resolved\n * in lockstep with {@link resolveDeploymentUrl} — the environment bound to the\n * active deployment (the first with a wired deployment) provides the URL, falling\n * back to the project-level `frontendUrl`. No active-environment selector exists\n * yet, so the bound environment is the active one. `undefined` when the active\n * project (or env-only wiring) configures no frontend URL.\n */\nexport function resolveDeploymentFrontendUrl(): string | undefined {\n const project = activeConfigProject()\n if (!project) return undefined\n const boundEnv = project.environments.find((e) => e.convex?.deployment)\n return frontendUrlForEnvironment(project, boundEnv?.key)\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/workspace/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { frontendUrlForEnvironment } from '@elytracms/core/studio-core'\nimport type {\n BreakpointConfig,\n ElytraConfigInput,\n ResolvedWorkspaceProject,\n} from '@elytracms/core/studio-core'\nimport type { CollectionDef, LocaleConfig, RouteRecord } from '@elytracms/core/cms-core'\nimport type { SidebarNode } from '../../components/app-views/module'\nimport type { CustomFieldRenderer, StudioConfig } from '../../components/app-views/studio-config'\nimport type { GeneratedPreviewLookup } from '../../components/builder/blocks-model'\nimport { provideStudioEnv } from '../persistence/backend-mode'\nimport { resolveWorkspaceState } from './workspace-source'\nimport type { WorkspaceState } from './workspace-source'\n\nexport {\n PROJECT_CONFIG_ISSUE_KEYS,\n PROJECT_CONFIG_POINT,\n projectConfigIssues,\n} from './config-issues'\nexport { resolveWorkspaceState } from './workspace-source'\nexport type { WorkspaceSources, WorkspaceState } from './workspace-source'\nexport {\n isSingleProjectWorkspace,\n launcherAutoForwardTarget,\n persistedProjects,\n selectSwitcherProjects,\n soleWorkspaceProject,\n unprovisionedProjects,\n} from './switcher-source'\nexport type { SwitcherEntry, SwitcherSourceInput } from './switcher-source'\n\nlet cached: WorkspaceState | null = null\nlet studioConfig: StudioConfig | null = null\n\n/**\n * Mount the studio against a project config (EC-223 mount contract) and an\n * optional studio-only customization config (EC-236). The consumer's entry point\n * does `import config from './elytra.config'` + `import studio from\n * './studio.config'` and calls this — an explicit init, NOT an `import.meta.glob`\n * autoload that reaches into the consumer's filesystem (Oli, 2026-06-19: \"there's\n * always an entry point that can trigger a function call\").\n *\n * Two configs, deliberately: `config` (`elytra.config.ts`) is the project\n * definition, shared with the `frontend/` delivery bundle and projected to data;\n * `studio` (`studio.config.tsx` via `defineStudio`) is live builder UI code — the\n * section rail, custom field renderers, branding — that must NEVER reach delivery.\n * Keeping them separate is the customization boundary principle in the mount\n * signature. Seeds the workspace state + the studio-config holder every later\n * query (`getActiveProjectSlug`, `resolveDeployment*`) reads.\n *\n * EC-357 — `options.env`: the consumer's `import.meta.env`, captured HERE because\n * the entry module is code Vite always transforms live. The engine's own\n * `import.meta.env` reads are unreliable inside dep-optimized package code (Vite ≥8\n * omits custom `VITE_*` values there; Vite 7 froze them at optimize time), which\n * silently broke backend URL / mode / dev-login resolution in real consumers.\n * Always pass it: `createStudio(config, studioConfig, { env: import.meta.env })`.\n */\nexport interface StudioMountOptions {\n /** The consumer entry's `import.meta.env` (see `provideStudioEnv` for the contract). */\n env?: Record<string, unknown>\n}\n\nexport function createStudio(\n config: ElytraConfigInput,\n studio?: StudioConfig,\n options?: StudioMountOptions,\n): void {\n provideStudioEnv(options?.env)\n cached = resolveWorkspaceState({\n fileModule: { default: config },\n fileName: 'elytra.config.ts',\n })\n studioConfig = studio ?? null\n}\n\n/** Test seam: set/clear the studio customization config. Never used by app code. */\nexport function setStudioConfigForTests(next: StudioConfig | null): void {\n studioConfig = next\n}\n\n/**\n * The active project slug (EC-179, vision AD-8). One project per backend, so the\n * active project selects which Convex deployment the studio talks to. The root\n * layout sets this from the URL (`/<slug>`) — or, on the sign-in screen, from\n * the `redirect` target's slug — before the auth/persistence adapters are\n * selected, so the *initial* sign-in authenticates against the project the user\n * is heading for (Sanity-style: authenticate to one workspace, then the full\n * config-based switcher is available). `null` at the launcher → the default\n * (first) configured project's deployment.\n */\nlet activeProjectSlug: string | null = null\n\n/** Set the active project slug; returns whether the resolved deployment changed. */\nexport function setActiveProjectSlug(slug: string | null): boolean {\n const before = resolveDeploymentUrl('')\n activeProjectSlug = slug && slug.trim() !== '' ? slug : null\n return resolveDeploymentUrl('') !== before\n}\n\nexport function getActiveProjectSlug(): string | null {\n return activeProjectSlug\n}\n\n/** Test seam: reset the active project slug. Never used by app code. */\nexport function setActiveProjectSlugForTests(slug: string | null): void {\n activeProjectSlug = slug\n}\n\n/**\n * Reserved first-path segments that are NOT project slugs: the studio's own\n * root routes (EC-220). Project surfaces now live at `/<slug>/...` (no `/p/`\n * prefix), so the studio's root namespace is shared between project slugs and\n * these fixed routes — they must be excluded from slug extraction. Keep in\n * sync with the non-`$projectId` files in `src/routes/`; the launcher (`/`) is\n * the empty segment, excluded by the regex requiring one path char.\n */\nconst RESERVED_ROOT_SEGMENTS: ReadonlySet<string> = new Set(['sign-in', 'styleguide'])\n\n/** Extract a project slug from a studio path (`/<slug>/...`), else `null`. */\nexport function projectSlugFromPath(pathname: string): string | null {\n const match = /^\\/([^/?#]+)/.exec(pathname)\n if (!match) return null\n const segment = decodeURIComponent(match[1]!)\n return RESERVED_ROOT_SEGMENTS.has(segment) ? null : segment\n}\n\n/**\n * The project whose backend the studio should bind for a given location\n * (EC-179). A project path (`/<slug>`) binds that project; the sign-in screen\n * binds the project from its `redirect` target so the *initial* sign-in\n * authenticates against the backend the user is heading for (Sanity-style).\n * Anywhere else (the launcher) → `null`, i.e. the default/first project.\n */\nexport function activeSlugForLocation(pathname: string, redirect?: string): string | null {\n const direct = projectSlugFromPath(pathname)\n if (direct) return direct\n if (pathname === '/sign-in' && redirect) return projectSlugFromPath(redirect)\n return null\n}\n\n/** The active config project (by slug), or the first configured project. */\nfunction activeConfigProject() {\n const state = getWorkspaceState()\n if (state.kind !== 'config') return undefined\n if (activeProjectSlug) {\n const match = state.projects.find((p) => p.slug === activeProjectSlug)\n if (match) return match\n }\n return state.projects[0]\n}\n\n/**\n * The studio's resolved workspace state. Deterministic per boot: the config is\n * static input (bundled file or deployment env), so it is resolved once and\n * cached — every consumer (gate, switcher, launcher) sees the same state.\n */\nexport function getWorkspaceState(): WorkspaceState {\n // Seeded by `createStudio(config)` at the entry point. Until then — or when the\n // consumer wires no config — resolve to the empty `none` state (the single-project\n // golden path), rather than reaching for the filesystem.\n if (!cached) cached = resolveWorkspaceState({})\n return cached\n}\n\n/** Test seam: clear/replace the cached state. Never used by app code. */\nexport function setWorkspaceStateForTests(next: WorkspaceState | null): void {\n cached = next\n}\n\n/**\n * Resolve the Convex deployment URL the studio should connect to (vision AD-8:\n * the backend is wired in config). One project per backend, so the **active**\n * project (see {@link setActiveProjectSlug}) selects the deployment via its\n * `convex.deployment` (or its production environment's). At the launcher (no\n * active project) the first configured project is the default backend. Absent\n * config — or a project without a wired deployment — falls back to\n * `VITE_CONVEX_URL` (`fallbackUrl`) for local single-deployment development.\n */\nexport function resolveDeploymentUrl(fallbackUrl: string): string {\n return projectDeploymentUrl(activeConfigProject(), fallbackUrl)\n}\n\n/** The deployment URL for a specific project slug (per-backend auth checks). */\nexport function deploymentUrlForSlug(slug: string, fallbackUrl: string): string {\n const state = getWorkspaceState()\n const project = state.kind === 'config' ? state.projects.find((p) => p.slug === slug) : undefined\n return projectDeploymentUrl(project, fallbackUrl)\n}\n\n/** A project's wired deployment: per-environment ref → project ref → env fallback. */\nfunction projectDeploymentUrl(\n project: ResolvedWorkspaceProject | undefined,\n fallbackUrl: string,\n): string {\n if (!project) return fallbackUrl\n const envDeployment = project.environments.find((e) => e.convex?.deployment)?.convex?.deployment\n return envDeployment ?? project.convex?.deployment ?? fallbackUrl\n}\n\n/**\n * The slug of the project bound to the active deployment (EC-179). One project\n * per backend, so the active config project's slug identifies the deployment's\n * one project; the Convex adapter stamps it onto deployment-scoped records.\n * Absent config falls back to `fallbackId` (used only when wired by env alone).\n */\nexport function resolveDeploymentProjectId(fallbackId: string): string {\n return activeConfigProject()?.slug ?? fallbackId\n}\n\n/**\n * The CMS collections of the active config project (AD-11, EC-183). CMS\n * collection schemas are config-owned: the studio reads the model from\n * `elytra.config.ts` (served through the persistence port — the backend has no\n * `cmsSchemas` table). `[]` when the active project declares none, or when the\n * studio is wired by env alone with no config.\n */\nexport function resolveDeploymentCollections(): CollectionDef[] {\n return activeConfigProject()?.collections ?? []\n}\n\n/**\n * The canvas preview breakpoints of the active config project (EC-250).\n * Config-sourced (config-as-code, derived from the site's Tailwind `screens`);\n * `undefined` when the project declares none — the canvas then falls back to\n * Tailwind's default screens (see `resolveCanvasViewports`), never a baked-in triple.\n */\nexport function resolveDeploymentBreakpoints(): BreakpointConfig[] | undefined {\n return activeConfigProject()?.breakpoints\n}\n\n/**\n * The custom field renderers from the studio config (EC-238): control name →\n * component. Studio-only live code (from `defineStudio`, never the project schema\n * or the delivery bundle); `undefined` when none — the document editor then uses\n * its built-in control for every field. Studio-wide (not per-project): the rail and\n * renderers customize the studio, not one backend.\n */\nexport function resolveDeploymentFieldRenderers(): Record<string, CustomFieldRenderer> | undefined {\n return studioConfig?.fieldRenderers\n}\n\n/**\n * The studio-UI localization config from the studio config (EC-241): the default\n * locale + imported translation catalogs. Studio-only live code (from `defineStudio`,\n * never the project schema or the delivery bundle); `undefined` when none — the\n * studio then renders its built-in English chrome. Studio-wide, like the rail and\n * field renderers (localizes the studio, not one backend).\n */\nexport function resolveDeploymentI18n(): StudioConfig['i18n'] {\n return studioConfig?.i18n\n}\n\n/**\n * The AI platform config from the studio config (EC-331): provider, knowledge, and\n * limits. Studio-only live code (from `defineStudio`, never the delivery bundle);\n * `undefined` when no `ai` block is configured — the feature is then fully absent\n * (the AI subsystem never boots, so `@elytracms/studio/lib/ai`'s runtime — and the\n * `ai`/`@ai-sdk/*` SDK behind it — is never imported). Studio-wide, like the rail\n * and field renderers.\n */\nexport function resolveDeploymentAi(): StudioConfig['ai'] {\n return studioConfig?.ai\n}\n\n/**\n * The generated-preview lookup from the studio config (EC-439 AD-2, the seam\n * EC-440 fills): a pure `componentId + fixtureName → URL` function the blocks\n * panel threads into its tile model. Studio-only live code (from `defineStudio`,\n * never the delivery bundle); `undefined` when none — tiles then fall back from\n * a curated `manifest.previewImage` straight to the icon+name placeholder.\n * Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentGeneratedPreviews(): GeneratedPreviewLookup | undefined {\n return studioConfig?.previews?.generated\n}\n\n/**\n * The configurable section rail from the studio config (EC-237): an ordered list of\n * `nav.*` nodes (views / groups / separators). Studio-only live code (carries view\n * objects + icons); `undefined` when none — the studio then derives the default\n * rail from its built-in views. Invalid entries surface as visible issues at\n * resolution, never silent.\n */\nexport function resolveDeploymentSidebar(): SidebarNode[] | undefined {\n return studioConfig?.sidebar\n}\n\n/**\n * The canvas page-surface theme from the studio config (EC-389): body classes +\n * raw CSS for the preview iframe, so the canvas wears the SITE's chrome. Studio-only\n * (from `defineStudio`, never the delivery bundle); `undefined` when none — the\n * canvas keeps its built-in defaults (`bg-background text-foreground font-sans`\n * body). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentCanvasTheme(): StudioConfig['canvas'] {\n return studioConfig?.canvas\n}\n\n/**\n * The reusable-elements declaration from the studio config (EC-467): WHICH\n * component is the project's reuse embed. Studio-only (from `defineStudio`, never\n * the delivery bundle); `undefined` when none — the studio then INFERS the embed\n * from the manifests + collections exactly as it always has (absence means infer,\n * not off). Studio-wide, like the rail and field renderers.\n */\nexport function resolveDeploymentReuse(): StudioConfig['reuse'] {\n return studioConfig?.reuse\n}\n\n/**\n * The resolved URL routes of the active config project (EC-207, \"routes as\n * code\"). Routes are config-sourced: the studio reads the materialized\n * `RouteRecord[]` from the active `elytra.config.ts` project — never from the\n * backend (the `routes` system collection is retired). The Routes surface and\n * the per-page URL preview both read through here. `[]` when the active project\n * declares none, or when the studio is wired by env alone with no config.\n */\nexport function resolveDeploymentRoutes(): RouteRecord[] {\n return activeConfigProject()?.routes ?? []\n}\n\n// Redirects are editorial DATA now (EC-303), managed in the studio Redirects rail\n// and served dynamically — no longer resolved from config. `resolveDeploymentRedirects`\n// is retired; static/infra redirects are the dev's job via native next.config/vercel.\n\n/**\n * The configured locales of the active config project (AD-8). The same locale\n * set the host stamps onto its per-locale cache tags — used by the revalidation\n * webhook layer (EC-192) so a publish always invalidates EVERY configured locale\n * (e.g. `/de`, `/fr`), never just the default. `[]` when wired by env alone.\n */\nexport function resolveDeploymentLocales(): string[] {\n return activeConfigProject()?.locales.locales ?? []\n}\n\n/**\n * The active config project's `LocaleConfig` (EC-357 finding #3). Locales are\n * structure and structure is code: when `elytra.config.ts` declares them, they\n * drive the studio's ambient locale switcher in EVERY mode — the CMS surface must\n * not fall back to the sample `en/de` fixture (playground, or a connected first\n * run with nothing persisted) when the config says otherwise. `undefined` when no\n * config project is active (env-only wiring keeps the existing kit/sample\n * fallbacks).\n *\n * EC-519: this used to REASSEMBLE the config's flat `locales` + `defaultLocale`\n * pair into a `LocaleConfig`, and had grown its own repair rule — an absent or\n * out-of-set default fell back to `locales[0]`. That is a fourth place a locale\n * could be invented, in the surface furthest from the author. The resolver now\n * hands over the same `LocaleConfig` the delivery host takes, so there is nothing\n * to reassemble and nothing to repair: a default outside the set is a config\n * ERROR (`default-locale-not-in-locales`) and the project never resolves.\n */\nexport function resolveDeploymentLocaleConfig(): LocaleConfig | undefined {\n return activeConfigProject()?.locales\n}\n\n/**\n * The active project's components reference (EC-194, AD-11): where the consuming\n * repo's OWN real components live (`{ package }` or `{ dir }`). When set, the\n * studio registry composes with the repo's components instead of the built-in\n * demo fixtures — so the page builder edits the repo's actual component library.\n * `undefined` → the project ships no code components; the studio falls back to the\n * golden-path fixtures.\n */\nexport function resolveDeploymentComponents() {\n return activeConfigProject()?.components\n}\n\n/**\n * The active project's public site URL (EC-215): the live site's base URL the\n * studio links to (a document's live URL + open-live button, EC-216). Resolved\n * in lockstep with {@link resolveDeploymentUrl} — the environment bound to the\n * active deployment (the first with a wired deployment) provides the URL, falling\n * back to the project-level `frontendUrl`. No active-environment selector exists\n * yet, so the bound environment is the active one. `undefined` when the active\n * project (or env-only wiring) configures no frontend URL.\n */\nexport function resolveDeploymentFrontendUrl(): string | undefined {\n const project = activeConfigProject()\n if (!project) return undefined\n const boundEnv = project.environments.find((e) => e.convex?.deployment)\n return frontendUrlForEnvironment(project, boundEnv?.key)\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry';
1
+ import type { ComponentFixture, ComponentIssue, ComponentManifest } from '@elytracms/core/component-registry';
2
2
  import type { ComponentImplementations } from '@elytracms/core/runtime-renderer';
3
3
  import type { StudioRegistry } from '../lib/registry/index.js';
4
4
  /**
@@ -23,13 +23,19 @@ export interface PreviewEntrySpec {
23
23
  /** The fixture name, or {@link DEFAULT_VARIANT} for a fixture-less manifest. */
24
24
  fixture: string;
25
25
  /**
26
- * The render-relevant inputs the CLI hashes (with the engine version) for
27
- * staleness: the manifest's prop + slot shape (defaults live in the prop
28
- * field-defs, so seeded renders are covered) and the declared fixture's own
29
- * props/slotChildren (`fixture: null` for the implicit Default variant).
30
- * Deliberately NOT hashed: the component implementation and the theme —
31
- * drift there is caught by regeneration runs, not by the hash (the ratified
32
- * EC-440 staleness split).
26
+ * The DECLARED render inputs the CLI hashes per entry for staleness: the
27
+ * manifest's prop + slot shape (defaults live in the prop field-defs, so
28
+ * seeded renders are covered) and the declared fixture's own
29
+ * props/slotChildren (`fixture: null` for the implicit Default variant). A
30
+ * change here re-renders one entry.
31
+ *
32
+ * NOT in here: the component implementation and the theme — a config arrives
33
+ * evaluated, so neither has a per-entry identity. The CLI covers them as
34
+ * manifest-level render dependencies instead (EC-600: content hashes of the
35
+ * project source files the config imports plus the theme `@import` chain),
36
+ * and a change there re-renders every entry. Before EC-600 this comment said
37
+ * regeneration caught such drift; it did not — regeneration only rendered what
38
+ * this hash called stale.
33
39
  */
34
40
  hashInput: {
35
41
  manifest: {
@@ -50,12 +56,24 @@ export interface PreviewEntrySpec {
50
56
  * id). A config that fails shape-resolution contributes no project components
51
57
  * — the primitives still enumerate, and the CLI surfaces config problems
52
58
  * through the studio/doctor, not here.
59
+ *
60
+ * EC-555: the kept entries' implementations (`unknown` in the config) go through
61
+ * core's one registration step, like the studio registry's and the Next host's —
62
+ * a non-component is withheld (its preview renders the missing-component
63
+ * fallback, never `createElement`) and returned as an `invalid-implementation`
64
+ * issue. The first-wins dedupe runs BEFORE the check, so a withheld entry still
65
+ * claims its id.
53
66
  */
54
67
  export declare function previewComponentsFromConfig(config: unknown): {
55
68
  manifests: ComponentManifest[];
56
69
  implementations: ComponentImplementations;
70
+ issues: ComponentIssue[];
57
71
  };
58
- /** The {@link StudioRegistry}-shaped registry the harness renders against. */
72
+ /**
73
+ * The {@link StudioRegistry}-shaped registry the harness renders against. Its
74
+ * `registry.issues` carries the implementation issues too — the same list the
75
+ * studio registry records them into (EC-555).
76
+ */
59
77
  export declare function previewRegistryFromConfig(config: unknown): StudioRegistry;
60
78
  /**
61
79
  * Enumerate every generated-preview entry for a config: canvas-eligible