@elytracms/studio 0.0.38 → 0.0.39

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 (179) hide show
  1. package/convex/cms.ts +8 -2
  2. package/dist/components/app-views/default-modules.d.ts +60 -5
  3. package/dist/components/app-views/default-modules.js +53 -15
  4. package/dist/components/app-views/default-modules.js.map +1 -1
  5. package/dist/components/app-views/module.d.ts +2 -0
  6. package/dist/components/app-views/module.js +8 -0
  7. package/dist/components/app-views/module.js.map +1 -1
  8. package/dist/components/assets/asset-detail.d.ts +24 -0
  9. package/dist/components/assets/asset-detail.js +163 -0
  10. package/dist/components/assets/asset-detail.js.map +1 -0
  11. package/dist/components/assets/asset-grid.d.ts +28 -0
  12. package/dist/components/assets/asset-grid.js +68 -0
  13. package/dist/components/assets/asset-grid.js.map +1 -0
  14. package/dist/components/assets/asset-metadata.d.ts +19 -0
  15. package/dist/components/assets/asset-metadata.js +44 -0
  16. package/dist/components/assets/asset-metadata.js.map +1 -1
  17. package/dist/components/assets/assets-collection-view-source.d.ts +54 -0
  18. package/dist/components/assets/assets-collection-view-source.js +186 -0
  19. package/dist/components/assets/assets-collection-view-source.js.map +1 -0
  20. package/dist/components/assets/assets-module.d.ts +18 -0
  21. package/dist/components/assets/assets-module.js +94 -0
  22. package/dist/components/assets/assets-module.js.map +1 -0
  23. package/dist/components/assets/assets-store.js +2 -7
  24. package/dist/components/assets/assets-store.js.map +1 -1
  25. package/dist/components/assets/assets-view-preference.d.ts +15 -0
  26. package/dist/components/assets/assets-view-preference.js +22 -0
  27. package/dist/components/assets/assets-view-preference.js.map +1 -0
  28. package/dist/components/builder/builder-store.d.ts +12 -0
  29. package/dist/components/builder/builder-store.js +18 -16
  30. package/dist/components/builder/builder-store.js.map +1 -1
  31. package/dist/components/builder/clipboard-actions.js +5 -14
  32. package/dist/components/builder/clipboard-actions.js.map +1 -1
  33. package/dist/components/builder/composition-field-editor-lazy.d.ts +19 -0
  34. package/dist/components/builder/composition-field-editor-lazy.js +29 -0
  35. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -0
  36. package/dist/components/builder/composition-field-editor.js +1 -4
  37. package/dist/components/builder/composition-field-editor.js.map +1 -1
  38. package/dist/components/builder/insert-context.d.ts +8 -0
  39. package/dist/components/builder/insert-context.js +1 -3
  40. package/dist/components/builder/insert-context.js.map +1 -1
  41. package/dist/components/builder/page-tree.js +1 -3
  42. package/dist/components/builder/page-tree.js.map +1 -1
  43. package/dist/components/builder/property-inspector.js +7 -134
  44. package/dist/components/builder/property-inspector.js.map +1 -1
  45. package/dist/components/cms/cms-store.d.ts +15 -4
  46. package/dist/components/cms/cms-store.js +42 -24
  47. package/dist/components/cms/cms-store.js.map +1 -1
  48. package/dist/components/cms/collection-table.d.ts +8 -1
  49. package/dist/components/cms/collection-table.js +62 -10
  50. package/dist/components/cms/collection-table.js.map +1 -1
  51. package/dist/components/cms/column-width.d.ts +25 -0
  52. package/dist/components/cms/column-width.js +47 -0
  53. package/dist/components/cms/column-width.js.map +1 -0
  54. package/dist/components/cms/detail-editor.js +101 -68
  55. package/dist/components/cms/detail-editor.js.map +1 -1
  56. package/dist/components/cms/discard-draft.d.ts +32 -0
  57. package/dist/components/cms/discard-draft.js +13 -0
  58. package/dist/components/cms/discard-draft.js.map +1 -0
  59. package/dist/components/cms/document-staging.d.ts +7 -0
  60. package/dist/components/cms/document-staging.js +44 -0
  61. package/dist/components/cms/document-staging.js.map +1 -0
  62. package/dist/components/cms/field-connection.d.ts +22 -0
  63. package/dist/components/cms/field-connection.js +37 -0
  64. package/dist/components/cms/field-connection.js.map +1 -0
  65. package/dist/components/cms/field-control.js +158 -34
  66. package/dist/components/cms/field-control.js.map +1 -1
  67. package/dist/components/cms/prop-fields.d.ts +43 -0
  68. package/dist/components/cms/prop-fields.js +130 -0
  69. package/dist/components/cms/prop-fields.js.map +1 -0
  70. package/dist/components/cms/rich-text/component-embed.d.ts +17 -3
  71. package/dist/components/cms/rich-text/component-embed.js +29 -7
  72. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  73. package/dist/components/cms/rich-text/embed-props.d.ts +49 -0
  74. package/dist/components/cms/rich-text/embed-props.js +106 -0
  75. package/dist/components/cms/rich-text/embed-props.js.map +1 -0
  76. package/dist/components/cms/rich-text/index.d.ts +6 -1
  77. package/dist/components/cms/rich-text/index.js +6 -1
  78. package/dist/components/cms/rich-text/index.js.map +1 -1
  79. package/dist/components/cms/rich-text/lazy.d.ts +19 -0
  80. package/dist/components/cms/rich-text/lazy.js +26 -0
  81. package/dist/components/cms/rich-text/lazy.js.map +1 -0
  82. package/dist/components/cms/row-actions.d.ts +28 -0
  83. package/dist/components/cms/row-actions.js +20 -0
  84. package/dist/components/cms/row-actions.js.map +1 -0
  85. package/dist/components/cms/use-document-versions.d.ts +8 -1
  86. package/dist/components/cms/use-document-versions.js +31 -4
  87. package/dist/components/cms/use-document-versions.js.map +1 -1
  88. package/dist/components/cms/version-compare-lazy.d.ts +1 -0
  89. package/dist/components/cms/version-compare-lazy.js +23 -0
  90. package/dist/components/cms/version-compare-lazy.js.map +1 -0
  91. package/dist/components/cms/version-compare-text.d.ts +46 -0
  92. package/dist/components/cms/version-compare-text.js +78 -0
  93. package/dist/components/cms/version-compare-text.js.map +1 -1
  94. package/dist/components/cms/version-compare.d.ts +9 -5
  95. package/dist/components/cms/version-compare.js +184 -59
  96. package/dist/components/cms/version-compare.js.map +1 -1
  97. package/dist/components/cms/versions-pane.js +10 -4
  98. package/dist/components/cms/versions-pane.js.map +1 -1
  99. package/dist/components/components/component-detail.d.ts +5 -3
  100. package/dist/components/components/component-detail.js +41 -8
  101. package/dist/components/components/component-detail.js.map +1 -1
  102. package/dist/components/components/component-preview.d.ts +61 -0
  103. package/dist/components/components/component-preview.js +103 -0
  104. package/dist/components/components/component-preview.js.map +1 -0
  105. package/dist/components/components/components-index.d.ts +5 -3
  106. package/dist/components/components/components-index.js +19 -24
  107. package/dist/components/components/components-index.js.map +1 -1
  108. package/dist/components/components/registry-browser.d.ts +7 -0
  109. package/dist/components/components/registry-browser.js +20 -36
  110. package/dist/components/components/registry-browser.js.map +1 -1
  111. package/dist/components/components/taxonomy.d.ts +51 -0
  112. package/dist/components/components/taxonomy.js +73 -0
  113. package/dist/components/components/taxonomy.js.map +1 -0
  114. package/dist/components/components/transfer-dialog.d.ts +20 -0
  115. package/dist/components/components/transfer-dialog.js +64 -0
  116. package/dist/components/components/transfer-dialog.js.map +1 -0
  117. package/dist/components/components/transfer.d.ts +43 -0
  118. package/dist/components/components/transfer.js +31 -0
  119. package/dist/components/components/transfer.js.map +1 -0
  120. package/dist/components/project-list.js +3 -4
  121. package/dist/components/project-list.js.map +1 -1
  122. package/dist/components/project-switcher.js +3 -2
  123. package/dist/components/project-switcher.js.map +1 -1
  124. package/dist/components/rail-store-status.d.ts +21 -0
  125. package/dist/components/rail-store-status.js +21 -0
  126. package/dist/components/rail-store-status.js.map +1 -0
  127. package/dist/components/redirects/redirects-module.js +4 -8
  128. package/dist/components/redirects/redirects-module.js.map +1 -1
  129. package/dist/components/redirects/redirects-store.js +2 -7
  130. package/dist/components/redirects/redirects-store.js.map +1 -1
  131. package/dist/components/sign-in-needed-badge.d.ts +6 -0
  132. package/dist/components/sign-in-needed-badge.js +13 -0
  133. package/dist/components/sign-in-needed-badge.js.map +1 -0
  134. package/dist/components/studio-project-screen.js +1 -4
  135. package/dist/components/studio-project-screen.js.map +1 -1
  136. package/dist/components/studio-shell.js +1 -4
  137. package/dist/components/studio-shell.js.map +1 -1
  138. package/dist/components/users/users-module.js +5 -7
  139. package/dist/components/users/users-module.js.map +1 -1
  140. package/dist/components/users/users-store.js +2 -7
  141. package/dist/components/users/users-store.js.map +1 -1
  142. package/dist/lib/auth/index.js +1 -1
  143. package/dist/lib/auth/index.js.map +1 -1
  144. package/dist/lib/clipboard/paste-target.js +1 -3
  145. package/dist/lib/clipboard/paste-target.js.map +1 -1
  146. package/dist/lib/cms-views/view-model.d.ts +0 -2
  147. package/dist/lib/cms-views/view-model.js +0 -7
  148. package/dist/lib/cms-views/view-model.js.map +1 -1
  149. package/dist/lib/diff-view-preference.d.ts +7 -0
  150. package/dist/lib/diff-view-preference.js +21 -0
  151. package/dist/lib/diff-view-preference.js.map +1 -0
  152. package/dist/lib/i18n/messages.en.js +79 -30
  153. package/dist/lib/i18n/messages.en.js.map +1 -1
  154. package/dist/lib/local-preference.d.ts +11 -0
  155. package/dist/lib/local-preference.js +26 -0
  156. package/dist/lib/local-preference.js.map +1 -0
  157. package/dist/lib/locale/fallback.d.ts +45 -0
  158. package/dist/lib/locale/fallback.js +85 -0
  159. package/dist/lib/locale/fallback.js.map +1 -1
  160. package/dist/lib/persona/labels.d.ts +6 -0
  161. package/dist/lib/persona/labels.js +8 -0
  162. package/dist/lib/persona/labels.js.map +1 -1
  163. package/dist/lib/query/index.d.ts +1 -1
  164. package/dist/lib/query/index.js +1 -1
  165. package/dist/lib/query/index.js.map +1 -1
  166. package/dist/lib/query/operations-query.d.ts +14 -2
  167. package/dist/lib/query/operations-query.js +11 -6
  168. package/dist/lib/query/operations-query.js.map +1 -1
  169. package/dist/lib/rail.d.ts +0 -6
  170. package/dist/lib/rail.js +6 -25
  171. package/dist/lib/rail.js.map +1 -1
  172. package/dist/lib/theme.d.ts +0 -6
  173. package/dist/lib/theme.js +10 -13
  174. package/dist/lib/theme.js.map +1 -1
  175. package/dist/router/match.d.ts +1 -0
  176. package/dist/router/match.js +4 -0
  177. package/dist/router/match.js.map +1 -1
  178. package/dist/ui/button.d.ts +1 -1
  179. package/package.json +22 -25
