@elytracms/studio 0.0.90 → 0.0.91

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 (159) hide show
  1. package/convex/asset-revalidate.test.ts +14 -1
  2. package/convex/cms.ts +3 -4
  3. package/convex/document-revalidate.test.ts +14 -1
  4. package/convex/redirect-revalidate.test.ts +14 -1
  5. package/dist/components/assets/asset-selection.d.ts +8 -9
  6. package/dist/components/assets/asset-selection.js +26 -16
  7. package/dist/components/assets/asset-selection.js.map +1 -1
  8. package/dist/components/builder/blocks-model.d.ts +7 -5
  9. package/dist/components/builder/blocks-model.js +11 -9
  10. package/dist/components/builder/blocks-model.js.map +1 -1
  11. package/dist/components/builder/blocks-panel.js +1 -1
  12. package/dist/components/builder/blocks-panel.js.map +1 -1
  13. package/dist/components/builder/builder-store.d.ts +11 -3
  14. package/dist/components/builder/builder-store.js +12 -5
  15. package/dist/components/builder/builder-store.js.map +1 -1
  16. package/dist/components/builder/canvas-dnd.d.ts +7 -1
  17. package/dist/components/builder/canvas-dnd.js +6 -3
  18. package/dist/components/builder/canvas-dnd.js.map +1 -1
  19. package/dist/components/builder/canvas-render-context.d.ts +13 -1
  20. package/dist/components/builder/canvas-render-context.js +28 -18
  21. package/dist/components/builder/canvas-render-context.js.map +1 -1
  22. package/dist/components/builder/canvas.js +1 -1
  23. package/dist/components/builder/canvas.js.map +1 -1
  24. package/dist/components/builder/clipboard-actions.d.ts +0 -6
  25. package/dist/components/builder/clipboard-actions.js +9 -4
  26. package/dist/components/builder/clipboard-actions.js.map +1 -1
  27. package/dist/components/builder/composition-field-editor.d.ts +22 -0
  28. package/dist/components/builder/composition-field-editor.js +47 -8
  29. package/dist/components/builder/composition-field-editor.js.map +1 -1
  30. package/dist/components/builder/inline-edit.d.ts +7 -4
  31. package/dist/components/builder/inline-edit.js +11 -8
  32. package/dist/components/builder/inline-edit.js.map +1 -1
  33. package/dist/components/builder/insert-context.js +9 -5
  34. package/dist/components/builder/insert-context.js.map +1 -1
  35. package/dist/components/builder/layout-inspector.d.ts +2 -2
  36. package/dist/components/builder/layout-inspector.js +8 -6
  37. package/dist/components/builder/layout-inspector.js.map +1 -1
  38. package/dist/components/builder/node-factory.d.ts +31 -14
  39. package/dist/components/builder/node-factory.js +73 -93
  40. package/dist/components/builder/node-factory.js.map +1 -1
  41. package/dist/components/builder/page-tree.d.ts +3 -1
  42. package/dist/components/builder/page-tree.js +6 -4
  43. package/dist/components/builder/page-tree.js.map +1 -1
  44. package/dist/components/builder/property-inspector.js +2 -1
  45. package/dist/components/builder/property-inspector.js.map +1 -1
  46. package/dist/components/builder/reuse.d.ts +1 -1
  47. package/dist/components/builder/reuse.js +8 -5
  48. package/dist/components/builder/reuse.js.map +1 -1
  49. package/dist/components/builder/selection-overlay.d.ts +2 -2
  50. package/dist/components/builder/selection-overlay.js +3 -3
  51. package/dist/components/builder/selection-overlay.js.map +1 -1
  52. package/dist/components/builder/spacing-handles.d.ts +2 -2
  53. package/dist/components/builder/spacing-handles.js +3 -3
  54. package/dist/components/builder/spacing-handles.js.map +1 -1
  55. package/dist/components/cms/asset-field.d.ts +1 -1
  56. package/dist/components/cms/asset-field.js +15 -13
  57. package/dist/components/cms/asset-field.js.map +1 -1
  58. package/dist/components/cms/asset-picker-core.js +3 -4
  59. package/dist/components/cms/asset-picker-core.js.map +1 -1
  60. package/dist/components/cms/detail-editor.js +10 -5
  61. package/dist/components/cms/detail-editor.js.map +1 -1
  62. package/dist/components/cms/discard-draft.d.ts +1 -2
  63. package/dist/components/cms/discard-draft.js +1 -1
  64. package/dist/components/cms/discard-draft.js.map +1 -1
  65. package/dist/components/cms/editable-cell.js +11 -7
  66. package/dist/components/cms/editable-cell.js.map +1 -1
  67. package/dist/components/cms/field-control-lib.d.ts +10 -0
  68. package/dist/components/cms/field-control-lib.js +20 -0
  69. package/dist/components/cms/field-control-lib.js.map +1 -1
  70. package/dist/components/cms/field-control.js +3 -3
  71. package/dist/components/cms/field-control.js.map +1 -1
  72. package/dist/components/cms/object-array-field.js +3 -5
  73. package/dist/components/cms/object-array-field.js.map +1 -1
  74. package/dist/components/cms/prop-fields.d.ts +16 -3
  75. package/dist/components/cms/prop-fields.js +57 -12
  76. package/dist/components/cms/prop-fields.js.map +1 -1
  77. package/dist/components/cms/rich-text/embed-props.d.ts +6 -1
  78. package/dist/components/cms/rich-text/embed-props.js +7 -12
  79. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  80. package/dist/components/cms/rich-text/insert.d.ts +13 -6
  81. package/dist/components/cms/rich-text/insert.js +16 -17
  82. package/dist/components/cms/rich-text/insert.js.map +1 -1
  83. package/dist/components/cms/use-cell-edit.js +7 -12
  84. package/dist/components/cms/use-cell-edit.js.map +1 -1
  85. package/dist/components/cms/use-discard-controller.d.ts +1 -2
  86. package/dist/components/cms/use-discard-controller.js +1 -2
  87. package/dist/components/cms/use-discard-controller.js.map +1 -1
  88. package/dist/components/cms/versions-pane.js +2 -2
  89. package/dist/components/cms/versions-pane.js.map +1 -1
  90. package/dist/components/components/component-detail.d.ts +1 -1
  91. package/dist/components/components/component-detail.js +2 -4
  92. package/dist/components/components/component-detail.js.map +1 -1
  93. package/dist/components/components/component-preview.js +9 -4
  94. package/dist/components/components/component-preview.js.map +1 -1
  95. package/dist/components/components/components-index.d.ts +2 -2
  96. package/dist/components/components/components-index.js +3 -6
  97. package/dist/components/components/components-index.js.map +1 -1
  98. package/dist/components/components/field-inspector.js +5 -2
  99. package/dist/components/components/field-inspector.js.map +1 -1
  100. package/dist/components/components/registry-browser.d.ts +6 -5
  101. package/dist/components/components/registry-browser.js +11 -37
  102. package/dist/components/components/registry-browser.js.map +1 -1
  103. package/dist/components/components/taxonomy.d.ts +14 -21
  104. package/dist/components/components/taxonomy.js +5 -29
  105. package/dist/components/components/taxonomy.js.map +1 -1
  106. package/dist/components/components/transfer.d.ts +10 -8
  107. package/dist/components/components/transfer.js +17 -13
  108. package/dist/components/components/transfer.js.map +1 -1
  109. package/dist/lib/ai/constraints.d.ts +16 -14
  110. package/dist/lib/ai/constraints.js +64 -48
  111. package/dist/lib/ai/constraints.js.map +1 -1
  112. package/dist/lib/ai/update-fields.js +5 -3
  113. package/dist/lib/ai/update-fields.js.map +1 -1
  114. package/dist/lib/assets/asset-placements.d.ts +3 -10
  115. package/dist/lib/assets/asset-placements.js +17 -8
  116. package/dist/lib/assets/asset-placements.js.map +1 -1
  117. package/dist/lib/clipboard/paste-target.js +3 -3
  118. package/dist/lib/clipboard/paste-target.js.map +1 -1
  119. package/dist/lib/cms-views/view-model.js +11 -8
  120. package/dist/lib/cms-views/view-model.js.map +1 -1
  121. package/dist/lib/data/collection-content.d.ts +10 -7
  122. package/dist/lib/data/collection-content.js +34 -15
  123. package/dist/lib/data/collection-content.js.map +1 -1
  124. package/dist/lib/dnd/drop-rules.d.ts +22 -7
  125. package/dist/lib/dnd/drop-rules.js +21 -14
  126. package/dist/lib/dnd/drop-rules.js.map +1 -1
  127. package/dist/lib/engine-version.d.ts +1 -1
  128. package/dist/lib/engine-version.js +1 -1
  129. package/dist/lib/engine-version.js.map +1 -1
  130. package/dist/lib/i18n/messages.de.js +25 -9
  131. package/dist/lib/i18n/messages.de.js.map +1 -1
  132. package/dist/lib/i18n/messages.en.d.ts +21 -7
  133. package/dist/lib/i18n/messages.en.js +28 -9
  134. package/dist/lib/i18n/messages.en.js.map +1 -1
  135. package/dist/lib/operations/cms-persistence.js +7 -4
  136. package/dist/lib/operations/cms-persistence.js.map +1 -1
  137. package/dist/lib/persona/labels.d.ts +4 -9
  138. package/dist/lib/persona/labels.js +5 -12
  139. package/dist/lib/persona/labels.js.map +1 -1
  140. package/dist/lib/references/reference-model.d.ts +0 -2
  141. package/dist/lib/references/reference-model.js +3 -5
  142. package/dist/lib/references/reference-model.js.map +1 -1
  143. package/dist/lib/registry/__fixtures__/demo-components.d.ts +32 -0
  144. package/dist/lib/registry/{project-components.js → __fixtures__/demo-components.js} +18 -5
  145. package/dist/lib/registry/__fixtures__/demo-components.js.map +1 -0
  146. package/dist/lib/registry/index.d.ts +9 -13
  147. package/dist/lib/registry/index.js +34 -61
  148. package/dist/lib/registry/index.js.map +1 -1
  149. package/dist/lib/registry/preview.d.ts +22 -7
  150. package/dist/lib/registry/preview.js +26 -89
  151. package/dist/lib/registry/preview.js.map +1 -1
  152. package/dist/previews/enumerate.d.ts +23 -27
  153. package/dist/previews/enumerate.js +27 -48
  154. package/dist/previews/enumerate.js.map +1 -1
  155. package/dist/previews/harness.js +1 -1
  156. package/dist/previews/harness.js.map +1 -1
  157. package/package.json +4 -3
  158. package/dist/lib/registry/project-components.d.ts +0 -16
  159. package/dist/lib/registry/project-components.js.map +0 -1