@@ -0,0 +1,61 @@
1
+ import type { ComponentNode, JsonValue } from '@elytracms/core/project-graph';
2
+ import type { ComponentManifest } from '@elytracms/core/component-registry';
3
+ import type { StudioRegistry } from '@elytracms/studio/lib/registry';
4
+ import type { PreviewSpec } from '@elytracms/studio/lib/registry/preview';
5
+ /**
6
+ * EC-320 — the editable component preview. A component's declared preview states
7
+ * (fixtures) select the SAMPLE STATE (slot content, base props); on top of that
8
+ * the user edits individual prop values in the Props sidebar and the preview
9
+ * re-renders live through the SAME runtime-renderer path the Builder and export
10
+ * use (EC-054). Slots follow the selected fixture — full slot-tree editing is out
11
+ * of scope for this reference surface (that's the composition editor's job); the
12
+ * Slots pane documents what a component accepts and which state supplies content.
13
+ */
14
+ export interface PreviewState {
15
+ specs: PreviewSpec[];
16
+ index: number;
17
+ setIndex: (i: number) => void;
18
+ /** Per-prop static-value overrides applied over the selected fixture. */
19
+ overrides: Record<string, JsonValue>;
20
+ setOverride: (name: string, value: JsonValue) => void;
21
+ clearOverride: (name: string) => void;
22
+ resetAll: () => void;
23
+ dirty: boolean;
24
+ /** The live node: the selected fixture with prop overrides applied. */
25
+ node: ComponentNode;
26
+ }
27
+ /** Preview state for a manifest: the fixture selection + the live prop overrides. */
28
+ export declare function usePreviewState(manifest: ComponentManifest): PreviewState;
29
+ /** Re-id a preview node into the canvas `node-<n>` namespace for embedding (transfer). */
30
+ export declare function embedNodeFrom(node: ComponentNode, id: string): ComponentNode;
31
+ /**
32
+ * The live preview surface: the fixture-state selector (when a component declares
33
+ * more than one) plus the rendered node. Server-only components (EC-191
34
+ * `clientRenderable: false`) can't run on the client-rendered canvas, so they show
35
+ * a documented fallback rather than risk a crash; a missing implementation falls
36
+ * back through the renderer itself (the incompatible state).
37
+ */
38
+ export declare function ComponentPreview({ manifest, studio, state, }: {
39
+ manifest: ComponentManifest;
40
+ studio: StudioRegistry;
41
+ state: PreviewState;
42
+ }): import("react").JSX.Element;
43
+ /**
44
+ * The editable Props pane (right inspector). Every non-`blocks` prop is edited
45
+ * through the shared {@link PropValueControl} — the exact control set documents
46
+ * use (EC-190/EC-314 phase 4) — so a change re-renders the preview immediately.
47
+ * `blocks` props are a canvas (composition) concern, edited in a document, not here.
48
+ */
49
+ export declare function PropsEditorPane({ manifest, state, }: {
50
+ manifest: ComponentManifest;
51
+ state: PreviewState;
52
+ }): import("react").JSX.Element;
53
+ /**
54
+ * The Slots pane (right inspector): the component's declared child regions and,
55
+ * for a fixture that supplies slot content, a note that the preview's slot content
56
+ * follows the selected state. Documents the pragmatic scope call — no slot-tree
57
+ * editing on this reference surface.
58
+ */
59
+ export declare function SlotsInfoPane({ manifest }: {
60
+ manifest: ComponentManifest;
61
+ }): import("react").JSX.Element;
@@ -0,0 +1,103 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { MonitorOff, RotateCcw } from 'lucide-react';
4
+ import { renderNode } from '@elytracms/core/runtime-renderer';
5
+ import { staticProp } from '@elytracms/core/project-graph';
6
+ import { componentStatus, previewContext } from '@elytracms/studio/lib/registry';
7
+ import { previewSpecs } from '@elytracms/studio/lib/registry/preview';
8
+ import { Badge, Button, Code, Field, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
9
+ import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
10
+ import { cn } from '@elytracms/studio/lib/utils';
11
+ import { PropValueControl } from '../cms/prop-fields';
12
+ /** Preview state for a manifest: the fixture selection + the live prop overrides. */
13
+ export function usePreviewState(manifest) {
14
+ const specs = useMemo(() => previewSpecs(manifest), [manifest]);
15
+ const [index, setIndexRaw] = useState(0);
16
+ const [overrides, setOverrides] = useState({});
17
+ // Switching preview state resets prop edits — they were relative to the old state.
18
+ const setIndex = (i) => {
19
+ setIndexRaw(i);
20
+ setOverrides({});
21
+ };
22
+ const setOverride = (name, value) => setOverrides((o) => ({ ...o, [name]: value }));
23
+ const clearOverride = (name) => setOverrides((o) => {
24
+ const next = { ...o };
25
+ delete next[name];
26
+ return next;
27
+ });
28
+ const resetAll = () => setOverrides({});
29
+ const base = specs[Math.min(index, specs.length - 1)]?.node;
30
+ const node = useMemo(() => {
31
+ const fixture = base ?? { id: `preview-${manifest.id}`, componentId: manifest.id, props: {} };
32
+ const props = { ...(fixture.props ?? {}) };
33
+ for (const [name, value] of Object.entries(overrides))
34
+ props[name] = staticProp(value);
35
+ return { ...fixture, props };
36
+ }, [base, overrides, manifest.id]);
37
+ return {
38
+ specs,
39
+ index: Math.min(index, Math.max(specs.length - 1, 0)),
40
+ setIndex,
41
+ overrides,
42
+ setOverride,
43
+ clearOverride,
44
+ resetAll,
45
+ dirty: Object.keys(overrides).length > 0,
46
+ node,
47
+ };
48
+ }
49
+ /** Re-id a preview node into the canvas `node-<n>` namespace for embedding (transfer). */
50
+ export function embedNodeFrom(node, id) {
51
+ return { ...node, id };
52
+ }
53
+ /**
54
+ * The live preview surface: the fixture-state selector (when a component declares
55
+ * more than one) plus the rendered node. Server-only components (EC-191
56
+ * `clientRenderable: false`) can't run on the client-rendered canvas, so they show
57
+ * a documented fallback rather than risk a crash; a missing implementation falls
58
+ * back through the renderer itself (the incompatible state).
59
+ */
60
+ export function ComponentPreview({ manifest, studio, state, }) {
61
+ const ctx = useMemo(() => previewContext(studio), [studio]);
62
+ const serverOnly = manifest.clientRenderable === false;
63
+ const status = componentStatus(studio, manifest);
64
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [state.specs.length > 1 ? (_jsxs(Row, { gap: "1", wrap: true, className: "shrink-0 border-b border-border px-3 py-2", role: "group", "aria-label": translate('components.fixtureRunner.statesAria'), children: [state.specs.map((spec, i) => (_jsx("button", { type: "button", "aria-pressed": i === state.index, onClick: () => state.setIndex(i), className: cn('rounded-md px-2.5 py-1 text-xs font-medium transition-colors', i === state.index
65
+ ? 'bg-accent text-accent-foreground'
66
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: spec.name }, spec.name))), _jsx(Spacer, {}), state.dirty ? (_jsxs(Button, { variant: "ghost", size: "sm", onClick: state.resetAll, "data-testid": "preview-reset", children: [_jsx(RotateCcw, {}), " ", translate('components.preview.reset')] })) : null] })) : null, _jsxs("div", { className: "canvas-grid min-h-0 flex-1 overflow-auto p-6", "data-fixture-state": state.specs[state.index]?.name, children: [serverOnly ? (_jsx(ServerOnlyNotice, {})) : (_jsx("div", { className: "mx-auto max-w-2xl rounded-lg border border-border bg-background p-6 shadow-sm", children: renderNode(state.node, ctx) })), status === 'incompatible' && !serverOnly ? (_jsx(Text, { size: "xs", tone: "muted", className: "mx-auto mt-3 max-w-2xl", children: translate('components.preview.incompatibleNote') })) : null] })] }));
67
+ }
68
+ function ServerOnlyNotice() {
69
+ return (_jsx("div", { className: "mx-auto grid max-w-2xl place-items-center rounded-lg border border-dashed border-border bg-muted/30 p-8 text-center", children: _jsxs(Stack, { gap: "2", align: "center", children: [_jsx(MonitorOff, { className: "size-5 text-muted-foreground", "aria-hidden": true }), _jsx(Text, { size: "sm", weight: "medium", children: translate('components.preview.serverOnlyTitle') }), _jsx(Text, { size: "xs", tone: "muted", className: "max-w-sm", children: translate('components.preview.serverOnlyBody') })] }) }));
70
+ }
71
+ /**
72
+ * The editable Props pane (right inspector). Every non-`blocks` prop is edited
73
+ * through the shared {@link PropValueControl} — the exact control set documents
74
+ * use (EC-190/EC-314 phase 4) — so a change re-renders the preview immediately.
75
+ * `blocks` props are a canvas (composition) concern, edited in a document, not here.
76
+ */
77
+ export function PropsEditorPane({ manifest, state, }) {
78
+ const editable = Object.entries(manifest.props).filter(([, spec]) => spec.type !== 'blocks');
79
+ const fixture = state.specs[state.index]?.node;
80
+ if (editable.length === 0) {
81
+ return (_jsx(Text, { size: "sm", tone: "subtle", className: "p-4", children: translate('components.fieldInspector.noProps') }));
82
+ }
83
+ return (_jsxs(Stack, { gap: "4", className: "p-4", children: [editable.map(([name, spec]) => {
84
+ const id = `prop-${name}`;
85
+ const overridden = state.overrides[name] !== undefined;
86
+ return (_jsx(Field, { label: _jsxs(Row, { gap: "2", children: [_jsx("span", { children: localizedLabel(spec.form?.label, name) }), overridden ? _jsx(Badge, { variant: "info", children: translate('components.preview.edited') }) : null] }), htmlFor: id, hint: localizedLabel(spec.form?.description) || undefined, children: _jsx(PropValueControl, { id: id, name: name, spec: spec, value: overridden
87
+ ? staticProp(state.overrides[name])
88
+ : (fixture?.props?.[name] ?? undefined), onSet: (v) => state.setOverride(name, v), onClear: () => state.clearOverride(name) }) }, name));
89
+ }), state.dirty ? (_jsxs(Button, { variant: "outline", size: "sm", onClick: state.resetAll, className: "self-start", children: [_jsx(RotateCcw, {}), " ", translate('components.preview.resetProps')] })) : null] }));
90
+ }
91
+ /**
92
+ * The Slots pane (right inspector): the component's declared child regions and,
93
+ * for a fixture that supplies slot content, a note that the preview's slot content
94
+ * follows the selected state. Documents the pragmatic scope call — no slot-tree
95
+ * editing on this reference surface.
96
+ */
97
+ export function SlotsInfoPane({ manifest }) {
98
+ if (manifest.slots.length === 0) {
99
+ return (_jsx(Text, { size: "sm", tone: "subtle", className: "p-4", children: translate('components.fieldInspector.noSlots') }));
100
+ }
101
+ return (_jsxs(Stack, { gap: "3", className: "p-4", children: [_jsx(Text, { size: "xs", tone: "muted", children: translate('components.preview.slotsNote') }), _jsx("div", { className: "divide-y divide-border rounded-md border border-border", children: manifest.slots.map((slot) => (_jsxs("div", { className: "flex flex-col gap-1 p-3", children: [_jsxs(Row, { gap: "2", children: [_jsx(Code, { tone: "primary", children: slot.name }), slot.required ? (_jsx(Badge, { variant: "warning", children: translate('common.required') })) : (_jsx(Badge, { variant: "outline", children: translate('common.optional') }))] }), slot.allow && slot.allow.length > 0 ? (_jsxs(Row, { gap: "2", className: "text-xs", wrap: true, children: [_jsx(Text, { size: "xs", tone: "subtle", children: translate('components.fieldInspector.allows') }), slot.allow.map((id) => (_jsx(Code, { tone: "muted", children: id }, id)))] })) : null] }, slot.name))) })] }));
102
+ }
103
+ //# sourceMappingURL=component-preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-preview.js","sourceRoot":"","sources":["../../../src/components/components/component-preview.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAG1D,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAEhF,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AAErE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3F,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AA0BrD,qFAAqF;AACrF,MAAM,UAAU,eAAe,CAAC,QAA2B;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC/D,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACxC,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAA4B,EAAE,CAAC,CAAA;IAEzE,mFAAmF;IACnF,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,EAAE;QAC7B,WAAW,CAAC,CAAC,CAAC,CAAA;QACd,YAAY,CAAC,EAAE,CAAC,CAAA;IAClB,CAAC,CAAA;IACD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,KAAgB,EAAE,EAAE,CACrD,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IAChD,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,EAAE,CACrC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE;QACjB,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,CAAA;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAA;QACjB,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CAAA;IACJ,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAA;IAEvC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAA;IAC3D,MAAM,IAAI,GAAG,OAAO,CAAgB,GAAG,EAAE;QACvC,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,EAAE,EAAE,WAAW,QAAQ,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;QAC7F,MAAM,KAAK,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAA;QAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;QACtF,OAAO,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAA;IAC9B,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;IAElC,OAAO;QACL,KAAK;QACL,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACrD,QAAQ;QACR,SAAS;QACT,WAAW;QACX,aAAa;QACb,QAAQ;QACR,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC;QACxC,IAAI;KACL,CAAA;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,aAAa,CAAC,IAAmB,EAAE,EAAU;IAC3D,OAAO,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAA;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,KAAK,GAKN;IACC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAC3D,MAAM,UAAU,GAAG,QAAQ,CAAC,gBAAgB,KAAK,KAAK,CAAA;IACtD,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAEhD,OAAO,CACL,eAAK,SAAS,EAAC,8BAA8B,aAC1C,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACxB,MAAC,GAAG,IACF,GAAG,EAAC,GAAG,EACP,IAAI,QACJ,SAAS,EAAC,2CAA2C,EACrD,IAAI,EAAC,OAAO,gBACA,SAAS,CAAC,qCAAqC,CAAC,aAE3D,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAC5B,iBAEE,IAAI,EAAC,QAAQ,kBACC,CAAC,KAAK,KAAK,CAAC,KAAK,EAC/B,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAChC,SAAS,EAAE,EAAE,CACX,8DAA8D,EAC9D,CAAC,KAAK,KAAK,CAAC,KAAK;4BACf,CAAC,CAAC,kCAAkC;4BACpC,CAAC,CAAC,4DAA4D,CACjE,YAEA,IAAI,CAAC,IAAI,IAXL,IAAI,CAAC,IAAI,CAYP,CACV,CAAC,EACF,KAAC,MAAM,KAAG,EACT,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CACb,MAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,iBAAc,eAAe,aACpF,KAAC,SAAS,KAAG,OAAE,SAAS,CAAC,0BAA0B,CAAC,IAC7C,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC,CAAC,CAAC,IAAI,EAER,eACE,SAAS,EAAC,8CAA8C,wBACpC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,aAEjD,UAAU,CAAC,CAAC,CAAC,CACZ,KAAC,gBAAgB,KAAG,CACrB,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,+EAA+E,YAC3F,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,GACxB,CACP,EACA,MAAM,KAAK,cAAc,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAC1C,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,wBAAwB,YAC5D,SAAS,CAAC,qCAAqC,CAAC,GAC5C,CACR,CAAC,CAAC,CAAC,IAAI,IACJ,IACF,CACP,CAAA;AACH,CAAC;AAED,SAAS,gBAAgB;IACvB,OAAO,CACL,cAAK,SAAS,EAAC,qHAAqH,YAClI,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aAC3B,KAAC,UAAU,IAAC,SAAS,EAAC,8BAA8B,wBAAe,EACnE,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,YAC5B,SAAS,CAAC,oCAAoC,CAAC,GAC3C,EACP,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,UAAU,YAC9C,SAAS,CAAC,mCAAmC,CAAC,GAC1C,IACD,GACJ,CACP,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,QAAQ,EACR,KAAK,GAIN;IACC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAA;IAC5F,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,CAAA;IAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,KAAK,YAC1C,SAAS,CAAC,mCAAmC,CAAC,GAC1C,CACR,CAAA;IACH,CAAC;IACD,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,KAAK,aAC3B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE;gBAC7B,MAAM,EAAE,GAAG,QAAQ,IAAI,EAAE,CAAA;gBACzB,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,SAAS,CAAA;gBACtD,OAAO,CACL,KAAC,KAAK,IAEJ,KAAK,EACH,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,yBAAO,cAAc,CAAE,IAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,GAAQ,EACnE,UAAU,CAAC,CAAC,CAAC,KAAC,KAAK,IAAC,OAAO,EAAC,MAAM,YAAE,SAAS,CAAC,2BAA2B,CAAC,GAAS,CAAC,CAAC,CAAC,IAAI,IACvF,EAER,OAAO,EAAE,EAAE,EACX,IAAI,EAAE,cAAc,CAAE,IAAkB,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,SAAS,YAExE,KAAC,gBAAgB,IACf,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAiB,EACvB,KAAK,EACH,UAAU;4BACR,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAE,CAAC;4BACpC,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,EAE3C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,EACxC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,GACxC,IArBG,IAAI,CAsBH,CACT,CAAA;YACH,CAAC,CAAC,EACD,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CACb,MAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAC,YAAY,aACjF,KAAC,SAAS,KAAG,OAAE,SAAS,CAAC,+BAA+B,CAAC,IAClD,CACV,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,QAAQ,EAAmC;IACzE,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,KAAK,YAC1C,SAAS,CAAC,mCAAmC,CAAC,GAC1C,CACR,CAAA;IACH,CAAC;IACD,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,KAAK,aAC5B,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,8BAA8B,CAAC,GACrC,EACP,cAAK,SAAS,EAAC,wDAAwD,YACpE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAC5B,eAAqB,SAAS,EAAC,yBAAyB,aACtD,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,aACV,KAAC,IAAI,IAAC,IAAI,EAAC,SAAS,YAAE,IAAI,CAAC,IAAI,GAAQ,EACtC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CACf,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,YAAE,SAAS,CAAC,iBAAiB,CAAC,GAAS,CAChE,CAAC,CAAC,CAAC,CACF,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,YAAE,SAAS,CAAC,iBAAiB,CAAC,GAAS,CAChE,IACG,EACL,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACrC,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,SAAS,EAAC,IAAI,mBACnC,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,SAAS,CAAC,kCAAkC,CAAC,GACzC,EACN,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CACtB,KAAC,IAAI,IAAU,IAAI,EAAC,OAAO,YACxB,EAAE,IADM,EAAE,CAEN,CACR,CAAC,IACE,CACP,CAAC,CAAC,CAAC,IAAI,KApBA,IAAI,CAAC,IAAI,CAqBb,CACP,CAAC,GACE,IACA,CACT,CAAA;AACH,CAAC","sourcesContent":["import { useMemo, useState } from 'react'\nimport { MonitorOff, RotateCcw } from 'lucide-react'\nimport { renderNode } from '@elytracms/core/runtime-renderer'\nimport { staticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport { componentStatus, previewContext } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { previewSpecs } from '@elytracms/studio/lib/registry/preview'\nimport type { PreviewSpec } from '@elytracms/studio/lib/registry/preview'\nimport { Badge, Button, Code, Field, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { PropValueControl } from '../cms/prop-fields'\n\n/**\n * EC-320 — the editable component preview. A component's declared preview states\n * (fixtures) select the SAMPLE STATE (slot content, base props); on top of that\n * the user edits individual prop values in the Props sidebar and the preview\n * re-renders live through the SAME runtime-renderer path the Builder and export\n * use (EC-054). Slots follow the selected fixture — full slot-tree editing is out\n * of scope for this reference surface (that's the composition editor's job); the\n * Slots pane documents what a component accepts and which state supplies content.\n */\n\nexport interface PreviewState {\n specs: PreviewSpec[]\n index: number\n setIndex: (i: number) => void\n /** Per-prop static-value overrides applied over the selected fixture. */\n overrides: Record<string, JsonValue>\n setOverride: (name: string, value: JsonValue) => void\n clearOverride: (name: string) => void\n resetAll: () => void\n dirty: boolean\n /** The live node: the selected fixture with prop overrides applied. */\n node: ComponentNode\n}\n\n/** Preview state for a manifest: the fixture selection + the live prop overrides. */\nexport function usePreviewState(manifest: ComponentManifest): PreviewState {\n const specs = useMemo(() => previewSpecs(manifest), [manifest])\n const [index, setIndexRaw] = useState(0)\n const [overrides, setOverrides] = useState<Record<string, JsonValue>>({})\n\n // Switching preview state resets prop edits — they were relative to the old state.\n const setIndex = (i: number) => {\n setIndexRaw(i)\n setOverrides({})\n }\n const setOverride = (name: string, value: JsonValue) =>\n setOverrides((o) => ({ ...o, [name]: value }))\n const clearOverride = (name: string) =>\n setOverrides((o) => {\n const next = { ...o }\n delete next[name]\n return next\n })\n const resetAll = () => setOverrides({})\n\n const base = specs[Math.min(index, specs.length - 1)]?.node\n const node = useMemo<ComponentNode>(() => {\n const fixture = base ?? { id: `preview-${manifest.id}`, componentId: manifest.id, props: {} }\n const props = { ...(fixture.props ?? {}) }\n for (const [name, value] of Object.entries(overrides)) props[name] = staticProp(value)\n return { ...fixture, props }\n }, [base, overrides, manifest.id])\n\n return {\n specs,\n index: Math.min(index, Math.max(specs.length - 1, 0)),\n setIndex,\n overrides,\n setOverride,\n clearOverride,\n resetAll,\n dirty: Object.keys(overrides).length > 0,\n node,\n }\n}\n\n/** Re-id a preview node into the canvas `node-<n>` namespace for embedding (transfer). */\nexport function embedNodeFrom(node: ComponentNode, id: string): ComponentNode {\n return { ...node, id }\n}\n\n/**\n * The live preview surface: the fixture-state selector (when a component declares\n * more than one) plus the rendered node. Server-only components (EC-191\n * `clientRenderable: false`) can't run on the client-rendered canvas, so they show\n * a documented fallback rather than risk a crash; a missing implementation falls\n * back through the renderer itself (the incompatible state).\n */\nexport function ComponentPreview({\n manifest,\n studio,\n state,\n}: {\n manifest: ComponentManifest\n studio: StudioRegistry\n state: PreviewState\n}) {\n const ctx = useMemo(() => previewContext(studio), [studio])\n const serverOnly = manifest.clientRenderable === false\n const status = componentStatus(studio, manifest)\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n {state.specs.length > 1 ? (\n <Row\n gap=\"1\"\n wrap\n className=\"shrink-0 border-b border-border px-3 py-2\"\n role=\"group\"\n aria-label={translate('components.fixtureRunner.statesAria')}\n >\n {state.specs.map((spec, i) => (\n <button\n key={spec.name}\n type=\"button\"\n aria-pressed={i === state.index}\n onClick={() => state.setIndex(i)}\n className={cn(\n 'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',\n i === state.index\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {spec.name}\n </button>\n ))}\n <Spacer />\n {state.dirty ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={state.resetAll} data-testid=\"preview-reset\">\n <RotateCcw /> {translate('components.preview.reset')}\n </Button>\n ) : null}\n </Row>\n ) : null}\n\n <div\n className=\"canvas-grid min-h-0 flex-1 overflow-auto p-6\"\n data-fixture-state={state.specs[state.index]?.name}\n >\n {serverOnly ? (\n <ServerOnlyNotice />\n ) : (\n <div className=\"mx-auto max-w-2xl rounded-lg border border-border bg-background p-6 shadow-sm\">\n {renderNode(state.node, ctx)}\n </div>\n )}\n {status === 'incompatible' && !serverOnly ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mx-auto mt-3 max-w-2xl\">\n {translate('components.preview.incompatibleNote')}\n </Text>\n ) : null}\n </div>\n </div>\n )\n}\n\nfunction ServerOnlyNotice() {\n return (\n <div className=\"mx-auto grid max-w-2xl place-items-center rounded-lg border border-dashed border-border bg-muted/30 p-8 text-center\">\n <Stack gap=\"2\" align=\"center\">\n <MonitorOff className=\"size-5 text-muted-foreground\" aria-hidden />\n <Text size=\"sm\" weight=\"medium\">\n {translate('components.preview.serverOnlyTitle')}\n </Text>\n <Text size=\"xs\" tone=\"muted\" className=\"max-w-sm\">\n {translate('components.preview.serverOnlyBody')}\n </Text>\n </Stack>\n </div>\n )\n}\n\n/**\n * The editable Props pane (right inspector). Every non-`blocks` prop is edited\n * through the shared {@link PropValueControl} — the exact control set documents\n * use (EC-190/EC-314 phase 4) — so a change re-renders the preview immediately.\n * `blocks` props are a canvas (composition) concern, edited in a document, not here.\n */\nexport function PropsEditorPane({\n manifest,\n state,\n}: {\n manifest: ComponentManifest\n state: PreviewState\n}) {\n const editable = Object.entries(manifest.props).filter(([, spec]) => spec.type !== 'blocks')\n const fixture = state.specs[state.index]?.node\n if (editable.length === 0) {\n return (\n <Text size=\"sm\" tone=\"subtle\" className=\"p-4\">\n {translate('components.fieldInspector.noProps')}\n </Text>\n )\n }\n return (\n <Stack gap=\"4\" className=\"p-4\">\n {editable.map(([name, spec]) => {\n const id = `prop-${name}`\n const overridden = state.overrides[name] !== undefined\n return (\n <Field\n key={name}\n label={\n <Row gap=\"2\">\n <span>{localizedLabel((spec as PropField).form?.label, name)}</span>\n {overridden ? <Badge variant=\"info\">{translate('components.preview.edited')}</Badge> : null}\n </Row>\n }\n htmlFor={id}\n hint={localizedLabel((spec as PropField).form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec as PropField}\n value={\n overridden\n ? staticProp(state.overrides[name]!)\n : (fixture?.props?.[name] ?? undefined)\n }\n onSet={(v) => state.setOverride(name, v)}\n onClear={() => state.clearOverride(name)}\n />\n </Field>\n )\n })}\n {state.dirty ? (\n <Button variant=\"outline\" size=\"sm\" onClick={state.resetAll} className=\"self-start\">\n <RotateCcw /> {translate('components.preview.resetProps')}\n </Button>\n ) : null}\n </Stack>\n )\n}\n\n/**\n * The Slots pane (right inspector): the component's declared child regions and,\n * for a fixture that supplies slot content, a note that the preview's slot content\n * follows the selected state. Documents the pragmatic scope call — no slot-tree\n * editing on this reference surface.\n */\nexport function SlotsInfoPane({ manifest }: { manifest: ComponentManifest }) {\n if (manifest.slots.length === 0) {\n return (\n <Text size=\"sm\" tone=\"subtle\" className=\"p-4\">\n {translate('components.fieldInspector.noSlots')}\n </Text>\n )\n }\n return (\n <Stack gap=\"3\" className=\"p-4\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('components.preview.slotsNote')}\n </Text>\n <div className=\"divide-y divide-border rounded-md border border-border\">\n {manifest.slots.map((slot) => (\n <div key={slot.name} className=\"flex flex-col gap-1 p-3\">\n <Row gap=\"2\">\n <Code tone=\"primary\">{slot.name}</Code>\n {slot.required ? (\n <Badge variant=\"warning\">{translate('common.required')}</Badge>\n ) : (\n <Badge variant=\"outline\">{translate('common.optional')}</Badge>\n )}\n </Row>\n {slot.allow && slot.allow.length > 0 ? (\n <Row gap=\"2\" className=\"text-xs\" wrap>\n <Text size=\"xs\" tone=\"subtle\">\n {translate('components.fieldInspector.allows')}\n </Text>\n {slot.allow.map((id) => (\n <Code key={id} tone=\"muted\">\n {id}\n </Code>\n ))}\n </Row>\n ) : null}\n </div>\n ))}\n </div>\n </Stack>\n )\n}\n"]}
@@ -1,7 +1,9 @@
1
1
  /**
2
- * The Components section landing view (EC-223 Stage 6): registry health overview
3
- * + the authoring guide, shown when no component is selected. Takes `projectId`
4
- * as a prop — the splat resolver sources it from the route, not a route file.
2
+ * The Components section landing view (EC-050, overhauled EC-320). Serves BOTH
3
+ * audiences: editors get a live census of what components exist (totals, the
4
+ * base/project split, per-category breakdown, health) and a "pick one" nudge;
5
+ * devs get pointers to the real in-repo docs for registering and extending
6
+ * components — no code sample to drift, no invented docs.
5
7
  */