@@ -1,72 +1,45 @@
1
- import { ComponentRegistry, manifestPropFields } from '@elytracms/core/component-registry';
2
- import { basePrimitives, getImplementation, registerImplementations } from '@elytracms/core/runtime-renderer';
3
- import { getActiveProjectSlug, resolveDeploymentComponents } from '../workspace/index.js';
4
- import { projectComponentImplementations, projectComponentManifests } from './project-components.js';
1
+ import { manifestPropFields } from '@elytracms/core/component-registry';
2
+ import { getImplementation, registerComponents } from '@elytracms/core/runtime-renderer';
3
+ import { resolveDeploymentComponents } from '../workspace/index.js';
5
4
  /**
6
- * EC-555: the config's implementations are `unknown` (`HostComponentEntry`), so
7
- * they go through core's one registration step — the same one the Next host
8
- * uses (EC-541) — instead of a cast: a non-component is withheld (its nodes
9
- * take the renderer's missing-component path, never `createElement`) and
10
- * recorded as an `invalid-implementation` issue. The fixture set below is
11
- * engine-owned and typed as components, so it needs no runtime check.
5
+ * EC-194/EC-208/EC-223, reshaped by EC-591: the consuming repo's component list,
6
+ * INJECTED through config-as-code (the mount contract) — the active project's
7
+ * `components`, the live (manifest + implementation) pairs its `elytra.config.ts`
8
+ * hands in (the components Elytra ships are an opt-in package listed there:
9
+ * `[...elytraComponents, ...siteComponents]`). The studio registers NOTHING of its
10
+ * own: no base primitives and no demo fallback. A project that lists no components
11
+ * gets an empty palette — the canvas says why (its role notices) — never a set of
12
+ * demo components the delivery host would render as missing-component fallbacks
13
+ * (the EC-591 worst row).
14
+ *
15
+ * The list goes through core's ONE registration step `registerComponents` — the
16
+ * same call the Next host and the preview harness make — so a duplicate id, a role
17
+ * claim or an `invalid-implementation` is the identical issue on every surface, in
18
+ * `registry.issues` (the one record the status readers below consult).
12
19
  */