6
8
  export declare function ComponentsIndex({ projectId }: {
7
9
  projectId: string;
@@ -1,37 +1,32 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Blocks, MousePointerClick } from 'lucide-react';
2
+ import { BookOpen, Layers, MousePointerClick, Sparkles } from 'lucide-react';
3
3
  import { getStudioRegistry, registryHealth } from '@elytracms/studio/lib/registry';
4
4
  import { translate } from '@elytracms/studio/lib/i18n';
5
- import { RegistryBrowser } from '@elytracms/studio/components/components/registry-browser';
6
- import { Badge, Card, Code, Heading, Panel, PanelHeader, Spacer, Stack, Text, } from '@elytracms/studio/ui';
7
- const REGISTER_EXAMPLE = `// project code registers components + manifests
8
- import { defineComponent } from '@elytracms/core/component-registry'
9
-
10
- export const MarketingHeroManifest = defineComponent({
11
- id: 'project.MarketingHero', // project.* namespace
12
- namespace: 'project',
13
- title: 'Marketing Hero',
14
- props: {
15
- heading: { schema: z.string().min(1), bindable: true,
16
- control: { kind: 'text' }, defaultValue: 'Hello' },
17
- },
18
- slots: [],
19
- fixtures: [{ name: 'Default' }],
20
- })`;
5
+ import { RegistryBrowser } from './registry-browser';
6
+ import { deriveComponentStats } from './taxonomy';
7
+ import { Badge, Card, Code, Heading, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
21
8
  /**
22
- * The Components section landing view (EC-223 Stage 6): registry health overview
23
- * + the authoring guide, shown when no component is selected. Takes `projectId`
24
- * as a prop — the splat resolver sources it from the route, not a route file.
9
+ * The Components section landing view (EC-050, overhauled EC-320). Serves BOTH
10
+ * audiences: editors get a live census of what components exist (totals, the
11
+ * base/project split, per-category breakdown, health) and a "pick one" nudge;
12
+ * devs get pointers to the real in-repo docs for registering and extending
13
+ * components — no code sample to drift, no invented docs.
25
14
  */
26
15
  export function ComponentsIndex({ projectId }) {
27
16
  const studio = getStudioRegistry();
17
+ const manifests = studio.registry.list();
18
+ const stats = deriveComponentStats(manifests);
28
19
  const health = registryHealth(studio);
29
- const primitives = studio.registry.primitives().length;
30
- const projects = studio.registry.projectComponents().length;
31
- return (_jsxs("div", { className: "mx-auto w-full max-w-4xl p-4 sm:p-6", children: [_jsx("div", { className: "mb-6 h-80 overflow-hidden rounded-lg border border-border md:hidden", children: _jsx(RegistryBrowser, { projectId: projectId }) }), _jsxs(Stack, { gap: "6", children: [_jsxs(Stack, { gap: "1", children: [_jsx(Heading, { size: "display", children: translate('components.index.title') }), _jsx(Text, { tone: "muted", children: translate('components.index.subtitle') })] }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [_jsx(StatCard, { label: translate('components.index.stat.total'), value: health.total, hint: translate('components.index.stat.totalHint', { primitives, projects }) }), _jsx(StatCard, { label: translate('components.index.stat.valid'), value: health.valid, tone: "success" }), _jsx(StatCard, { label: translate('components.index.stat.needAttention'), value: health.incompatible + health.invalid, tone: health.incompatible + health.invalid > 0 ? 'warning' : 'muted', hint: translate('components.index.stat.needAttentionHint', {
20
+ return (_jsxs("div", { className: "mx-auto w-full max-w-4xl p-4 sm:p-6", children: [_jsx("div", { className: "mb-6 h-80 overflow-hidden rounded-lg border border-border md:hidden", children: _jsx(RegistryBrowser, { projectId: projectId }) }), _jsxs(Stack, { gap: "6", children: [_jsxs(Stack, { gap: "1", children: [_jsx(Heading, { size: "display", children: translate('components.index.title') }), _jsx(Text, { tone: "muted", children: translate('components.index.subtitle') })] }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [_jsx(StatCard, { label: translate('components.index.stat.total'), value: stats.total, hint: translate('components.index.stat.totalHint', {
21
+ primitives: stats.base,
22
+ projects: stats.project,
23
+ }) }), _jsx(StatCard, { label: translate('components.index.stat.valid'), value: health.valid, tone: "success" }), _jsx(StatCard, { label: translate('components.index.stat.needAttention'), value: health.incompatible + health.invalid, tone: health.incompatible + health.invalid > 0 ? 'warning' : 'muted', hint: translate('components.index.stat.needAttentionHint', {
32
24
  incompatible: health.incompatible,
33
25
  invalid: health.invalid,
34
- }) })] }), _jsxs(Card, { className: "flex items-center gap-3 p-4", children: [_jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-muted-foreground", children: _jsx(MousePointerClick, { className: "size-4" }) }), _jsx(Text, { size: "sm", tone: "muted", children: translate('components.index.pickHint') })] }), _jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsx(Blocks, { className: "size-4 text-muted-foreground" }), _jsx(Text, { size: "sm", weight: "semibold", children: translate('components.index.guide.title') }), _jsx(Spacer, {}), _jsx(Badge, { variant: "outline", children: translate('components.index.guide.badge') })] }), _jsxs(Stack, { gap: "3", className: "p-4", children: [_jsxs(Text, { size: "sm", tone: "muted", children: ["Project code owns its components. Register a manifest under the", ' ', _jsx(Code, { tone: "primary", children: "project.*" }), " namespace (platform primitives live under", ' ', _jsx(Code, { tone: "muted", children: "base.*" }), "). The manifest declares prop schemas, defaults, inspector controls, binding permissions, slots, and preview fixtures."] }), _jsx("pre", { className: "overflow-x-auto rounded-lg border border-border bg-muted/40 p-3 font-mono text-xs leading-relaxed text-foreground/90", children: REGISTER_EXAMPLE }), _jsx(Text, { size: "xs", tone: "subtle", children: translate('components.index.guide.fullGuide', { path: 'docs/component-authoring.md' }) })] })] })] })] }));
26
+ }) })] }), stats.categories.length > 0 ? (_jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsx(Layers, { className: "size-4 text-muted-foreground" }), _jsx(Text, { size: "sm", weight: "semibold", children: translate('components.index.byCategory') })] }), _jsx(Row, { gap: "2", wrap: true, className: "p-4", children: stats.categories.map((category) => (_jsxs(Badge, { variant: "outline", children: [_jsx("span", { className: category.key ? 'capitalize' : undefined, children: category.key || translate('components.taxonomy.uncategorized') }), _jsx("span", { className: "font-mono text-muted-foreground", children: category.count })] }, category.key || '__uncategorized__'))) })] })) : null, _jsxs(Card, { className: "flex items-center gap-3 p-4", children: [_jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-muted-foreground", children: _jsx(MousePointerClick, { className: "size-4" }) }), _jsx(Text, { size: "sm", tone: "muted", children: translate('components.index.pickHint') })] }), _jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsx(BookOpen, { className: "size-4 text-muted-foreground" }), _jsx(Text, { size: "sm", weight: "semibold", children: translate('components.index.guide.title') }), _jsx(Spacer, {}), _jsx(Badge, { variant: "outline", children: translate('components.index.guide.badge') })] }), _jsxs(Stack, { gap: "3", className: "p-4", children: [_jsx(Text, { size: "sm", tone: "muted", children: translate('components.index.guide.intro') }), _jsxs(Stack, { gap: "2", children: [_jsx(DocPointer, { path: "docs/component-authoring.md", description: translate('components.index.guide.doc.authoring') }), _jsx(DocPointer, { path: "docs/fixtures.md", description: translate('components.index.guide.doc.fixtures') }), _jsx(DocPointer, { path: "docs/extensibility.md", description: translate('components.index.guide.doc.extensibility') })] }), _jsxs(Text, { size: "xs", tone: "subtle", children: [_jsx(Sparkles, { className: "mr-1 inline size-3", "aria-hidden": true }), translate('components.index.guide.namespaceNote')] })] })] })] })] }));
27
+ }
28
+ function DocPointer({ path, description }) {
29
+ return (_jsxs(Row, { gap: "3", align: "center", className: "rounded-md border border-border p-2.5", children: [_jsx(Code, { tone: "primary", className: "shrink-0", children: path }), _jsx(Text, { size: "xs", tone: "muted", children: description })] }));
35
30
  }
36
31
  function StatCard({ label, value, hint, tone = 'default', }) {
37
32
  const valueClass = tone === 'success'
@@ -1 +1 @@
1
- {"version":3,"file":"components-index.js","sourceRoot":"","sources":["../../../src/components/components/components-index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Blocks, MousePointerClick } from 'lucide-react'\nimport { getStudioRegistry, registryHealth } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { RegistryBrowser } from '@elytracms/studio/components/components/registry-browser'\nimport {\n Badge,\n Card,\n Code,\n Heading,\n Panel,\n PanelHeader,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\n\nconst REGISTER_EXAMPLE = `// project code registers components + manifests\nimport { defineComponent } from '@elytracms/core/component-registry'\n\nexport const MarketingHeroManifest = defineComponent({\n id: 'project.MarketingHero', // project.* namespace\n namespace: 'project',\n title: 'Marketing Hero',\n props: {\n heading: { schema: z.string().min(1), bindable: true,\n control: { kind: 'text' }, defaultValue: 'Hello' },\n },\n slots: [],\n fixtures: [{ name: 'Default' }],\n})`\n\n/**\n * The Components section landing view (EC-223 Stage 6): registry health overview\n * + the authoring guide, shown when no component is selected. Takes `projectId`\n * as a prop — the splat resolver sources it from the route, not a route file.\n */\nexport function ComponentsIndex({ projectId }: { projectId: string }) {\n const studio = getStudioRegistry()\n const health = registryHealth(studio)\n const primitives = studio.registry.primitives().length\n const projects = studio.registry.projectComponents().length\n\n return (\n <div className=\"mx-auto w-full max-w-4xl p-4 sm:p-6\">\n {/* Mobile-only browser (the sidebar is hidden under md). */}\n <div className=\"mb-6 h-80 overflow-hidden rounded-lg border border-border md:hidden\">\n <RegistryBrowser projectId={projectId} />\n </div>\n\n <Stack gap=\"6\">\n <Stack gap=\"1\">\n <Heading size=\"display\">{translate('components.index.title')}</Heading>\n <Text tone=\"muted\">{translate('components.index.subtitle')}</Text>\n </Stack>\n\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <StatCard\n label={translate('components.index.stat.total')}\n value={health.total}\n hint={translate('components.index.stat.totalHint', { primitives, projects })}\n />\n <StatCard label={translate('components.index.stat.valid')} value={health.valid} tone=\"success\" />\n <StatCard\n label={translate('components.index.stat.needAttention')}\n value={health.incompatible + health.invalid}\n tone={health.incompatible + health.invalid > 0 ? 'warning' : 'muted'}\n hint={translate('components.index.stat.needAttentionHint', {\n incompatible: health.incompatible,\n invalid: health.invalid,\n })}\n />\n </div>\n\n <Card className=\"flex items-center gap-3 p-4\">\n <span className=\"grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-muted-foreground\">\n <MousePointerClick className=\"size-4\" />\n </span>\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.pickHint')}\n </Text>\n </Card>\n\n <Panel>\n <PanelHeader>\n <Blocks className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.guide.title')}\n </Text>\n <Spacer />\n <Badge variant=\"outline\">{translate('components.index.guide.badge')}</Badge>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <Text size=\"sm\" tone=\"muted\">\n Project code owns its components. Register a manifest under the{' '}\n <Code tone=\"primary\">project.*</Code> namespace (platform primitives live under{' '}\n <Code tone=\"muted\">base.*</Code>). The manifest declares prop schemas,\n defaults, inspector controls, binding permissions, slots, and preview fixtures.\n </Text>\n <pre className=\"overflow-x-auto rounded-lg border border-border bg-muted/40 p-3 font-mono text-xs leading-relaxed text-foreground/90\">\n {REGISTER_EXAMPLE}\n </pre>\n <Text size=\"xs\" tone=\"subtle\">\n {translate('components.index.guide.fullGuide', { path: 'docs/component-authoring.md' })}\n </Text>\n </Stack>\n </Panel>\n </Stack>\n </div>\n )\n}\n\nfunction StatCard({\n label,\n value,\n hint,\n tone = 'default',\n}: {\n label: string\n value: number\n hint?: string\n tone?: 'default' | 'success' | 'warning' | 'muted'\n}) {\n const valueClass =\n tone === 'success'\n ? 'text-success'\n : tone === 'warning'\n ? 'text-warning'\n : tone === 'muted'\n ? 'text-muted-foreground'\n : 'text-foreground'\n return (\n <Card className=\"p-4\">\n <Text size=\"xs\" tone=\"muted\" mono>\n {label.toUpperCase()}\n </Text>\n <div className={`mt-1 text-2xl font-semibold tracking-tight ${valueClass}`}>{value}</div>\n {hint ? (\n <Text size=\"xs\" tone=\"subtle\" className=\"mt-0.5\">\n {hint}\n </Text>\n ) : null}\n </Card>\n )\n}\n"]}
1
+ {"version":3,"file":"components-index.js","sourceRoot":"","sources":["../../../src/components/components/components-index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { BookOpen, Layers, MousePointerClick, Sparkles } from 'lucide-react'\nimport { getStudioRegistry, registryHealth } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { RegistryBrowser } from './registry-browser'\nimport { deriveComponentStats } from './taxonomy'\nimport { Badge, Card, Code, Heading, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\n\n/**\n * The Components section landing view (EC-050, overhauled EC-320). Serves BOTH\n * audiences: editors get a live census of what components exist (totals, the\n * base/project split, per-category breakdown, health) and a \"pick one\" nudge;\n * devs get pointers to the real in-repo docs for registering and extending\n * components — no code sample to drift, no invented docs.\n */\nexport function ComponentsIndex({ projectId }: { projectId: string }) {\n const studio = getStudioRegistry()\n const manifests = studio.registry.list()\n const stats = deriveComponentStats(manifests)\n const health = registryHealth(studio)\n\n return (\n <div className=\"mx-auto w-full max-w-4xl p-4 sm:p-6\">\n {/* Mobile-only browser (the sidebar is hidden under md). */}\n <div className=\"mb-6 h-80 overflow-hidden rounded-lg border border-border md:hidden\">\n <RegistryBrowser projectId={projectId} />\n </div>\n\n <Stack gap=\"6\">\n <Stack gap=\"1\">\n <Heading size=\"display\">{translate('components.index.title')}</Heading>\n <Text tone=\"muted\">{translate('components.index.subtitle')}</Text>\n </Stack>\n\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <StatCard\n label={translate('components.index.stat.total')}\n value={stats.total}\n hint={translate('components.index.stat.totalHint', {\n primitives: stats.base,\n projects: stats.project,\n })}\n />\n <StatCard\n label={translate('components.index.stat.valid')}\n value={health.valid}\n tone=\"success\"\n />\n <StatCard\n label={translate('components.index.stat.needAttention')}\n value={health.incompatible + health.invalid}\n tone={health.incompatible + health.invalid > 0 ? 'warning' : 'muted'}\n hint={translate('components.index.stat.needAttentionHint', {\n incompatible: health.incompatible,\n invalid: health.invalid,\n })}\n />\n </div>\n\n {/* By category — a cheap census derived from manifest `category`. */}\n {stats.categories.length > 0 ? (\n <Panel>\n <PanelHeader>\n <Layers className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.byCategory')}\n </Text>\n </PanelHeader>\n <Row gap=\"2\" wrap className=\"p-4\">\n {stats.categories.map((category) => (\n <Badge key={category.key || '__uncategorized__'} variant=\"outline\">\n <span className={category.key ? 'capitalize' : undefined}>\n {category.key || translate('components.taxonomy.uncategorized')}\n </span>\n <span className=\"font-mono text-muted-foreground\">{category.count}</span>\n </Badge>\n ))}\n </Row>\n </Panel>\n ) : null}\n\n <Card className=\"flex items-center gap-3 p-4\">\n <span className=\"grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-muted-foreground\">\n <MousePointerClick className=\"size-4\" />\n </span>\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.pickHint')}\n </Text>\n </Card>\n\n {/* Dev guidance — pointers to the REAL in-repo docs (no code sample to drift). */}\n <Panel>\n <PanelHeader>\n <BookOpen className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.guide.title')}\n </Text>\n <Spacer />\n <Badge variant=\"outline\">{translate('components.index.guide.badge')}</Badge>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.guide.intro')}\n </Text>\n <Stack gap=\"2\">\n <DocPointer\n path=\"docs/component-authoring.md\"\n description={translate('components.index.guide.doc.authoring')}\n />\n <DocPointer\n path=\"docs/fixtures.md\"\n description={translate('components.index.guide.doc.fixtures')}\n />\n <DocPointer\n path=\"docs/extensibility.md\"\n description={translate('components.index.guide.doc.extensibility')}\n />\n </Stack>\n <Text size=\"xs\" tone=\"subtle\">\n <Sparkles className=\"mr-1 inline size-3\" aria-hidden />\n {translate('components.index.guide.namespaceNote')}\n </Text>\n </Stack>\n </Panel>\n </Stack>\n </div>\n )\n}\n\nfunction DocPointer({ path, description }: { path: string; description: string }) {\n return (\n <Row gap=\"3\" align=\"center\" className=\"rounded-md border border-border p-2.5\">\n <Code tone=\"primary\" className=\"shrink-0\">\n {path}\n </Code>\n <Text size=\"xs\" tone=\"muted\">\n {description}\n </Text>\n </Row>\n )\n}\n\nfunction StatCard({\n label,\n value,\n hint,\n tone = 'default',\n}: {\n label: string\n value: number\n hint?: string\n tone?: 'default' | 'success' | 'warning' | 'muted'\n}) {\n const valueClass =\n tone === 'success'\n ? 'text-success'\n : tone === 'warning'\n ? 'text-warning'\n : tone === 'muted'\n ? 'text-muted-foreground'\n : 'text-foreground'\n return (\n <Card className=\"p-4\">\n <Text size=\"xs\" tone=\"muted\" mono>\n {label.toUpperCase()}\n </Text>\n <div className={`mt-1 text-2xl font-semibold tracking-tight ${valueClass}`}>{value}</div>\n {hint ? (\n <Text size=\"xs\" tone=\"subtle\" className=\"mt-0.5\">\n {hint}\n </Text>\n ) : null}\n </Card>\n )\n}\n"]}
@@ -1,3 +1,10 @@
1
+ /**
2
+ * The registry browser sidebar (EC-050, taxonomy overhaul EC-320). A searchable,
3
+ * two-level tree: namespace (base / project) → category (the manifest's declared
4
+ * `category`, uncategorized last). The segmented control narrows to a namespace;
5
+ * search filters across id/title/category/description. Grouping + search are pure
6
+ * ({@link deriveTaxonomy}), so what renders here is exactly what the tests pin.
7
+ */
1
8
  export declare function RegistryBrowser({ projectId, activeComponentId, }: {
2
9
  projectId: string;
3
10
  activeComponentId?: string;
@@ -2,58 +2,42 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router';
3
3
  import { Search } from 'lucide-react';
4
4
  import { useMemo, useState } from 'react';
5
- import { isPrimitive } from '@elytracms/core/component-registry';
6
5
  import { componentStatus, getStudioRegistry } from '@elytracms/studio/lib/registry';
7
6
  import { translate } from '@elytracms/studio/lib/i18n';
8
7
  import { Code, Input, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
9
8
  import { cn } from '@elytracms/studio/lib/utils';
10
9
  import { ComponentStatusDot } from './component-status';
10
+ import { deriveTaxonomy } from './taxonomy';
11
+ /**
12
+ * The registry browser sidebar (EC-050, taxonomy overhaul EC-320). A searchable,
13
+ * two-level tree: namespace (base / project) → category (the manifest's declared
14
+ * `category`, uncategorized last). The segmented control narrows to a namespace;
15
+ * search filters across id/title/category/description. Grouping + search are pure
16
+ * ({@link deriveTaxonomy}), so what renders here is exactly what the tests pin.
17
+ */
11
18
  export function RegistryBrowser({ projectId, activeComponentId, }) {
12
19
  const studio = getStudioRegistry();
13
20
  const [query, setQuery] = useState('');
14
21
  const [namespace, setNamespace] = useState('all');
15
- const [category, setCategory] = useState(null);
22
+ const manifests = useMemo(() => studio.registry.list(), [studio]);
16
23
  const primitiveCount = studio.registry.primitives().length;
17
24
  const projectCount = studio.registry.projectComponents().length;
18
- // Switching namespace clears any category that doesn't exist in the new one.
19
- function selectNamespace(ns) {
20
- setNamespace(ns);
21
- setCategory(null);
22
- }
23
- // Categories are scoped to the active namespace so every chip can yield results.
24
- const categories = useMemo(() => {
25
- const set = new Set();
26
- for (const m of studio.registry.list({ namespace: namespace === 'all' ? undefined : namespace })) {
27
- if (m.category)
28
- set.add(m.category);
29
- }
30
- return [...set].sort();
31
- }, [studio, namespace]);
32
- const results = useMemo(() => studio.registry.list({
33
- query: query.trim() || undefined,
34
- namespace: namespace === 'all' ? undefined : namespace,
35
- category: category ?? undefined,
36
- }), [studio, query, namespace, category]);
37
- const primitives = results.filter((m) => isPrimitive(m.id));
38
- const projects = results.filter((m) => !isPrimitive(m.id));
39
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "shrink-0 space-y-2 border-b border-border p-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: translate('components.browser.searchPlaceholder'), className: "pl-8", "aria-label": translate('components.browser.searchAria') })] }), _jsxs(Row, { gap: "1", className: "rounded-md bg-muted/50 p-0.5", role: "group", "aria-label": translate('components.browser.namespaceAria'), children: [_jsx(NamespaceTab, { label: translate('components.browser.all'), count: primitiveCount + projectCount, active: namespace === 'all', onClick: () => selectNamespace('all') }), _jsx(NamespaceTab, { label: translate('components.browser.primitives'), count: primitiveCount, active: namespace === 'base', onClick: () => selectNamespace('base') }), _jsx(NamespaceTab, { label: translate('components.browser.project'), count: projectCount, active: namespace === 'project', onClick: () => selectNamespace('project') })] }), categories.length > 0 ? (_jsxs(Row, { gap: "1", wrap: true, children: [_jsx(CategoryChip, { label: translate('components.browser.all'), active: category === null, onClick: () => setCategory(null) }), categories.map((c) => (_jsx(CategoryChip, { label: c, active: category === c, onClick: () => setCategory(c) }, c)))] })) : null] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-2", children: results.length === 0 ? (_jsx(Text, { size: "sm", tone: "muted", className: "p-3", children: query.trim()
25
+ // The full taxonomy for the query; the segmented control then narrows namespaces.
26
+ const taxonomy = useMemo(() => deriveTaxonomy(manifests, query), [manifests, query]);
27
+ const shown = taxonomy.filter((group) => namespace === 'all' || group.namespace === namespace);
28
+ const empty = shown.length === 0;
29
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "shrink-0 space-y-2 border-b border-border p-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: translate('components.browser.searchPlaceholder'), className: "pl-8", "aria-label": translate('components.browser.searchAria') })] }), _jsxs(Row, { gap: "1", className: "rounded-md bg-muted/50 p-0.5", role: "group", "aria-label": translate('components.browser.namespaceAria'), children: [_jsx(NamespaceTab, { label: translate('components.browser.all'), count: primitiveCount + projectCount, active: namespace === 'all', onClick: () => setNamespace('all') }), _jsx(NamespaceTab, { label: translate('components.browser.primitives'), count: primitiveCount, active: namespace === 'base', onClick: () => setNamespace('base') }), _jsx(NamespaceTab, { label: translate('components.browser.project'), count: projectCount, active: namespace === 'project', onClick: () => setNamespace('project') })] })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-2", children: empty ? (_jsx(Text, { size: "sm", tone: "muted", className: "p-3", children: query.trim()
40
30
  ? translate('components.browser.noMatchQuery', { query })
41
- : translate('components.browser.noMatchFilters') })) : (_jsxs(Stack, { gap: "3", children: [(namespace === 'all' || namespace === 'base') && primitives.length > 0 ? (_jsx(GroupedList, { heading: "base.*", count: primitives.length, manifests: primitives, projectId: projectId, activeComponentId: activeComponentId })) : null, (namespace === 'all' || namespace === 'project') && projects.length > 0 ? (_jsx(GroupedList, { heading: "project.*", count: projects.length, manifests: projects, projectId: projectId, activeComponentId: activeComponentId })) : null] })) })] }));
31
+ : translate('components.browser.noMatchFilters') })) : (_jsx(Stack, { gap: "4", children: shown.map((group) => (_jsxs(Stack, { gap: "2", children: [_jsxs(Row, { gap: "2", className: "px-2", children: [_jsx(Code, { tone: "muted", children: `${group.namespace}.*` }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "subtle", mono: true, children: group.count })] }), _jsx(Stack, { gap: "3", children: group.categories.map((category) => (_jsxs(Stack, { gap: "1", children: [_jsx(Text, { size: "xs", weight: "medium", tone: "subtle", className: cn('px-2 uppercase tracking-wide', category.key && 'capitalize'), children: category.key || translate('components.taxonomy.uncategorized') }), _jsx(Stack, { gap: "px", children: category.manifests.map((m) => (_jsx(ComponentRow, { manifest: m, projectId: projectId, active: m.id === activeComponentId }, m.id))) })] }, category.key || '__uncategorized__'))) })] }, group.namespace))) })) })] }));
42
32
  }
43
33
  function NamespaceTab({ label, count, active, onClick, }) {
44
34
  return (_jsxs("button", { type: "button", "aria-pressed": active, onClick: onClick, className: cn('flex flex-1 items-center justify-center gap-1.5 rounded-[5px] px-2 py-1 text-xs font-medium transition-colors', active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'), children: [label, _jsx("span", { className: "font-mono text-[0.65rem] text-muted-foreground", children: count })] }));
45
35
  }
46
- function CategoryChip({ label, active, onClick }) {
47
- return (_jsx("button", { type: "button", onClick: onClick, className: cn('rounded-full border px-2 py-0.5 text-xs capitalize transition-colors', active
48
- ? 'border-primary/30 bg-primary/10 text-primary'
49
- : 'border-border text-muted-foreground hover:bg-muted'), children: label }));
50
- }
51
- function GroupedList({ heading, count, manifests, projectId, activeComponentId, }) {
36
+ function ComponentRow({ manifest, projectId, active, }) {
52
37
  const studio = getStudioRegistry();
53
- return (_jsxs(Stack, { gap: "1", children: [_jsxs(Row, { gap: "2", className: "px-2", children: [_jsx(Code, { tone: "muted", children: heading }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "subtle", mono: true, children: count })] }), _jsx(Stack, { gap: "px", children: manifests.map((m) => {
54
- const status = componentStatus(studio, m);
55
- const active = m.id === activeComponentId;
56
- return (_jsxs(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: studioPath.component(m.id) }, "aria-current": active ? 'page' : undefined, className: cn('flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors', active ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: [_jsx(ComponentStatusDot, { status: status }), _jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: m.title ?? m.id }), m.category ? (_jsx("span", { className: "shrink-0 text-[0.7rem] capitalize text-muted-foreground", children: m.category })) : null] }, m.id));
57
- }) })] }));
38
+ const status = componentStatus(studio, manifest);
39
+ return (_jsxs(Link, { to: STUDIO_PROJECT_ROUTE, params: { projectId, _splat: studioPath.component(manifest.id) }, "aria-current": active ? 'page' : undefined, className: cn('flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors', active
40
+ ? 'bg-accent text-accent-foreground'
41
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: [_jsx(ComponentStatusDot, { status: status }), _jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: manifest.title ?? manifest.id })] }));
58
42
  }
59
43
  //# sourceMappingURL=registry-browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry-browser.js","sourceRoot":"","sources":["../../../src/components/components/registry-browser.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { Search } from 'lucide-react'\nimport { useMemo, useState } from 'react'\nimport { isPrimitive } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { componentStatus, getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { Code, Input, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ComponentStatusDot } from './component-status'\n\ntype NamespaceFilter = 'all' | 'base' | 'project'\n\nexport function RegistryBrowser({\n projectId,\n activeComponentId,\n}: {\n projectId: string\n activeComponentId?: string\n}) {\n const studio = getStudioRegistry()\n const [query, setQuery] = useState('')\n const [namespace, setNamespace] = useState<NamespaceFilter>('all')\n const [category, setCategory] = useState<string | null>(null)\n\n const primitiveCount = studio.registry.primitives().length\n const projectCount = studio.registry.projectComponents().length\n\n // Switching namespace clears any category that doesn't exist in the new one.\n function selectNamespace(ns: NamespaceFilter) {\n setNamespace(ns)\n setCategory(null)\n }\n\n // Categories are scoped to the active namespace so every chip can yield results.\n const categories = useMemo(() => {\n const set = new Set<string>()\n for (const m of studio.registry.list({ namespace: namespace === 'all' ? undefined : namespace })) {\n if (m.category) set.add(m.category)\n }\n return [...set].sort()\n }, [studio, namespace])\n\n const results = useMemo(\n () =>\n studio.registry.list({\n query: query.trim() || undefined,\n namespace: namespace === 'all' ? undefined : namespace,\n category: category ?? undefined,\n }),\n [studio, query, namespace, category],\n )\n\n const primitives = results.filter((m) => isPrimitive(m.id))\n const projects = results.filter((m) => !isPrimitive(m.id))\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 space-y-2 border-b border-border p-3\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('components.browser.searchPlaceholder')}\n className=\"pl-8\"\n aria-label={translate('components.browser.searchAria')}\n />\n </div>\n <Row gap=\"1\" className=\"rounded-md bg-muted/50 p-0.5\" role=\"group\" aria-label={translate('components.browser.namespaceAria')}>\n <NamespaceTab label={translate('components.browser.all')} count={primitiveCount + projectCount} active={namespace === 'all'} onClick={() => selectNamespace('all')} />\n <NamespaceTab label={translate('components.browser.primitives')} count={primitiveCount} active={namespace === 'base'} onClick={() => selectNamespace('base')} />\n <NamespaceTab label={translate('components.browser.project')} count={projectCount} active={namespace === 'project'} onClick={() => selectNamespace('project')} />\n </Row>\n {categories.length > 0 ? (\n <Row gap=\"1\" wrap>\n <CategoryChip label={translate('components.browser.all')} active={category === null} onClick={() => setCategory(null)} />\n {categories.map((c) => (\n <CategoryChip key={c} label={c} active={category === c} onClick={() => setCategory(c)} />\n ))}\n </Row>\n ) : null}\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto p-2\">\n {results.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\" className=\"p-3\">\n {query.trim()\n ? translate('components.browser.noMatchQuery', { query })\n : translate('components.browser.noMatchFilters')}\n </Text>\n ) : (\n <Stack gap=\"3\">\n {(namespace === 'all' || namespace === 'base') && primitives.length > 0 ? (\n <GroupedList\n heading=\"base.*\"\n count={primitives.length}\n manifests={primitives}\n projectId={projectId}\n activeComponentId={activeComponentId}\n />\n ) : null}\n {(namespace === 'all' || namespace === 'project') && projects.length > 0 ? (\n <GroupedList\n heading=\"project.*\"\n count={projects.length}\n manifests={projects}\n projectId={projectId}\n activeComponentId={activeComponentId}\n />\n ) : null}\n </Stack>\n )}\n </div>\n </div>\n )\n}\n\nfunction NamespaceTab({\n label,\n count,\n active,\n onClick,\n}: {\n label: string\n count: number\n active: boolean\n onClick: () => void\n}) {\n return (\n <button\n type=\"button\"\n aria-pressed={active}\n onClick={onClick}\n className={cn(\n 'flex flex-1 items-center justify-center gap-1.5 rounded-[5px] px-2 py-1 text-xs font-medium transition-colors',\n active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {label}\n <span className=\"font-mono text-[0.65rem] text-muted-foreground\">{count}</span>\n </button>\n )\n}\n\nfunction CategoryChip({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs capitalize transition-colors',\n active\n ? 'border-primary/30 bg-primary/10 text-primary'\n : 'border-border text-muted-foreground hover:bg-muted',\n )}\n >\n {label}\n </button>\n )\n}\n\nfunction GroupedList({\n heading,\n count,\n manifests,\n projectId,\n activeComponentId,\n}: {\n heading: string\n count: number\n manifests: ComponentManifest[]\n projectId: string\n activeComponentId?: string\n}) {\n const studio = getStudioRegistry()\n return (\n <Stack gap=\"1\">\n <Row gap=\"2\" className=\"px-2\">\n <Code tone=\"muted\">{heading}</Code>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {count}\n </Text>\n </Row>\n <Stack gap=\"px\">\n {manifests.map((m) => {\n const status = componentStatus(studio, m)\n const active = m.id === activeComponentId\n return (\n <Link\n key={m.id}\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.component(m.id) }}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',\n active ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n <ComponentStatusDot status={status} />\n <span className=\"min-w-0 flex-1 truncate font-medium text-foreground\">{m.title ?? m.id}</span>\n {m.category ? (\n <span className=\"shrink-0 text-[0.7rem] capitalize text-muted-foreground\">{m.category}</span>\n ) : null}\n </Link>\n )\n })}\n </Stack>\n </Stack>\n )\n}\n"]}
1
+ {"version":3,"file":"registry-browser.js","sourceRoot":"","sources":["../../../src/components/components/registry-browser.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { Search } from 'lucide-react'\nimport { useMemo, useState } from 'react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { componentStatus, getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { Code, Input, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ComponentStatusDot } from './component-status'\nimport { deriveTaxonomy } from './taxonomy'\nimport type { ComponentNamespace } from './taxonomy'\n\ntype NamespaceFilter = 'all' | ComponentNamespace\n\n/**\n * The registry browser sidebar (EC-050, taxonomy overhaul EC-320). A searchable,\n * two-level tree: namespace (base / project) → category (the manifest's declared\n * `category`, uncategorized last). The segmented control narrows to a namespace;\n * search filters across id/title/category/description. Grouping + search are pure\n * ({@link deriveTaxonomy}), so what renders here is exactly what the tests pin.\n */\nexport function RegistryBrowser({\n projectId,\n activeComponentId,\n}: {\n projectId: string\n activeComponentId?: string\n}) {\n const studio = getStudioRegistry()\n const [query, setQuery] = useState('')\n const [namespace, setNamespace] = useState<NamespaceFilter>('all')\n\n const manifests = useMemo(() => studio.registry.list(), [studio])\n const primitiveCount = studio.registry.primitives().length\n const projectCount = studio.registry.projectComponents().length\n\n // The full taxonomy for the query; the segmented control then narrows namespaces.\n const taxonomy = useMemo(() => deriveTaxonomy(manifests, query), [manifests, query])\n const shown = taxonomy.filter((group) => namespace === 'all' || group.namespace === namespace)\n const empty = shown.length === 0\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <div className=\"shrink-0 space-y-2 border-b border-border p-3\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('components.browser.searchPlaceholder')}\n className=\"pl-8\"\n aria-label={translate('components.browser.searchAria')}\n />\n </div>\n <Row\n gap=\"1\"\n className=\"rounded-md bg-muted/50 p-0.5\"\n role=\"group\"\n aria-label={translate('components.browser.namespaceAria')}\n >\n <NamespaceTab\n label={translate('components.browser.all')}\n count={primitiveCount + projectCount}\n active={namespace === 'all'}\n onClick={() => setNamespace('all')}\n />\n <NamespaceTab\n label={translate('components.browser.primitives')}\n count={primitiveCount}\n active={namespace === 'base'}\n onClick={() => setNamespace('base')}\n />\n <NamespaceTab\n label={translate('components.browser.project')}\n count={projectCount}\n active={namespace === 'project'}\n onClick={() => setNamespace('project')}\n />\n </Row>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-auto p-2\">\n {empty ? (\n <Text size=\"sm\" tone=\"muted\" className=\"p-3\">\n {query.trim()\n ? translate('components.browser.noMatchQuery', { query })\n : translate('components.browser.noMatchFilters')}\n </Text>\n ) : (\n <Stack gap=\"4\">\n {shown.map((group) => (\n <Stack key={group.namespace} gap=\"2\">\n <Row gap=\"2\" className=\"px-2\">\n <Code tone=\"muted\">{`${group.namespace}.*`}</Code>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {group.count}\n </Text>\n </Row>\n <Stack gap=\"3\">\n {group.categories.map((category) => (\n <Stack key={category.key || '__uncategorized__'} gap=\"1\">\n <Text\n size=\"xs\"\n weight=\"medium\"\n tone=\"subtle\"\n className={cn('px-2 uppercase tracking-wide', category.key && 'capitalize')}\n >\n {category.key || translate('components.taxonomy.uncategorized')}\n </Text>\n <Stack gap=\"px\">\n {category.manifests.map((m) => (\n <ComponentRow\n key={m.id}\n manifest={m}\n projectId={projectId}\n active={m.id === activeComponentId}\n />\n ))}\n </Stack>\n </Stack>\n ))}\n </Stack>\n </Stack>\n ))}\n </Stack>\n )}\n </div>\n </div>\n )\n}\n\nfunction NamespaceTab({\n label,\n count,\n active,\n onClick,\n}: {\n label: string\n count: number\n active: boolean\n onClick: () => void\n}) {\n return (\n <button\n type=\"button\"\n aria-pressed={active}\n onClick={onClick}\n className={cn(\n 'flex flex-1 items-center justify-center gap-1.5 rounded-[5px] px-2 py-1 text-xs font-medium transition-colors',\n active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {label}\n <span className=\"font-mono text-[0.65rem] text-muted-foreground\">{count}</span>\n </button>\n )\n}\n\nfunction ComponentRow({\n manifest,\n projectId,\n active,\n}: {\n manifest: ComponentManifest\n projectId: string\n active: boolean\n}) {\n const studio = getStudioRegistry()\n const status = componentStatus(studio, manifest)\n return (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: studioPath.component(manifest.id) }}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',\n active\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n <ComponentStatusDot status={status} />\n <span className=\"min-w-0 flex-1 truncate font-medium text-foreground\">\n {manifest.title ?? manifest.id}\n </span>\n </Link>\n )\n}\n"]}
@@ -0,0 +1,51 @@
1
+ import type { ComponentManifest } from '@elytracms/core/component-registry';
2
+ /**
3
+ * EC-320 — the component registry TAXONOMY, derived purely from the metadata a
4
+ * manifest actually declares (no invented axes). Two levels of grouping:
5
+ *
6
+ * 1. **namespace** — `base` platform primitives vs `project` components
7
+ * (`isPrimitive(id)`, i.e. `namespaceOf(id) === 'base'`).
8
+ * 2. **category** — the manifest's optional `category` string, with an
9
+ * "uncategorized" bucket (`key: ''`) collected last.
10
+ *
11
+ * Manifests carry no free-form `tags`, so category is the finest declared axis —
12
+ * this makes the best of what's there rather than fabricating structure. Pure and
13
+ * store-agnostic so the sidebar's grouping/search is unit-testable without React.
14
+ */
15
+ export type ComponentNamespace = 'base' | 'project';
16
+ export interface TaxonomyCategory {
17
+ /** The manifest category, or `''` for the uncategorized bucket. */
18
+ key: string;
19
+ manifests: ComponentManifest[];
20
+ }
21
+ export interface TaxonomyNamespace {
22
+ namespace: ComponentNamespace;
23
+ count: number;
24
+ categories: TaxonomyCategory[];
25
+ }
26
+ /** Case-insensitive match against a manifest's id, title, category, description. */
27
+ export declare function manifestMatchesQuery(manifest: ComponentManifest, query: string): boolean;
28
+ /**
29
+ * Derive the two-level taxonomy for the sidebar. `base` precedes `project` (rail
30
+ * order). An optional `query` filters manifests first; a namespace/category with
31
+ * no surviving manifest is omitted, so every rendered group yields results.
32
+ */
33
+ export declare function deriveTaxonomy(manifests: readonly ComponentManifest[], query?: string): TaxonomyNamespace[];
34
+ export interface CategoryCount {
35
+ /** The manifest category, or `''` for the uncategorized bucket. */
36
+ key: string;
37
+ count: number;
38
+ }
39
+ /**
40
+ * Cheap registry stats for the intro page (EC-320): the total, the base/project
41
+ * split, and a per-category breakdown (uncategorized last). Derived purely from
42
+ * the manifest set — no health/implementation lookups here (those come from the
43
+ * separate {@link import('@elytracms/studio/lib/registry').registryHealth}).
44
+ */
45
+ export interface ComponentStats {
46
+ total: number;
47
+ base: number;
48
+ project: number;
49
+ categories: CategoryCount[];
50
+ }
51
+ export declare function deriveComponentStats(manifests: readonly ComponentManifest[]): ComponentStats;