13
- function projectComponents() {
14
- const components = resolveDeploymentComponents();
15
- if (components && components.length > 0) {
16
- const { implementations, issues } = registerImplementations(components);
17
- return { manifests: components.map((c) => c.manifest), implementations, issues };
18
- }
19
- return {
20
- manifests: projectComponentManifests,
21
- implementations: projectComponentImplementations,
22
- issues: [],
23
- };
24
- }
25
- /** Compose a StudioRegistry from the base primitives plus a project component set. */
26
- function buildStudioRegistry(project) {
27
- const base = basePrimitives();
28
- // Inline-editable opt-ins (EC-158) live directly on the base-primitive
29
- // manifests (`Heading.text`, `Text.value`) in `@elytracms/core/runtime-renderer`.
30
- const registry = new ComponentRegistry([...base.manifests, ...project.manifests]);
31
- // EC-555: implementation issues join the registry's OWN list, because that is
32
- // the one list every studio status reader consults — `componentStatus` below
33
- // (→ 'invalid'), the Components surface, and the blocks panel tile, which
34
- // prints each issue's message. A second list would be a second reader.
35
- registry.issues.push(...project.issues);
36
- return {
37
- registry,
38
- // Project wins over base by spread (a project entry under a `base.*` id
39
- // replaces the primitive; a WITHHELD one leaves the primitive in place).
40
- implementations: { ...base.implementations, ...project.implementations },
41
- };
20
+ let componentsForTests = null;
21
+ /**
22
+ * Test seam: register this list instead of the active project's config list
23
+ * (`null` restores the config). Never used by app code — the studio's components
24
+ * come from `elytra.config.ts`.
25
+ */
26
+ export function setStudioComponentsForTests(entries) {
27
+ componentsForTests = entries;
28
+ cached = null;
42
29
  }
30
+ const NO_COMPONENTS = [];
43
31
  let cached = null;
44
- let cachedSlug = null;
32
+ let cachedList = null;
45
33
  export function getStudioRegistry() {
46
- // Re-derive when the active project changes — different projects may register
47
- // different component libraries (per-project registry, AD-8).
48
- const slug = getActiveProjectSlug();
49
- if (cached && cachedSlug === slug)
34
+ // Re-derive when the list changes — a different active project (per-project
35
+ // registry, AD-8) or a re-mounted config hands over a different list.
36
+ const list = componentsForTests ?? resolveDeploymentComponents() ?? NO_COMPONENTS;
37
+ if (cached && cachedList === list)
50
38
  return cached;
51
- cached = buildStudioRegistry(projectComponents());
52
- cachedSlug = slug;
39
+ cached = registerComponents(list);
40
+ cachedList = list;
53
41
  return cached;
54
42
  }
55
- /**
56
- * The built-in demo fixtures as a StudioRegistry, independent of the active
57
- * project's `components` config ref. This is the golden-path component set
58
- * (`project.MarketingHero`, `FeatureCard`, `PricingCallout`, `LegacyBanner`) used
59
- * when a project declares no ref — and the deterministic registry that blocks /
60
- * clipboard / thumbnail tests pin to, so they assert against a known component set
61
- * regardless of which library `elytra.config.ts` happens to select (EC-194).
62
- */
63
- export function fixtureStudioRegistry() {
64
- return buildStudioRegistry({
65
- manifests: projectComponentManifests,
66
- implementations: projectComponentImplementations,
67
- issues: [],
68
- });
69
- }
70
43
  /**
71
44
  * Adapt the live registry to the cms-core {@link PropFieldLookup} contract (EC-277):
72
45
  * a component id → its prop field-defs, name-injected. Threaded into the studio's
@@ -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 { 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"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/registry/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAGvE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAExF,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAA;AAc1D;;;;;;;;;;;;;;;GAeG;AACH,IAAI,kBAAkB,GAAqC,IAAI,CAAA;AAE/D;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,OAAyC;IACnF,kBAAkB,GAAG,OAAO,CAAA;IAC5B,MAAM,GAAG,IAAI,CAAA;AACf,CAAC;AAED,MAAM,aAAa,GAA8B,EAAE,CAAA;AACnD,IAAI,MAAM,GAA0B,IAAI,CAAA;AACxC,IAAI,UAAU,GAAqC,IAAI,CAAA;AAEvD,MAAM,UAAU,iBAAiB;IAC/B,4EAA4E;IAC5E,sEAAsE;IACtE,MAAM,IAAI,GAAG,kBAAkB,IAAI,2BAA2B,EAAE,IAAI,aAAa,CAAA;IACjF,IAAI,MAAM,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,MAAM,CAAA;IAChD,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAA;IACjC,UAAU,GAAG,IAAI,CAAA;IACjB,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACxC,OAAO,CAAC,WAAW,EAAE,EAAE;QACrB,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;QAClD,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAA;QAC/B,OAAO,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;IAC7F,CAAC,CAAA;AACH,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,cAAc,CAAC,MAAsB;IACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAC,eAAe,EAAE,CAAA;AAC/E,CAAC;AAYD,MAAM,UAAU,eAAe,CAC7B,MAAsB,EACtB,QAA2B;IAE3B,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,SAAS,CAAA;IACvF,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,eAAe,EAAE,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,cAAc,CAAA;IAClF,OAAO,OAAO,CAAA;AAChB,CAAC;AASD,MAAM,UAAU,cAAc,CAAC,MAAsB;IACnD,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IAClC,MAAM,MAAM,GAAmB,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAA;IAC3F,KAAK,MAAM,CAAC,IAAI,GAAG;QAAE,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IAC5D,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import { manifestPropFields } from '@elytracms/core/component-registry'\nimport type { ComponentManifest, ComponentRegistry } from '@elytracms/core/component-registry'\nimport type { PropFieldLookup } from '@elytracms/core/cms-core'\nimport { getImplementation, registerComponents } from '@elytracms/core/runtime-renderer'\nimport type { ComponentEntry, ComponentImplementations, RenderContext } from '@elytracms/core/runtime-renderer'\nimport { resolveDeploymentComponents } from '../workspace'\n\n/**\n * The Studio's view of a project's component registry (brief §7.2, §8.3): the\n * project's component LIST — exactly what its config's `components` names — with\n * the 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, reshaped by EC-591: the consuming repo's component list,\n * INJECTED through config-as-code (the mount contract) — the active project's\n * `components`, the live (manifest + implementation) pairs its `elytra.config.ts`\n * hands in (the components Elytra ships are an opt-in package listed there:\n * `[...elytraComponents, ...siteComponents]`). The studio registers NOTHING of its\n * own: no base primitives and no demo fallback. A project that lists no components\n * gets an empty palette — the canvas says why (its role notices) — never a set of\n * demo components the delivery host would render as missing-component fallbacks\n * (the EC-591 worst row).\n *\n * The list goes through core's ONE registration step `registerComponents` — the\n * same call the Next host and the preview harness make — so a duplicate id, a role\n * claim or an `invalid-implementation` is the identical issue on every surface, in\n * `registry.issues` (the one record the status readers below consult).\n */\nlet componentsForTests: readonly ComponentEntry[] | null = null\n\n/**\n * Test seam: register this list instead of the active project's config list\n * (`null` restores the config). Never used by app code — the studio's components\n * come from `elytra.config.ts`.\n */\nexport function setStudioComponentsForTests(entries: readonly ComponentEntry[] | null): void {\n componentsForTests = entries\n cached = null\n}\n\nconst NO_COMPONENTS: readonly ComponentEntry[] = []\nlet cached: StudioRegistry | null = null\nlet cachedList: readonly ComponentEntry[] | null = null\n\nexport function getStudioRegistry(): StudioRegistry {\n // Re-derive when the list changes — a different active project (per-project\n // registry, AD-8) or a re-mounted config hands over a different list.\n const list = componentsForTests ?? resolveDeploymentComponents() ?? NO_COMPONENTS\n if (cached && cachedList === list) return cached\n cached = registerComponents(list)\n cachedList = list\n return cached\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"]}
@@ -1,15 +1,30 @@
1
1
  import type { ComponentNode, JsonValue } from '@elytracms/core/project-graph';
2
- import type { ComponentManifest } from '@elytracms/core/component-registry';
2
+ import type { ComponentManifest, ComponentRegistry } from '@elytracms/core/component-registry';
3
3
  /** A named, renderable preview state for a component. */
4
4
  export interface PreviewSpec {
5
5
  name: string;
6
6
  node: ComponentNode;
7
7
  }
8
- /** Build a node from a manifest's defaults, overlaying any provided prop values. */
9
- export declare function nodeFromProps(manifest: ComponentManifest, overrides: Record<string, JsonValue>, id: string): ComponentNode;
10
8
  /**
11
- * Resolve the preview states for a component (brief §7.2, §8.3, EC-054):
12
- * declared manifest fixtures first; otherwise authored primitive previews;
13
- * otherwise a single default-props preview. Deterministic ids throughout.
9
+ * A node carrying exactly the given prop values (EC-591 / ratified 2f): a prop
10
+ * the caller does not set stays ABSENT — the implementation's own default applies
11
+ * — and the manifest `default` is never copied in (it is an authoring hint). Until
12
+ * EC-591 this overlaid the overrides on every manifest default, so a fixture
13
+ * insert stored — and rendered — something other than the same fixture pasted
14
+ * from "copy exact configuration" (COMPOSITION #19).
15
+ *
16
+ * Node ids are derived deterministically from a stable base (manifest id +
17
+ * fixture index) so previews are byte-identical across SSR and client hydration
18
+ * and reproducible across runs — no module-level counter, no time/random.
14
19
  */
15
- export declare function previewSpecs(manifest: ComponentManifest): PreviewSpec[];
20
+ export declare function nodeFromProps(manifest: ComponentManifest, values: Record<string, JsonValue>, id: string): ComponentNode;
21
+ /**
22
+ * Resolve the preview states for a component (brief §7.2, §8.3, EC-054): the
23
+ * declared manifest fixtures; otherwise the single `Default` — the SAME seeded
24
+ * node the blocks panel inserts and `elytra previews` renders for a fixture-less
25
+ * component (a layout container comes pre-filled through its role, EC-383).
26
+ * EC-591: there are no authored previews keyed by engine ids any more — a
27
+ * component that wants a richer preview declares `fixtures`, like any other.
28
+ * Deterministic ids throughout.
29
+ */
30
+ export declare function previewSpecs(manifest: ComponentManifest, registry: ComponentRegistry): PreviewSpec[];
@@ -1,110 +1,47 @@
1
- import { staticProp } from '@elytracms/core/project-graph';
1
+ import { cloneWithNewIds, staticProp } from '@elytracms/core/project-graph';
2
+ import { seededNodeFromManifest } from '../../components/builder/node-factory.js';
2
3
  /**
4
+ * A node carrying exactly the given prop values (EC-591 / ratified 2f): a prop
5
+ * the caller does not set stays ABSENT — the implementation's own default applies
6
+ * — and the manifest `default` is never copied in (it is an authoring hint). Until
7
+ * EC-591 this overlaid the overrides on every manifest default, so a fixture
8
+ * insert stored — and rendered — something other than the same fixture pasted
9
+ * from "copy exact configuration" (COMPOSITION #19).
10
+ *
3
11
  * Node ids are derived deterministically from a stable base (manifest id +
4
12
  * fixture index) so previews are byte-identical across SSR and client hydration
5
13
  * and reproducible across runs — no module-level counter, no time/random.
6
14
  */
7
- function text(value, id) {
8
- return { id, componentId: 'base.Text', props: { value: staticProp(value) } };
9
- }
10
- /** Build a node from a manifest's defaults, overlaying any provided prop values. */
11
- export function nodeFromProps(manifest, overrides, id) {
15
+ export function nodeFromProps(manifest, values, id) {
12
16
  const props = {};
13
- for (const [name, spec] of Object.entries(manifest.props)) {
14
- const value = name in overrides ? overrides[name] : spec.default;
17
+ for (const name of Object.keys(manifest.props)) {
18
+ const value = values[name];
15
19
  if (value !== undefined)
16
20
  props[name] = staticProp(value);
17
21
  }
18
22
  return { id, componentId: manifest.id, props };
19
23
  }
20
- function nodeFromPropsRaw(componentId, props, id) {
21
- const out = {};
22
- for (const [k, v] of Object.entries(props))
23
- out[k] = staticProp(v);
24
- return { id, componentId, props: out };
25
- }
26
- /**
27
- * Authored preview nodes for the base primitives (which ship no fixtures of their
28
- * own). Slot-bearing primitives get sample children so previews aren't blank.
29
- * Each builder receives a deterministic `base` id prefix.
30
- */
31
- const PRIMITIVE_PREVIEWS = {
32
- 'base.Container': (base) => [
33
- {
34
- name: 'With content',
35
- node: {
36
- id: base,
37
- componentId: 'base.Container',
38
- slots: { children: [text('Container content', `${base}-c0`)] },
39
- },
40
- },
41
- ],
42
- 'base.Stack': (base) => [
43
- {
44
- name: 'Gap 4',
45
- node: {
46
- id: base,
47
- componentId: 'base.Stack',
48
- props: { gap: staticProp(4) },
49
- slots: {
50
- children: [
51
- text('Item one', `${base}-c0`),
52
- text('Item two', `${base}-c1`),
53
- text('Item three', `${base}-c2`),
54
- ],
55
- },
56
- },
57
- },
58
- ],
59
- 'base.Grid': (base) => [
60
- {
61
- name: '2 columns',
62
- node: {
63
- id: base,
64
- componentId: 'base.Grid',
65
- props: { columns: staticProp(2), gap: staticProp(3) },
66
- slots: {
67
- children: [
68
- text('Cell A', `${base}-c0`),
69
- text('Cell B', `${base}-c1`),
70
- text('Cell C', `${base}-c2`),
71
- text('Cell D', `${base}-c3`),
72
- ],
73
- },
74
- },
75
- },
76
- ],
77
- 'base.Text': (base) => [
78
- { name: 'Sample', node: nodeFromPropsRaw('base.Text', { value: 'The quick brown fox.' }, base) },
79
- ],
80
- 'base.Heading': (base) => [
81
- { name: 'Sample', node: nodeFromPropsRaw('base.Heading', { value: 'A section heading' }, base) },
82
- ],
83
- 'base.Image': (base) => [
84
- {
85
- name: 'Placeholder',
86
- node: nodeFromPropsRaw('base.Image', { src: 'https://placehold.co/320x180', alt: 'Placeholder image' }, base),
87
- },
88
- ],
89
- 'base.Button': (base) => [
90
- { name: 'Sample', node: nodeFromPropsRaw('base.Button', { label: 'Click me' }, base) },
91
- ],
92
- };
93
24
  /**
94
- * Resolve the preview states for a component (brief §7.2, §8.3, EC-054):
95
- * declared manifest fixtures first; otherwise authored primitive previews;
96
- * otherwise a single default-props preview. Deterministic ids throughout.
25
+ * Resolve the preview states for a component (brief §7.2, §8.3, EC-054): the
26
+ * declared manifest fixtures; otherwise the single `Default` — the SAME seeded
27
+ * node the blocks panel inserts and `elytra previews` renders for a fixture-less
28
+ * component (a layout container comes pre-filled through its role, EC-383).
29
+ * EC-591: there are no authored previews keyed by engine ids any more — a
30
+ * component that wants a richer preview declares `fixtures`, like any other.
31
+ * Deterministic ids throughout.
97
32
  */
98
- export function previewSpecs(manifest) {
33
+ export function previewSpecs(manifest, registry) {
99
34
  if (manifest.fixtures && manifest.fixtures.length > 0) {
100
35
  return manifest.fixtures.map((fixture, i) => ({
101
36
  name: fixture.name,
102
37
  node: nodeFromProps(manifest, fixture.props ?? {}, `preview-${manifest.id}-${i}`),
103
38
  }));
104
39
  }
105
- const authored = PRIMITIVE_PREVIEWS[manifest.id];
106
- if (authored)
107
- return authored(`preview-${manifest.id}-0`);
108
- return [{ name: 'Default', node: nodeFromProps(manifest, {}, `preview-${manifest.id}-0`) }];
40
+ // The seed's placeholder ids are re-minted deterministically (root `…-0`, then
41
+ // `…-0-1`, `…-0-2` …) so every node in the preview tree has a distinct, stable id.
42
+ const base = `preview-${manifest.id}-0`;
43
+ let n = 0;
44
+ const node = cloneWithNewIds(seededNodeFromManifest(manifest, registry), () => (n++ === 0 ? base : `${base}-${n - 1}`));
45
+ return [{ name: 'Default', node }];
109
46
  }
110
47
  //# sourceMappingURL=preview.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../../src/lib/registry/preview.ts"],"names":[],"mappings":"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","sourcesContent":["import { staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\n\n/** A named, renderable preview state for a component. */\nexport interface PreviewSpec {\n name: string\n node: ComponentNode\n}\n\n/**\n * Node ids are derived deterministically from a stable base (manifest id +\n * fixture index) so previews are byte-identical across SSR and client hydration\n * and reproducible across runs — no module-level counter, no time/random.\n */\nfunction text(value: string, id: string): ComponentNode {\n return { id, componentId: 'base.Text', props: { value: staticProp(value) } }\n}\n\n/** Build a node from a manifest's defaults, overlaying any provided prop values. */\nexport function nodeFromProps(\n manifest: ComponentManifest,\n overrides: Record<string, JsonValue>,\n id: string,\n): ComponentNode {\n const props: ComponentNode['props'] = {}\n for (const [name, spec] of Object.entries(manifest.props)) {\n const value = name in overrides ? overrides[name] : (spec.default as JsonValue | undefined)\n if (value !== undefined) props[name] = staticProp(value)\n }\n return { id, componentId: manifest.id, props }\n}\n\nfunction nodeFromPropsRaw(componentId: string, props: Record<string, JsonValue>, id: string): ComponentNode {\n const out: ComponentNode['props'] = {}\n for (const [k, v] of Object.entries(props)) out[k] = staticProp(v)\n return { id, componentId, props: out }\n}\n\n/**\n * Authored preview nodes for the base primitives (which ship no fixtures of their\n * own). Slot-bearing primitives get sample children so previews aren't blank.\n * Each builder receives a deterministic `base` id prefix.\n */\nconst PRIMITIVE_PREVIEWS: Record<string, (base: string) => PreviewSpec[]> = {\n 'base.Container': (base) => [\n {\n name: 'With content',\n node: {\n id: base,\n componentId: 'base.Container',\n slots: { children: [text('Container content', `${base}-c0`)] },\n },\n },\n ],\n 'base.Stack': (base) => [\n {\n name: 'Gap 4',\n node: {\n id: base,\n componentId: 'base.Stack',\n props: { gap: staticProp(4) },\n slots: {\n children: [\n text('Item one', `${base}-c0`),\n text('Item two', `${base}-c1`),\n text('Item three', `${base}-c2`),\n ],\n },\n },\n },\n ],\n 'base.Grid': (base) => [\n {\n name: '2 columns',\n node: {\n id: base,\n componentId: 'base.Grid',\n props: { columns: staticProp(2), gap: staticProp(3) },\n slots: {\n children: [\n text('Cell A', `${base}-c0`),\n text('Cell B', `${base}-c1`),\n text('Cell C', `${base}-c2`),\n text('Cell D', `${base}-c3`),\n ],\n },\n },\n },\n ],\n 'base.Text': (base) => [\n { name: 'Sample', node: nodeFromPropsRaw('base.Text', { value: 'The quick brown fox.' }, base) },\n ],\n 'base.Heading': (base) => [\n { name: 'Sample', node: nodeFromPropsRaw('base.Heading', { value: 'A section heading' }, base) },\n ],\n 'base.Image': (base) => [\n {\n name: 'Placeholder',\n node: nodeFromPropsRaw(\n 'base.Image',\n { src: 'https://placehold.co/320x180', alt: 'Placeholder image' },\n base,\n ),\n },\n ],\n 'base.Button': (base) => [\n { name: 'Sample', node: nodeFromPropsRaw('base.Button', { label: 'Click me' }, base) },\n ],\n}\n\n/**\n * Resolve the preview states for a component (brief §7.2, §8.3, EC-054):\n * declared manifest fixtures first; otherwise authored primitive previews;\n * otherwise a single default-props preview. Deterministic ids throughout.\n */\nexport function previewSpecs(manifest: ComponentManifest): PreviewSpec[] {\n if (manifest.fixtures && manifest.fixtures.length > 0) {\n return manifest.fixtures.map((fixture, i) => ({\n name: fixture.name,\n node: nodeFromProps(manifest, fixture.props ?? {}, `preview-${manifest.id}-${i}`),\n }))\n }\n const authored = PRIMITIVE_PREVIEWS[manifest.id]\n if (authored) return authored(`preview-${manifest.id}-0`)\n return [{ name: 'Default', node: nodeFromProps(manifest, {}, `preview-${manifest.id}-0`) }]\n}\n"]}
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../../src/lib/registry/preview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAG3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAA;AAQ9E;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAC3B,QAA2B,EAC3B,MAAiC,EACjC,EAAU;IAEV,MAAM,KAAK,GAA2B,EAAE,CAAA;IACxC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;QAC1B,IAAI,KAAK,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAC1D,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,CAAA;AAChD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,QAA2B,EAAE,QAA2B;IACnF,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,OAAO,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5C,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,IAAI,EAAE,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE,EAAE,WAAW,QAAQ,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC;SAClF,CAAC,CAAC,CAAA;IACL,CAAC;IACD,+EAA+E;IAC/E,mFAAmF;IACnF,MAAM,IAAI,GAAG,WAAW,QAAQ,CAAC,EAAE,IAAI,CAAA;IACvC,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,MAAM,IAAI,GAAG,eAAe,CAAC,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IACvH,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;AACpC,CAAC","sourcesContent":["import { cloneWithNewIds, staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, ComponentRegistry } from '@elytracms/core/component-registry'\nimport { seededNodeFromManifest } from '../../components/builder/node-factory'\n\n/** A named, renderable preview state for a component. */\nexport interface PreviewSpec {\n name: string\n node: ComponentNode\n}\n\n/**\n * A node carrying exactly the given prop values (EC-591 / ratified 2f): a prop\n * the caller does not set stays ABSENT — the implementation's own default applies\n * — and the manifest `default` is never copied in (it is an authoring hint). Until\n * EC-591 this overlaid the overrides on every manifest default, so a fixture\n * insert stored — and rendered — something other than the same fixture pasted\n * from \"copy exact configuration\" (COMPOSITION #19).\n *\n * Node ids are derived deterministically from a stable base (manifest id +\n * fixture index) so previews are byte-identical across SSR and client hydration\n * and reproducible across runs — no module-level counter, no time/random.\n */\nexport function nodeFromProps(\n manifest: ComponentManifest,\n values: Record<string, JsonValue>,\n id: string,\n): ComponentNode {\n const props: ComponentNode['props'] = {}\n for (const name of Object.keys(manifest.props)) {\n const value = values[name]\n if (value !== undefined) props[name] = staticProp(value)\n }\n return { id, componentId: manifest.id, props }\n}\n\n/**\n * Resolve the preview states for a component (brief §7.2, §8.3, EC-054): the\n * declared manifest fixtures; otherwise the single `Default` — the SAME seeded\n * node the blocks panel inserts and `elytra previews` renders for a fixture-less\n * component (a layout container comes pre-filled through its role, EC-383).\n * EC-591: there are no authored previews keyed by engine ids any more — a\n * component that wants a richer preview declares `fixtures`, like any other.\n * Deterministic ids throughout.\n */\nexport function previewSpecs(manifest: ComponentManifest, registry: ComponentRegistry): PreviewSpec[] {\n if (manifest.fixtures && manifest.fixtures.length > 0) {\n return manifest.fixtures.map((fixture, i) => ({\n name: fixture.name,\n node: nodeFromProps(manifest, fixture.props ?? {}, `preview-${manifest.id}-${i}`),\n }))\n }\n // The seed's placeholder ids are re-minted deterministically (root `…-0`, then\n // `…-0-1`, `…-0-2` …) so every node in the preview tree has a distinct, stable id.\n const base = `preview-${manifest.id}-0`\n let n = 0\n const node = cloneWithNewIds(seededNodeFromManifest(manifest, registry), () => (n++ === 0 ? base : `${base}-${n - 1}`))\n return [{ name: 'Default', node }]\n}\n"]}
@@ -1,5 +1,5 @@
1
- import type { ComponentFixture, ComponentIssue, ComponentManifest } from '@elytracms/core/component-registry';
2
- import type { ComponentImplementations } from '@elytracms/core/runtime-renderer';
1
+ import type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry';
2
+ import type { ComponentEntry } from '@elytracms/core/runtime-renderer';
3
3
  import type { StudioRegistry } from '../lib/registry/index.js';
4
4
  /**
5
5
  * EC-440 — the fixture-preview enumeration behind `elytra previews`. ONE
@@ -13,9 +13,10 @@ import type { StudioRegistry } from '../lib/registry/index.js';
13
13
  * manifests + implementations — code), never a persistence adapter, auth, or
14
14
  * content data. The entry set mirrors the blocks panel exactly (EC-439): every
15
15
  * canvas-eligible manifest, one entry per declared fixture, and a single
16
- * implicit {@link DEFAULT_VARIANT} entry for fixture-less manifests. Base
17
- * primitives are included — they are insertable tiles too. Layout-helper
18
- * presets are NOT manifests and keep their placeholder treatment.
16
+ * implicit {@link DEFAULT_VARIANT} entry for fixture-less manifests. Exactly the
17
+ * config's components — the engine adds none (EC-591; the shipped components are
18
+ * there when the config lists `@elytracms/components`). Layout-helper presets are
19
+ * NOT manifests and keep their placeholder treatment.
19
20
  */
20
21
  /** One renderable preview: a component fixture plus its staleness-hash inputs. */
21
22
  export interface PreviewEntrySpec {
@@ -49,30 +50,25 @@ export interface PreviewEntrySpec {
49
50
  };
50
51
  }
51
52
  /**
52
- * The component set a config carries, as the harness registry input: the base
53
- * primitives plus every resolved project's `components` entries, deduped by
54
- * component id (first declaration wins — the generated-preview seam is keyed
55
- * by component id alone, so one image serves every project that registers the
56
- * id). A config that fails shape-resolution contributes no project components
57
- * — the primitives still enumerate, and the CLI surfaces config problems
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
+ * The component list a config carries, as the harness registry input: every
54
+ * resolved project's `components`, concatenated in project order. An entry two
55
+ * projects share (the same `{ manifest, implementation }` object — e.g. both list
56
+ * `...elytraComponents`) is the SAME component and is kept once; two DIFFERENT
57
+ * entries under one id are a real conflict — the generated-preview seam is keyed
58
+ * by component id alone, so one image would serve both — and go to the one
59
+ * registration rule like any duplicate: the first is kept whole, the second is a
60
+ * `duplicate-id` issue (EC-591; before, previews skipped it silently and showed
61
+ * the first project's render in the second project's tile — COMPOSITION #13).
62
+ * A config that fails shape-resolution contributes no components; the CLI
63
+ * surfaces config problems through the studio/doctor, not here.
66
64
  */
67
- export declare function previewComponentsFromConfig(config: unknown): {
68
- manifests: ComponentManifest[];
69
- implementations: ComponentImplementations;
70
- issues: ComponentIssue[];
71
- };
65
+ export declare function previewComponentsFromConfig(config: unknown): readonly ComponentEntry[];
72
66
  /**
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).
67
+ * The {@link StudioRegistry}-shaped registry the harness renders against: the
68
+ * config's list through core's ONE registration step (`registerComponents`, the
69
+ * same call the studio registry and the Next host make — EC-591), so a duplicate,
70
+ * a role claim and an `invalid-implementation` are the identical issue in
71
+ * `registry.issues` on every surface.
76
72
  */
77
73
  export declare function previewRegistryFromConfig(config: unknown): StudioRegistry;
78
74
  /**
@@ -1,63 +1,42 @@
1
- import { ComponentRegistry, isCanvasEligible } from '@elytracms/core/component-registry';
2
- import { basePrimitives, getImplementation, registerImplementations } from '@elytracms/core/runtime-renderer';
1
+ import { isCanvasEligible } from '@elytracms/core/component-registry';
2
+ import { registerComponents } from '@elytracms/core/runtime-renderer';
3
3
  import { resolveWorkspaceConfig } from '@elytracms/core/studio-core';
4
4
  import { DEFAULT_VARIANT } from '../components/builder/blocks-model.js';
5
5
  /**
6
- * The component set a config carries, as the harness registry input: the base
7
- * primitives plus every resolved project's `components` entries, deduped by
8
- * component id (first declaration wins — the generated-preview seam is keyed
9
- * by component id alone, so one image serves every project that registers the
10
- * id). A config that fails shape-resolution contributes no project components
11
- * — the primitives still enumerate, and the CLI surfaces config problems
12
- * through the studio/doctor, not here.
13
- *
14
- * EC-555: the kept entries' implementations (`unknown` in the config) go through
15
- * core's one registration step, like the studio registry's and the Next host's —
16
- * a non-component is withheld (its preview renders the missing-component
17
- * fallback, never `createElement`) and returned as an `invalid-implementation`
18
- * issue. The first-wins dedupe runs BEFORE the check, so a withheld entry still
19
- * claims its id.
6
+ * The component list a config carries, as the harness registry input: every
7
+ * resolved project's `components`, concatenated in project order. An entry two
8
+ * projects share (the same `{ manifest, implementation }` object — e.g. both list
9
+ * `...elytraComponents`) is the SAME component and is kept once; two DIFFERENT
10
+ * entries under one id are a real conflict — the generated-preview seam is keyed
11
+ * by component id alone, so one image would serve both — and go to the one
12
+ * registration rule like any duplicate: the first is kept whole, the second is a
13
+ * `duplicate-id` issue (EC-591; before, previews skipped it silently and showed
14
+ * the first project's render in the second project's tile — COMPOSITION #13).
15
+ * A config that fails shape-resolution contributes no components; the CLI
16
+ * surfaces config problems through the studio/doctor, not here.
20
17
  */
21
18
  export function previewComponentsFromConfig(config) {
22
- const base = basePrimitives();
23
- const manifests = [...base.manifests];
24
- const implementations = {};
25
- for (const manifest of base.manifests) {
26
- const implementation = getImplementation(base.implementations, manifest.id);
27
- if (implementation)
28
- implementations[manifest.id] = implementation;
29
- }
30
- const seen = new Set(manifests.map((m) => m.id));
31
- const kept = [];
32
- const resolved = resolveWorkspaceConfig(config);
33
- for (const project of resolved.projects) {
19
+ const seen = new Set();
20
+ const entries = [];
21
+ for (const project of resolveWorkspaceConfig(config).projects) {
34
22
  for (const entry of project.components ?? []) {
35
- if (seen.has(entry.manifest.id))
23
+ if (seen.has(entry))
36
24
  continue;
37
- seen.add(entry.manifest.id);
38
- manifests.push(entry.manifest);
39
- kept.push(entry);
25
+ seen.add(entry);
26
+ entries.push(entry);
40
27
  }
41
28
  }
42
- // Ids are unique by construction here (deduped against the primitives too),
43
- // so the merge order below decides nothing.
44
- const project = registerImplementations(kept);
45
- return {
46
- manifests,
47
- implementations: { ...implementations, ...project.implementations },
48
- issues: project.issues,
49
- };
29
+ return entries;
50
30
  }
51
31
  /**
52
- * The {@link StudioRegistry}-shaped registry the harness renders against. Its
53
- * `registry.issues` carries the implementation issues too — the same list the
54
- * studio registry records them into (EC-555).
32
+ * The {@link StudioRegistry}-shaped registry the harness renders against: the
33
+ * config's list through core's ONE registration step (`registerComponents`, the
34
+ * same call the studio registry and the Next host make — EC-591), so a duplicate,
35
+ * a role claim and an `invalid-implementation` are the identical issue in
36
+ * `registry.issues` on every surface.
55
37
  */
56
38
  export function previewRegistryFromConfig(config) {
57
- const { manifests, implementations, issues } = previewComponentsFromConfig(config);
58
- const registry = new ComponentRegistry(manifests);
59
- registry.issues.push(...issues);
60
- return { registry, implementations };
39
+ return registerComponents(previewComponentsFromConfig(config));
61
40
  }
62
41
  /**
63
42
  * Enumerate every generated-preview entry for a config: canvas-eligible
@@ -68,7 +47,7 @@ export function previewRegistryFromConfig(config) {
68
47
  * deterministic regardless of registration order.
69
48
  */
70
49
  export function previewEntriesForConfig(config) {
71
- const { manifests } = previewComponentsFromConfig(config);
50
+ const manifests = previewRegistryFromConfig(config).registry.list();
72
51
  const entries = [];
73
52
  for (const manifest of [...manifests].sort((a, b) => a.id.localeCompare(b.id))) {
74
53
  if (!isCanvasEligible(manifest))
@@ -1 +1 @@
1
- {"version":3,"file":"enumerate.js","sourceRoot":"","sources":["../../src/previews/enumerate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType } from 'react'\nimport { ComponentRegistry, isCanvasEligible } from '@elytracms/core/component-registry'\nimport type {\n ComponentFixture,\n ComponentIssue,\n ComponentManifest,\n} from '@elytracms/core/component-registry'\nimport { basePrimitives, getImplementation, registerImplementations } from '@elytracms/core/runtime-renderer'\nimport type { ComponentImplementations } from '@elytracms/core/runtime-renderer'\nimport { resolveWorkspaceConfig } from '@elytracms/core/studio-core'\nimport type { HostComponentEntry } from '@elytracms/core/studio-core'\nimport { DEFAULT_VARIANT } from '../components/builder/blocks-model'\nimport type { StudioRegistry } from '../lib/registry'\n\n/**\n * EC-440 — the fixture-preview enumeration behind `elytra previews`. ONE\n * source of truth for \"which previews exist\" shared by the CLI's node-side\n * driver (hashing, staleness `--check`) and the browser render harness\n * ({@link import('./harness').mountPreviewHarness}) — both derive the entry\n * set from the consumer's `elytra.config.ts` default export through here, so\n * generation, staleness checking, and rendering can never disagree.\n *\n * DATA-FREE by construction: this module reads the consumer CONFIG (registry\n * manifests + implementations — code), never a persistence adapter, auth, or\n * content data. The entry set mirrors the blocks panel exactly (EC-439): every\n * canvas-eligible manifest, one entry per declared fixture, and a single\n * implicit {@link DEFAULT_VARIANT} entry for fixture-less manifests. Base\n * primitives are included — they are insertable tiles too. Layout-helper\n * presets are NOT manifests and keep their placeholder treatment.\n */\n\n/** One renderable preview: a component fixture plus its staleness-hash inputs. */\nexport interface PreviewEntrySpec {\n componentId: string\n /** The fixture name, or {@link DEFAULT_VARIANT} for a fixture-less manifest. */\n fixture: string\n /**\n * The DECLARED render inputs the CLI hashes per entry for staleness: the\n * manifest's prop + slot shape (defaults live in the prop field-defs, so\n * seeded renders are covered) and the declared fixture's own\n * props/slotChildren (`fixture: null` for the implicit Default variant). A\n * change here re-renders one entry.\n *\n * NOT in here: the component implementation and the theme — a config arrives\n * evaluated, so neither has a per-entry identity. The CLI covers them as\n * manifest-level render dependencies instead (EC-600: content hashes of the\n * project source files the config imports plus the theme `@import` chain),\n * and a change there re-renders every entry. Before EC-600 this comment said\n * regeneration caught such drift; it did not — regeneration only rendered what\n * this hash called stale.\n */\n hashInput: {\n manifest: { props: ComponentManifest['props']; slots: ComponentManifest['slots'] }\n fixture: {\n props: NonNullable<ComponentFixture['props']> | null\n slotChildren: NonNullable<ComponentFixture['slotChildren']> | null\n } | null\n }\n}\n\n/**\n * The component set a config carries, as the harness registry input: the base\n * primitives plus every resolved project's `components` entries, deduped by\n * component id (first declaration wins — the generated-preview seam is keyed\n * by component id alone, so one image serves every project that registers the\n * id). A config that fails shape-resolution contributes no project components\n * — the primitives still enumerate, and the CLI surfaces config problems\n * through the studio/doctor, not here.\n *\n * EC-555: the kept entries' implementations (`unknown` in the config) go through\n * core's one registration step, like the studio registry's and the Next host's —\n * a non-component is withheld (its preview renders the missing-component\n * fallback, never `createElement`) and returned as an `invalid-implementation`\n * issue. The first-wins dedupe runs BEFORE the check, so a withheld entry still\n * claims its id.\n */\nexport function previewComponentsFromConfig(config: unknown): {\n manifests: ComponentManifest[]\n implementations: ComponentImplementations\n issues: ComponentIssue[]\n} {\n const base = basePrimitives()\n const manifests: ComponentManifest[] = [...base.manifests]\n const implementations: Record<string, ComponentType<Record<string, unknown>>> = {}\n for (const manifest of base.manifests) {\n const implementation = getImplementation(base.implementations, manifest.id)\n if (implementation) implementations[manifest.id] = implementation\n }\n const seen = new Set(manifests.map((m) => m.id))\n\n const kept: HostComponentEntry[] = []\n const resolved = resolveWorkspaceConfig(config)\n for (const project of resolved.projects) {\n for (const entry of project.components ?? []) {\n if (seen.has(entry.manifest.id)) continue\n seen.add(entry.manifest.id)\n manifests.push(entry.manifest)\n kept.push(entry)\n }\n }\n // Ids are unique by construction here (deduped against the primitives too),\n // so the merge order below decides nothing.\n const project = registerImplementations(kept)\n return {\n manifests,\n implementations: { ...implementations, ...project.implementations },\n issues: project.issues,\n }\n}\n\n/**\n * The {@link StudioRegistry}-shaped registry the harness renders against. Its\n * `registry.issues` carries the implementation issues too — the same list the\n * studio registry records them into (EC-555).\n */\nexport function previewRegistryFromConfig(config: unknown): StudioRegistry {\n const { manifests, implementations, issues } = previewComponentsFromConfig(config)\n const registry = new ComponentRegistry(manifests)\n registry.issues.push(...issues)\n return { registry, implementations }\n}\n\n/**\n * Enumerate every generated-preview entry for a config: canvas-eligible\n * manifests only (server-only components never render on the client canvas),\n * one entry per declared fixture in declaration order, else one implicit\n * {@link DEFAULT_VARIANT} entry. Components are sorted by id so the output —\n * and everything the CLI derives from it (manifest JSON, file writes) — is\n * deterministic regardless of registration order.\n */\nexport function previewEntriesForConfig(config: unknown): PreviewEntrySpec[] {\n const { manifests } = previewComponentsFromConfig(config)\n const entries: PreviewEntrySpec[] = []\n for (const manifest of [...manifests].sort((a, b) => a.id.localeCompare(b.id))) {\n if (!isCanvasEligible(manifest)) continue\n const manifestShape = { props: manifest.props, slots: manifest.slots }\n if (manifest.fixtures && manifest.fixtures.length > 0) {\n for (const fixture of manifest.fixtures) {\n entries.push({\n componentId: manifest.id,\n fixture: fixture.name,\n hashInput: {\n manifest: manifestShape,\n fixture: { props: fixture.props ?? null, slotChildren: fixture.slotChildren ?? null },\n },\n })\n }\n } else {\n entries.push({\n componentId: manifest.id,\n fixture: DEFAULT_VARIANT,\n hashInput: { manifest: manifestShape, fixture: null },\n })\n }\n }\n return entries\n}\n"]}
1
+ {"version":3,"file":"enumerate.js","sourceRoot":"","sources":["../../src/previews/enumerate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AAErE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAErE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAA;AAkDpE;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAe;IACzD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAA;IACtC,MAAM,OAAO,GAAqB,EAAE,CAAA;IACpC,KAAK,MAAM,OAAO,IAAI,sBAAsB,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC9D,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;YAC7C,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAQ;YAC7B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACf,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAe;IACvD,OAAO,kBAAkB,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAAA;AAChE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAe;IACrD,MAAM,SAAS,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IACnE,MAAM,OAAO,GAAuB,EAAE,CAAA;IACtC,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAC/E,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC;YAAE,SAAQ;QACzC,MAAM,aAAa,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAA;QACtE,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtD,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBACxC,OAAO,CAAC,IAAI,CAAC;oBACX,WAAW,EAAE,QAAQ,CAAC,EAAE;oBACxB,OAAO,EAAE,OAAO,CAAC,IAAI;oBACrB,SAAS,EAAE;wBACT,QAAQ,EAAE,aAAa;wBACvB,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,IAAI,EAAE;qBACtF;iBACF,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC;gBACX,WAAW,EAAE,QAAQ,CAAC,EAAE;gBACxB,OAAO,EAAE,eAAe;gBACxB,SAAS,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE;aACtD,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["import { isCanvasEligible } from '@elytracms/core/component-registry'\nimport type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry'\nimport { registerComponents } from '@elytracms/core/runtime-renderer'\nimport type { ComponentEntry } from '@elytracms/core/runtime-renderer'\nimport { resolveWorkspaceConfig } from '@elytracms/core/studio-core'\nimport { DEFAULT_VARIANT } from '../components/builder/blocks-model'\nimport type { StudioRegistry } from '../lib/registry'\n\n/**\n * EC-440 — the fixture-preview enumeration behind `elytra previews`. ONE\n * source of truth for \"which previews exist\" shared by the CLI's node-side\n * driver (hashing, staleness `--check`) and the browser render harness\n * ({@link import('./harness').mountPreviewHarness}) — both derive the entry\n * set from the consumer's `elytra.config.ts` default export through here, so\n * generation, staleness checking, and rendering can never disagree.\n *\n * DATA-FREE by construction: this module reads the consumer CONFIG (registry\n * manifests + implementations — code), never a persistence adapter, auth, or\n * content data. The entry set mirrors the blocks panel exactly (EC-439): every\n * canvas-eligible manifest, one entry per declared fixture, and a single\n * implicit {@link DEFAULT_VARIANT} entry for fixture-less manifests. Exactly the\n * config's components — the engine adds none (EC-591; the shipped components are\n * there when the config lists `@elytracms/components`). Layout-helper presets are\n * NOT manifests and keep their placeholder treatment.\n */\n\n/** One renderable preview: a component fixture plus its staleness-hash inputs. */\nexport interface PreviewEntrySpec {\n componentId: string\n /** The fixture name, or {@link DEFAULT_VARIANT} for a fixture-less manifest. */\n fixture: string\n /**\n * The DECLARED render inputs the CLI hashes per entry for staleness: the\n * manifest's prop + slot shape (defaults live in the prop field-defs, so\n * seeded renders are covered) and the declared fixture's own\n * props/slotChildren (`fixture: null` for the implicit Default variant). A\n * change here re-renders one entry.\n *\n * NOT in here: the component implementation and the theme — a config arrives\n * evaluated, so neither has a per-entry identity. The CLI covers them as\n * manifest-level render dependencies instead (EC-600: content hashes of the\n * project source files the config imports plus the theme `@import` chain),\n * and a change there re-renders every entry. Before EC-600 this comment said\n * regeneration caught such drift; it did not — regeneration only rendered what\n * this hash called stale.\n */\n hashInput: {\n manifest: { props: ComponentManifest['props']; slots: ComponentManifest['slots'] }\n fixture: {\n props: NonNullable<ComponentFixture['props']> | null\n slotChildren: NonNullable<ComponentFixture['slotChildren']> | null\n } | null\n }\n}\n\n/**\n * The component list a config carries, as the harness registry input: every\n * resolved project's `components`, concatenated in project order. An entry two\n * projects share (the same `{ manifest, implementation }` object — e.g. both list\n * `...elytraComponents`) is the SAME component and is kept once; two DIFFERENT\n * entries under one id are a real conflict — the generated-preview seam is keyed\n * by component id alone, so one image would serve both — and go to the one\n * registration rule like any duplicate: the first is kept whole, the second is a\n * `duplicate-id` issue (EC-591; before, previews skipped it silently and showed\n * the first project's render in the second project's tile — COMPOSITION #13).\n * A config that fails shape-resolution contributes no components; the CLI\n * surfaces config problems through the studio/doctor, not here.\n */\nexport function previewComponentsFromConfig(config: unknown): readonly ComponentEntry[] {\n const seen = new Set<ComponentEntry>()\n const entries: ComponentEntry[] = []\n for (const project of resolveWorkspaceConfig(config).projects) {\n for (const entry of project.components ?? []) {\n if (seen.has(entry)) continue\n seen.add(entry)\n entries.push(entry)\n }\n }\n return entries\n}\n\n/**\n * The {@link StudioRegistry}-shaped registry the harness renders against: the\n * config's list through core's ONE registration step (`registerComponents`, the\n * same call the studio registry and the Next host make — EC-591), so a duplicate,\n * a role claim and an `invalid-implementation` are the identical issue in\n * `registry.issues` on every surface.\n */\nexport function previewRegistryFromConfig(config: unknown): StudioRegistry {\n return registerComponents(previewComponentsFromConfig(config))\n}\n\n/**\n * Enumerate every generated-preview entry for a config: canvas-eligible\n * manifests only (server-only components never render on the client canvas),\n * one entry per declared fixture in declaration order, else one implicit\n * {@link DEFAULT_VARIANT} entry. Components are sorted by id so the output —\n * and everything the CLI derives from it (manifest JSON, file writes) — is\n * deterministic regardless of registration order.\n */\nexport function previewEntriesForConfig(config: unknown): PreviewEntrySpec[] {\n const manifests = previewRegistryFromConfig(config).registry.list()\n const entries: PreviewEntrySpec[] = []\n for (const manifest of [...manifests].sort((a, b) => a.id.localeCompare(b.id))) {\n if (!isCanvasEligible(manifest)) continue\n const manifestShape = { props: manifest.props, slots: manifest.slots }\n if (manifest.fixtures && manifest.fixtures.length > 0) {\n for (const fixture of manifest.fixtures) {\n entries.push({\n componentId: manifest.id,\n fixture: fixture.name,\n hashInput: {\n manifest: manifestShape,\n fixture: { props: fixture.props ?? null, slotChildren: fixture.slotChildren ?? null },\n },\n })\n }\n } else {\n entries.push({\n componentId: manifest.id,\n fixture: DEFAULT_VARIANT,\n hashInput: { manifest: manifestShape, fixture: null },\n })\n }\n }\n return entries\n}\n"]}
@@ -71,7 +71,7 @@ function nodeForEntry(studio, componentId, fixture) {
71
71
  if (declared)
72
72
  return { node: fixtureSubtree(studio, manifest, declared, `preview-${componentId}`).node };
73
73
  if (fixture === DEFAULT_VARIANT && (manifest.fixtures ?? []).length === 0) {
74
- return { node: seededNodeFromManifest(manifest) };
74
+ return { node: seededNodeFromManifest(manifest, studio.registry) };
75
75
  }
76
76
  return { error: `Unknown fixture "${fixture}" for component "${componentId}".` };
77
77
  }