@elytracms/studio 0.0.94 → 0.0.96

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 (164) hide show
  1. package/convex/asset-alt-migration.test.ts +2 -2
  2. package/convex/asset-meta-patch.test.ts +218 -0
  3. package/convex/asset-replace-delete-race.test.ts +127 -0
  4. package/convex/assets.ts +27 -2
  5. package/convex/cms.ts +124 -8
  6. package/convex/delivery-redirects.test.ts +167 -0
  7. package/convex/delivery.ts +15 -5
  8. package/convex/rail-write-parity.test.ts +6 -6
  9. package/convex/redirects.ts +1 -1
  10. package/convex/schema.ts +6 -0
  11. package/convex/strict-reference-race.test.ts +139 -0
  12. package/dist/addon.d.ts +1 -1
  13. package/dist/addon.js.map +1 -1
  14. package/dist/components/ai/ai-chat-panel.js +15 -2
  15. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  16. package/dist/components/ai/translate-document-action.d.ts +6 -0
  17. package/dist/components/ai/translate-document-action.js.map +1 -1
  18. package/dist/components/ai/translate-document-dialog.d.ts +1 -1
  19. package/dist/components/ai/translate-document-dialog.js +5 -5
  20. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  21. package/dist/components/assets/asset-detail.js +29 -11
  22. package/dist/components/assets/asset-detail.js.map +1 -1
  23. package/dist/components/assets/asset-locale.d.ts +16 -0
  24. package/dist/components/assets/asset-locale.js +30 -0
  25. package/dist/components/assets/asset-locale.js.map +1 -0
  26. package/dist/components/assets/asset-meta-drift-notice.d.ts +13 -0
  27. package/dist/components/assets/asset-meta-drift-notice.js +22 -0
  28. package/dist/components/assets/asset-meta-drift-notice.js.map +1 -0
  29. package/dist/components/assets/asset-meta-label.d.ts +13 -0
  30. package/dist/components/assets/asset-meta-label.js +17 -0
  31. package/dist/components/assets/asset-meta-label.js.map +1 -0
  32. package/dist/components/assets/asset-metadata.d.ts +89 -15
  33. package/dist/components/assets/asset-metadata.js +123 -11
  34. package/dist/components/assets/asset-metadata.js.map +1 -1
  35. package/dist/components/assets/asset-search.d.ts +6 -2
  36. package/dist/components/assets/asset-search.js +19 -3
  37. package/dist/components/assets/asset-search.js.map +1 -1
  38. package/dist/components/assets/asset-thumb.js +6 -2
  39. package/dist/components/assets/asset-thumb.js.map +1 -1
  40. package/dist/components/assets/assets-collection-view-source.d.ts +25 -9
  41. package/dist/components/assets/assets-collection-view-source.js +80 -26
  42. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  43. package/dist/components/assets/assets-store.js +17 -4
  44. package/dist/components/assets/assets-store.js.map +1 -1
  45. package/dist/components/builder/builder-store.d.ts +21 -0
  46. package/dist/components/builder/builder-store.js +22 -0
  47. package/dist/components/builder/builder-store.js.map +1 -1
  48. package/dist/components/builder/canvas-commit-boundary.d.ts +22 -0
  49. package/dist/components/builder/canvas-commit-boundary.js +27 -0
  50. package/dist/components/builder/canvas-commit-boundary.js.map +1 -0
  51. package/dist/components/builder/canvas-rebase.d.ts +101 -0
  52. package/dist/components/builder/canvas-rebase.js +179 -0
  53. package/dist/components/builder/canvas-rebase.js.map +1 -0
  54. package/dist/components/builder/canvas-render-context.d.ts +6 -3
  55. package/dist/components/builder/canvas-render-context.js +17 -8
  56. package/dist/components/builder/canvas-render-context.js.map +1 -1
  57. package/dist/components/builder/composition-field-editor.d.ts +26 -2
  58. package/dist/components/builder/composition-field-editor.js +46 -27
  59. package/dist/components/builder/composition-field-editor.js.map +1 -1
  60. package/dist/components/builder/node-factory.js +15 -3
  61. package/dist/components/builder/node-factory.js.map +1 -1
  62. package/dist/components/builder/property-inspector.js +3 -1
  63. package/dist/components/builder/property-inspector.js.map +1 -1
  64. package/dist/components/builder/run-inspector.js +10 -6
  65. package/dist/components/builder/run-inspector.js.map +1 -1
  66. package/dist/components/cms/asset-field.d.ts +5 -2
  67. package/dist/components/cms/asset-field.js +34 -17
  68. package/dist/components/cms/asset-field.js.map +1 -1
  69. package/dist/components/cms/asset-picker-core.d.ts +2 -2
  70. package/dist/components/cms/asset-picker-core.js +2 -2
  71. package/dist/components/cms/asset-picker-core.js.map +1 -1
  72. package/dist/components/cms/asset-picker.js +6 -2
  73. package/dist/components/cms/asset-picker.js.map +1 -1
  74. package/dist/components/cms/canvas-commit.d.ts +62 -0
  75. package/dist/components/cms/canvas-commit.js +39 -0
  76. package/dist/components/cms/canvas-commit.js.map +1 -0
  77. package/dist/components/cms/cms-store.d.ts +21 -8
  78. package/dist/components/cms/cms-store.js +19 -10
  79. package/dist/components/cms/cms-store.js.map +1 -1
  80. package/dist/components/cms/collection-table.js +27 -18
  81. package/dist/components/cms/collection-table.js.map +1 -1
  82. package/dist/components/cms/commit-policy.d.ts +6 -2
  83. package/dist/components/cms/commit-policy.js +1 -1
  84. package/dist/components/cms/commit-policy.js.map +1 -1
  85. package/dist/components/cms/detail-editor.js +12 -2
  86. package/dist/components/cms/detail-editor.js.map +1 -1
  87. package/dist/components/cms/draft-commit.d.ts +16 -3
  88. package/dist/components/cms/draft-commit.js +3 -2
  89. package/dist/components/cms/draft-commit.js.map +1 -1
  90. package/dist/components/cms/use-document-draft.d.ts +121 -5
  91. package/dist/components/cms/use-document-draft.js +197 -54
  92. package/dist/components/cms/use-document-draft.js.map +1 -1
  93. package/dist/components/cms/use-live-url.d.ts +6 -1
  94. package/dist/components/cms/use-live-url.js +12 -9
  95. package/dist/components/cms/use-live-url.js.map +1 -1
  96. package/dist/components/collection-view/cms-collection-view-source.js +11 -5
  97. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  98. package/dist/components/components/transfer-dialog.js +6 -4
  99. package/dist/components/components/transfer-dialog.js.map +1 -1
  100. package/dist/components/deployment-setup-gate.js +10 -1
  101. package/dist/components/deployment-setup-gate.js.map +1 -1
  102. package/dist/components/records/record-lens.js +3 -8
  103. package/dist/components/records/record-lens.js.map +1 -1
  104. package/dist/lib/ai/chat-state.d.ts +7 -0
  105. package/dist/lib/ai/chat-state.js.map +1 -1
  106. package/dist/lib/ai/tools.d.ts +8 -0
  107. package/dist/lib/ai/tools.js +4 -0
  108. package/dist/lib/ai/tools.js.map +1 -1
  109. package/dist/lib/assets/upload.d.ts +25 -7
  110. package/dist/lib/assets/upload.js +30 -21
  111. package/dist/lib/assets/upload.js.map +1 -1
  112. package/dist/lib/cms/published-documents.d.ts +24 -6
  113. package/dist/lib/cms/published-documents.js +39 -6
  114. package/dist/lib/cms/published-documents.js.map +1 -1
  115. package/dist/lib/cms-views/view-model.d.ts +25 -105
  116. package/dist/lib/cms-views/view-model.js +36 -103
  117. package/dist/lib/cms-views/view-model.js.map +1 -1
  118. package/dist/lib/collection-view/__fixtures__/document-writes.js +1 -0
  119. package/dist/lib/collection-view/__fixtures__/document-writes.js.map +1 -1
  120. package/dist/lib/collection-view/collection-view-source.d.ts +21 -8
  121. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  122. package/dist/lib/data/collection-content.js +5 -0
  123. package/dist/lib/data/collection-content.js.map +1 -1
  124. package/dist/lib/engine-version.d.ts +1 -1
  125. package/dist/lib/engine-version.js +1 -1
  126. package/dist/lib/engine-version.js.map +1 -1
  127. package/dist/lib/i18n/messages.de.js +20 -4
  128. package/dist/lib/i18n/messages.de.js.map +1 -1
  129. package/dist/lib/i18n/messages.en.d.ts +16 -1
  130. package/dist/lib/i18n/messages.en.js +22 -5
  131. package/dist/lib/i18n/messages.en.js.map +1 -1
  132. package/dist/lib/operations/cms-persistence.d.ts +80 -35
  133. package/dist/lib/operations/cms-persistence.js +348 -85
  134. package/dist/lib/operations/cms-persistence.js.map +1 -1
  135. package/dist/lib/operations/config-manifest-lookup.d.ts +1 -1
  136. package/dist/lib/operations/config-manifest-lookup.js +3 -3
  137. package/dist/lib/operations/config-manifest-lookup.js.map +1 -1
  138. package/dist/lib/persistence/convex-adapter.js +54 -8
  139. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  140. package/dist/lib/persistence/deployment-readiness.js +14 -11
  141. package/dist/lib/persistence/deployment-readiness.js.map +1 -1
  142. package/dist/lib/persona/labels.js +2 -0
  143. package/dist/lib/persona/labels.js.map +1 -1
  144. package/dist/lib/query/index.d.ts +2 -2
  145. package/dist/lib/query/index.js +1 -1
  146. package/dist/lib/query/index.js.map +1 -1
  147. package/dist/lib/query/operations-query.d.ts +26 -0
  148. package/dist/lib/query/operations-query.js +12 -0
  149. package/dist/lib/query/operations-query.js.map +1 -1
  150. package/dist/lib/registry/index.js +2 -2
  151. package/dist/lib/registry/index.js.map +1 -1
  152. package/dist/lib/registry/preview.d.ts +10 -0
  153. package/dist/lib/registry/preview.js +13 -4
  154. package/dist/lib/registry/preview.js.map +1 -1
  155. package/dist/lib/routes/route-issues.js +6 -3
  156. package/dist/lib/routes/route-issues.js.map +1 -1
  157. package/dist/lib/workspace/workspace-source.js +6 -7
  158. package/dist/lib/workspace/workspace-source.js.map +1 -1
  159. package/dist/previews/entry-node.d.ts +14 -0
  160. package/dist/previews/entry-node.js +22 -0
  161. package/dist/previews/entry-node.js.map +1 -0
  162. package/dist/previews/harness.js +2 -16
  163. package/dist/previews/harness.js.map +1 -1
  164. package/package.json +3 -3
@@ -2,5 +2,5 @@
2
2
  * The studio client read seam (EC-308 / AD-17): one operations-backed query cache.
3
3
  * See `./operations-query`.
4
4
  */
5
- export { StudioQueryProvider, useOperationQuery, useOperationCache, listStoreState, OPERATION_INVALIDATE_DEBOUNCE_MS, } from './operations-query.js';
6
- export type { OperationCache, UseOperationQueryOptions } from './operations-query.js';
5
+ export { StudioQueryProvider, useOperationQuery, useOperationCache, listStoreState, readStateOf, OPERATION_INVALIDATE_DEBOUNCE_MS, } from './operations-query.js';
6
+ export type { OperationCache, ReadState, UseOperationQueryOptions } from './operations-query.js';
@@ -2,5 +2,5 @@
2
2
  * The studio client read seam (EC-308 / AD-17): one operations-backed query cache.
3
3
  * See `./operations-query`.
4
4
  */
5
- export { StudioQueryProvider, useOperationQuery, useOperationCache, listStoreState, OPERATION_INVALIDATE_DEBOUNCE_MS, } from './operations-query.js';
5
+ export { StudioQueryProvider, useOperationQuery, useOperationCache, listStoreState, readStateOf, OPERATION_INVALIDATE_DEBOUNCE_MS, } from './operations-query.js';
6
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/query/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,gCAAgC,GACjC,MAAM,oBAAoB,CAAA","sourcesContent":["/**\n * The studio client read seam (EC-308 / AD-17): one operations-backed query cache.\n * See `./operations-query`.\n */\nexport {\n StudioQueryProvider,\n useOperationQuery,\n useOperationCache,\n listStoreState,\n OPERATION_INVALIDATE_DEBOUNCE_MS,\n} from './operations-query'\nexport type { OperationCache, UseOperationQueryOptions } from './operations-query'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/query/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,gCAAgC,GACjC,MAAM,oBAAoB,CAAA","sourcesContent":["/**\n * The studio client read seam (EC-308 / AD-17): one operations-backed query cache.\n * See `./operations-query`.\n */\nexport {\n StudioQueryProvider,\n useOperationQuery,\n useOperationCache,\n listStoreState,\n readStateOf,\n OPERATION_INVALIDATE_DEBOUNCE_MS,\n} from './operations-query'\nexport type { OperationCache, ReadState, UseOperationQueryOptions } from './operations-query'\n"]}
@@ -81,6 +81,32 @@ export interface OperationCache {
81
81
  invalidate(queryKey: QueryKey): Promise<void>;
82
82
  }
83
83
  export declare function useOperationCache(): OperationCache;
84
+ /**
85
+ * EC-628 — a server read as a surface renders it: still loading, the value, or a failure.
86
+ * A union so a failed read cannot be represented as an empty value: a surface reads
87
+ * `value` only on `ready`, and has to say something on `error` (the published pool used to
88
+ * turn a failed `documents.listPublished` into an empty map, and every live URL then read
89
+ * "not published" — silently).
90
+ */
91
+ export type ReadState<T> = {
92
+ readonly status: 'loading';
93
+ } | {
94
+ readonly status: 'ready';
95
+ readonly value: T;
96
+ } | {
97
+ readonly status: 'error';
98
+ readonly message: string;
99
+ };
100
+ /**
101
+ * The {@link ReadState} of an operation query. A failure wins over a value: TanStack keeps
102
+ * the last good `data` when a refetch fails, and serving it would present a stale answer
103
+ * as the current one. `message` renders the failure (localized by the caller's catalog).
104
+ */
105
+ export declare function readStateOf<T>(query: {
106
+ readonly isError: boolean;
107
+ readonly error: unknown;
108
+ readonly data: T | undefined;
109
+ }, message: (error: unknown) => string): ReadState<T>;
84
110
  /**
85
111
  * Derive a list store's public `{ status, errorMessage }` from its operation query —
86
112
  * the identical mapping the assets/users/redirects rail stores (EC-319) each
@@ -114,6 +114,18 @@ export function useOperationCache() {
114
114
  const invalidate = useCallback((queryKey) => queryClient.invalidateQueries({ queryKey }), [queryClient]);
115
115
  return useMemo(() => ({ patch, invalidate }), [patch, invalidate]);
116
116
  }
117
+ /**
118
+ * The {@link ReadState} of an operation query. A failure wins over a value: TanStack keeps
119
+ * the last good `data` when a refetch fails, and serving it would present a stale answer
120
+ * as the current one. `message` renders the failure (localized by the caller's catalog).
121
+ */
122
+ export function readStateOf(query, message) {
123
+ if (query.isError)
124
+ return { status: 'error', message: message(query.error) };
125
+ if (query.data === undefined)
126
+ return { status: 'loading' };
127
+ return { status: 'ready', value: query.data };
128
+ }
117
129
  /**
118
130
  * Derive a list store's public `{ status, errorMessage }` from its operation query —
119
131
  * the identical mapping the assets/users/redirects rail stores (EC-319) each
@@ -1 +1 @@
1
- {"version":3,"file":"operations-query.js","sourceRoot":"","sources":["../../../src/lib/query/operations-query.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n QueryClient,\n QueryClientProvider,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query'\nimport type { QueryKey, UseQueryResult } from '@tanstack/react-query'\nimport { getStudioOperations } from '../operations'\nimport type { OperationEvent, OperationsClient } from '../operations'\n\n/**\n * EC-308 / AD-17 — the ONE studio client read seam.\n *\n * Every studio **server read** goes through {@link useOperationQuery} over the\n * shared operations client, cached by TanStack Query; invalidation rides the\n * operations event bus into the same cache. This replaces the bespoke per-store\n * `useState` + epoch-guard + debounce + reload machinery that each store used to\n * hand-roll (the patchwork AD-17 calls out). See\n * [`docs/client-data-flow.md`](../../../../../docs/client-data-flow.md) and\n * [`docs/principles.md`](../../../../../docs/principles.md) §12.\n *\n * It is also the **Phase-2 prep** (EC-309): reactivity/multiplayer is added by\n * swapping pull → push *behind this hook* (an adapter `subscribe` capability\n * driving `queryClient.setQueryData`), never at the call sites — so call sites\n * never change when push lands.\n *\n * Deliberately NOT here: the optimistic editing session (the draft being typed,\n * live validation, the table working view-state in `cms-store`). That is client\n * state, not server state, and stays local (principle 12).\n */\n\n/**\n * Trailing-debounce window (ms) for event-driven invalidation: a burst of\n * commands — e.g. a run of `documents.patch` commits from one editor, or one\n * event per row in a bulk action — coalesces into a single refetch once the\n * writes settle. (The shared replacement for the per-store `*_RELOAD_DEBOUNCE_MS`.)\n */\nexport const OPERATION_INVALIDATE_DEBOUNCE_MS = 300\n\n/** Studio-tuned cache defaults: invalidation (not polling/focus) drives freshness. */\nfunction createStudioQueryClient(): QueryClient {\n return new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5_000,\n refetchOnWindowFocus: false,\n retry: false,\n },\n },\n })\n}\n\n/**\n * Mounts the single studio `QueryClient` (one per studio lifetime, surviving\n * project switches). Mounted package-owned in `StudioProvider`, above every\n * per-project provider, so every studio hook can reach the cache.\n */\nexport function StudioQueryProvider({ children }: { children: ReactNode }): ReactNode {\n const [client] = useState(createStudioQueryClient)\n return <QueryClientProvider client={client}>{children}</QueryClientProvider>\n}\n\nexport interface UseOperationQueryOptions<T> {\n /** Stable query key; the first element is the domain tag used for grouping. */\n queryKey: QueryKey\n /** Loads the data from the shared operations client. */\n loader: (operations: OperationsClient) => Promise<T>\n /**\n * Which succeeded commands invalidate this query — a list of operation names\n * (`'documents.publish'`) and/or domains (`'documents'`). A succeeded command\n * matches when its `operation` OR `domain` is in the list. Keep it a stable\n * (module-level) array. Omit for a query that only loads once / refetches\n * manually.\n */\n invalidateOn?: readonly string[]\n /** Skip the query (e.g. fixture/playground sessions with no backend). */\n enabled?: boolean\n}\n\n/**\n * One operations-backed, cached, self-invalidating query. Thin wrapper over\n * `useQuery` whose `queryFn` resolves the shared operations client and runs the\n * loader, plus one shared invalidation subscriber (the only place that touches\n * the operations event bus for reads).\n */\nexport function useOperationQuery<T>(options: UseOperationQueryOptions<T>): UseQueryResult<T> {\n const { queryKey, loader, invalidateOn, enabled = true } = options\n const queryClient = useQueryClient()\n\n const result = useQuery<T>({\n queryKey,\n queryFn: async () => {\n const { operations } = await getStudioOperations()\n return loader(operations)\n },\n enabled,\n })\n\n // Stable serializations so an inline `queryKey`/`invalidateOn` doesn't churn the\n // subscription every render (re-subscribe only when their CONTENTS change).\n const keyJson = JSON.stringify(queryKey)\n const invalidateJson = JSON.stringify(invalidateOn ?? [])\n\n useEffect(() => {\n const triggers = new Set<string>(JSON.parse(invalidateJson) as string[])\n if (triggers.size === 0) return\n const key = JSON.parse(keyJson) as QueryKey\n let timer: ReturnType<typeof setTimeout> | undefined\n let unsubscribe: (() => void) | undefined\n let disposed = false\n const matches = (event: OperationEvent): boolean =>\n event.kind === 'command' &&\n event.status === 'succeeded' &&\n (triggers.has(event.operation) || triggers.has(event.domain))\n void getStudioOperations().then((studio) => {\n if (disposed) return\n unsubscribe = studio.operations.events.subscribe((event) => {\n if (!matches(event)) return\n // Trailing-debounce: a burst (per-keystroke autosave, bulk actions)\n // collapses into one invalidation; the active query then refetches once.\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => {\n void queryClient.invalidateQueries({ queryKey: key })\n }, OPERATION_INVALIDATE_DEBOUNCE_MS)\n })\n })\n return () => {\n disposed = true\n if (timer) clearTimeout(timer)\n unsubscribe?.()\n }\n }, [keyJson, invalidateJson, queryClient])\n\n return result\n}\n\n/**\n * The write-side companion to {@link useOperationQuery}: an imperative handle on\n * the studio cache for a store that OWNS its writes. Lets a store apply an\n * EC-301-style optimistic patch (drop/replace one record) instead of re-listing\n * the whole collection on every edit — keeping `@tanstack/react-query` out of the\n * call sites so the seam stays the only place that knows the cache exists.\n */\nexport interface OperationCache {\n /**\n * Patch a cached query in place — no refetch. The EC-301 pattern: a focal-point\n * or alt edit patches the one changed record; it does NOT trigger a full\n * `*.list` round-trip. No-op if nothing is cached under `queryKey` yet.\n */\n patch<T>(queryKey: QueryKey, updater: (prev: T | undefined) => T): void\n /**\n * Invalidate a cached query, refetching it if it is currently active. Returns a\n * promise that settles when the refetch does, so a caller that must sequence a\n * read after a local write (e.g. flush-then-reload) can await it.\n */\n invalidate(queryKey: QueryKey): Promise<void>\n}\n\nexport function useOperationCache(): OperationCache {\n const queryClient = useQueryClient()\n const patch = useCallback(\n <T,>(queryKey: QueryKey, updater: (prev: T | undefined) => T): void => {\n queryClient.setQueryData<T>(queryKey, updater)\n },\n [queryClient],\n )\n const invalidate = useCallback(\n (queryKey: QueryKey): Promise<void> => queryClient.invalidateQueries({ queryKey }),\n [queryClient],\n )\n return useMemo(() => ({ patch, invalidate }), [patch, invalidate])\n}\n\n/**\n * Derive a list store's public `{ status, errorMessage }` from its operation query —\n * the identical mapping the assets/users/redirects rail stores (EC-319) each\n * hand-rolled. `errorMessage` is set only on an error; a store spreads it\n * conditionally onto its public shape.\n */\nexport function listStoreState(query: { isPending: boolean; isError: boolean; error: unknown }): {\n status: 'loading' | 'ready' | 'error'\n errorMessage: string | undefined\n} {\n return {\n status: query.isPending ? 'loading' : query.isError ? 'error' : 'ready',\n errorMessage: query.isError && query.error instanceof Error ? query.error.message : undefined,\n }\n}\n"]}
1
+ {"version":3,"file":"operations-query.js","sourceRoot":"","sources":["../../../src/lib/query/operations-query.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n QueryClient,\n QueryClientProvider,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query'\nimport type { QueryKey, UseQueryResult } from '@tanstack/react-query'\nimport { getStudioOperations } from '../operations'\nimport type { OperationEvent, OperationsClient } from '../operations'\n\n/**\n * EC-308 / AD-17 — the ONE studio client read seam.\n *\n * Every studio **server read** goes through {@link useOperationQuery} over the\n * shared operations client, cached by TanStack Query; invalidation rides the\n * operations event bus into the same cache. This replaces the bespoke per-store\n * `useState` + epoch-guard + debounce + reload machinery that each store used to\n * hand-roll (the patchwork AD-17 calls out). See\n * [`docs/client-data-flow.md`](../../../../../docs/client-data-flow.md) and\n * [`docs/principles.md`](../../../../../docs/principles.md) §12.\n *\n * It is also the **Phase-2 prep** (EC-309): reactivity/multiplayer is added by\n * swapping pull → push *behind this hook* (an adapter `subscribe` capability\n * driving `queryClient.setQueryData`), never at the call sites — so call sites\n * never change when push lands.\n *\n * Deliberately NOT here: the optimistic editing session (the draft being typed,\n * live validation, the table working view-state in `cms-store`). That is client\n * state, not server state, and stays local (principle 12).\n */\n\n/**\n * Trailing-debounce window (ms) for event-driven invalidation: a burst of\n * commands — e.g. a run of `documents.patch` commits from one editor, or one\n * event per row in a bulk action — coalesces into a single refetch once the\n * writes settle. (The shared replacement for the per-store `*_RELOAD_DEBOUNCE_MS`.)\n */\nexport const OPERATION_INVALIDATE_DEBOUNCE_MS = 300\n\n/** Studio-tuned cache defaults: invalidation (not polling/focus) drives freshness. */\nfunction createStudioQueryClient(): QueryClient {\n return new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5_000,\n refetchOnWindowFocus: false,\n retry: false,\n },\n },\n })\n}\n\n/**\n * Mounts the single studio `QueryClient` (one per studio lifetime, surviving\n * project switches). Mounted package-owned in `StudioProvider`, above every\n * per-project provider, so every studio hook can reach the cache.\n */\nexport function StudioQueryProvider({ children }: { children: ReactNode }): ReactNode {\n const [client] = useState(createStudioQueryClient)\n return <QueryClientProvider client={client}>{children}</QueryClientProvider>\n}\n\nexport interface UseOperationQueryOptions<T> {\n /** Stable query key; the first element is the domain tag used for grouping. */\n queryKey: QueryKey\n /** Loads the data from the shared operations client. */\n loader: (operations: OperationsClient) => Promise<T>\n /**\n * Which succeeded commands invalidate this query — a list of operation names\n * (`'documents.publish'`) and/or domains (`'documents'`). A succeeded command\n * matches when its `operation` OR `domain` is in the list. Keep it a stable\n * (module-level) array. Omit for a query that only loads once / refetches\n * manually.\n */\n invalidateOn?: readonly string[]\n /** Skip the query (e.g. fixture/playground sessions with no backend). */\n enabled?: boolean\n}\n\n/**\n * One operations-backed, cached, self-invalidating query. Thin wrapper over\n * `useQuery` whose `queryFn` resolves the shared operations client and runs the\n * loader, plus one shared invalidation subscriber (the only place that touches\n * the operations event bus for reads).\n */\nexport function useOperationQuery<T>(options: UseOperationQueryOptions<T>): UseQueryResult<T> {\n const { queryKey, loader, invalidateOn, enabled = true } = options\n const queryClient = useQueryClient()\n\n const result = useQuery<T>({\n queryKey,\n queryFn: async () => {\n const { operations } = await getStudioOperations()\n return loader(operations)\n },\n enabled,\n })\n\n // Stable serializations so an inline `queryKey`/`invalidateOn` doesn't churn the\n // subscription every render (re-subscribe only when their CONTENTS change).\n const keyJson = JSON.stringify(queryKey)\n const invalidateJson = JSON.stringify(invalidateOn ?? [])\n\n useEffect(() => {\n const triggers = new Set<string>(JSON.parse(invalidateJson) as string[])\n if (triggers.size === 0) return\n const key = JSON.parse(keyJson) as QueryKey\n let timer: ReturnType<typeof setTimeout> | undefined\n let unsubscribe: (() => void) | undefined\n let disposed = false\n const matches = (event: OperationEvent): boolean =>\n event.kind === 'command' &&\n event.status === 'succeeded' &&\n (triggers.has(event.operation) || triggers.has(event.domain))\n void getStudioOperations().then((studio) => {\n if (disposed) return\n unsubscribe = studio.operations.events.subscribe((event) => {\n if (!matches(event)) return\n // Trailing-debounce: a burst (per-keystroke autosave, bulk actions)\n // collapses into one invalidation; the active query then refetches once.\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => {\n void queryClient.invalidateQueries({ queryKey: key })\n }, OPERATION_INVALIDATE_DEBOUNCE_MS)\n })\n })\n return () => {\n disposed = true\n if (timer) clearTimeout(timer)\n unsubscribe?.()\n }\n }, [keyJson, invalidateJson, queryClient])\n\n return result\n}\n\n/**\n * The write-side companion to {@link useOperationQuery}: an imperative handle on\n * the studio cache for a store that OWNS its writes. Lets a store apply an\n * EC-301-style optimistic patch (drop/replace one record) instead of re-listing\n * the whole collection on every edit — keeping `@tanstack/react-query` out of the\n * call sites so the seam stays the only place that knows the cache exists.\n */\nexport interface OperationCache {\n /**\n * Patch a cached query in place — no refetch. The EC-301 pattern: a focal-point\n * or alt edit patches the one changed record; it does NOT trigger a full\n * `*.list` round-trip. No-op if nothing is cached under `queryKey` yet.\n */\n patch<T>(queryKey: QueryKey, updater: (prev: T | undefined) => T): void\n /**\n * Invalidate a cached query, refetching it if it is currently active. Returns a\n * promise that settles when the refetch does, so a caller that must sequence a\n * read after a local write (e.g. flush-then-reload) can await it.\n */\n invalidate(queryKey: QueryKey): Promise<void>\n}\n\nexport function useOperationCache(): OperationCache {\n const queryClient = useQueryClient()\n const patch = useCallback(\n <T,>(queryKey: QueryKey, updater: (prev: T | undefined) => T): void => {\n queryClient.setQueryData<T>(queryKey, updater)\n },\n [queryClient],\n )\n const invalidate = useCallback(\n (queryKey: QueryKey): Promise<void> => queryClient.invalidateQueries({ queryKey }),\n [queryClient],\n )\n return useMemo(() => ({ patch, invalidate }), [patch, invalidate])\n}\n\n/**\n * EC-628 — a server read as a surface renders it: still loading, the value, or a failure.\n * A union so a failed read cannot be represented as an empty value: a surface reads\n * `value` only on `ready`, and has to say something on `error` (the published pool used to\n * turn a failed `documents.listPublished` into an empty map, and every live URL then read\n * \"not published\" — silently).\n */\nexport type ReadState<T> =\n | { readonly status: 'loading' }\n | { readonly status: 'ready'; readonly value: T }\n | { readonly status: 'error'; readonly message: string }\n\n/**\n * The {@link ReadState} of an operation query. A failure wins over a value: TanStack keeps\n * the last good `data` when a refetch fails, and serving it would present a stale answer\n * as the current one. `message` renders the failure (localized by the caller's catalog).\n */\nexport function readStateOf<T>(\n query: { readonly isError: boolean; readonly error: unknown; readonly data: T | undefined },\n message: (error: unknown) => string,\n): ReadState<T> {\n if (query.isError) return { status: 'error', message: message(query.error) }\n if (query.data === undefined) return { status: 'loading' }\n return { status: 'ready', value: query.data }\n}\n\n/**\n * Derive a list store's public `{ status, errorMessage }` from its operation query —\n * the identical mapping the assets/users/redirects rail stores (EC-319) each\n * hand-rolled. `errorMessage` is set only on an error; a store spreads it\n * conditionally onto its public shape.\n */\nexport function listStoreState(query: { isPending: boolean; isError: boolean; error: unknown }): {\n status: 'loading' | 'ready' | 'error'\n errorMessage: string | undefined\n} {\n return {\n status: query.isPending ? 'loading' : query.isError ? 'error' : 'ready',\n errorMessage: query.isError && query.error instanceof Error ? query.error.message : undefined,\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { manifestPropFields } from '@elytracms/core/component-registry';
1
+ import { manifestValueProps } from '@elytracms/core/component-registry';
2
2
  import { getImplementation, registerComponents } from '@elytracms/core/runtime-renderer';
3
3
  import { resolveDeploymentComponents } from '../workspace/index.js';
4
4
  /**
@@ -53,7 +53,7 @@ export function studioManifestLookup() {
53
53
  const manifest = registry.getManifest(componentId);
54
54
  if (!manifest)
55
55
  return undefined;
56
- return Object.fromEntries(manifestPropFields(manifest).map((field) => [field.name, field]));
56
+ return manifestValueProps(manifest);
57
57
  };
58
58
  }
59
59
  /** A RenderContext for fixture previews (static props only; no asset/relation resolvers). */
@@ -1 +1 @@
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
+ {"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,kBAAkB,CAAC,QAAQ,CAAC,CAAA;IACrC,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 { manifestValueProps } 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 manifestValueProps(manifest)\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"]}
@@ -28,3 +28,13 @@ export declare function nodeFromProps(manifest: ComponentManifest, values: Recor
28
28
  * Deterministic ids throughout.
29
29
  */
30
30
  export declare function previewSpecs(manifest: ComponentManifest, registry: ComponentRegistry): PreviewSpec[];
31
+ /**
32
+ * The `Default` preview of a fixture-less component — the seeded node the blocks panel
33
+ * inserts (a layout container pre-filled through its role, EC-383) with the seed's
34
+ * placeholder ids re-minted deterministically (root `…-0`, then `…-0-1`, `…-0-2` …), so
35
+ * every node has a distinct, stable id. The builder store re-ids an insert itself; a
36
+ * preview renders the seed directly, and React keys slot children by node id — the
37
+ * seed's shared `tmp` placeholder was a duplicate key (EC-624). Both preview surfaces
38
+ * (the Components view and `elytra previews`) render this one node.
39
+ */
40
+ export declare function defaultPreviewNode(manifest: ComponentManifest, registry: ComponentRegistry): ComponentNode;
@@ -37,11 +37,20 @@ export function previewSpecs(manifest, registry) {
37
37
  node: nodeFromProps(manifest, fixture.props ?? {}, `preview-${manifest.id}-${i}`),
38
38
  }));
39
39
  }
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.
40
+ return [{ name: 'Default', node: defaultPreviewNode(manifest, registry) }];
41
+ }
42
+ /**
43
+ * The `Default` preview of a fixture-less component — the seeded node the blocks panel
44
+ * inserts (a layout container pre-filled through its role, EC-383) with the seed's
45
+ * placeholder ids re-minted deterministically (root `…-0`, then `…-0-1`, `…-0-2` …), so
46
+ * every node has a distinct, stable id. The builder store re-ids an insert itself; a
47
+ * preview renders the seed directly, and React keys slot children by node id — the
48
+ * seed's shared `tmp` placeholder was a duplicate key (EC-624). Both preview surfaces
49
+ * (the Components view and `elytra previews`) render this one node.
50
+ */
51
+ export function defaultPreviewNode(manifest, registry) {
42
52
  const base = `preview-${manifest.id}-0`;
43
53
  let n = 0;
44
- const node = cloneWithNewIds(seededNodeFromManifest(manifest, registry), () => (n++ === 0 ? base : `${base}-${n - 1}`));
45
- return [{ name: 'Default', node }];
54
+ return cloneWithNewIds(seededNodeFromManifest(manifest, registry), () => (n++ === 0 ? base : `${base}-${n - 1}`));
46
55
  }
47
56
  //# sourceMappingURL=preview.js.map
@@ -1 +1 @@
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
+ {"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,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAA;AAC5E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAA2B,EAAE,QAA2B;IACzF,MAAM,IAAI,GAAG,WAAW,QAAQ,CAAC,EAAE,IAAI,CAAA;IACvC,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,OAAO,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;AACnH,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 return [{ name: 'Default', node: defaultPreviewNode(manifest, registry) }]\n}\n\n/**\n * The `Default` preview of a fixture-less component — the seeded node the blocks panel\n * inserts (a layout container pre-filled through its role, EC-383) with the seed's\n * placeholder ids re-minted deterministically (root `…-0`, then `…-0-1`, `…-0-2` …), so\n * every node has a distinct, stable id. The builder store re-ids an insert itself; a\n * preview renders the seed directly, and React keys slot children by node id — the\n * seed's shared `tmp` placeholder was a duplicate key (EC-624). Both preview surfaces\n * (the Components view and `elytra previews`) render this one node.\n */\nexport function defaultPreviewNode(manifest: ComponentManifest, registry: ComponentRegistry): ComponentNode {\n const base = `preview-${manifest.id}-0`\n let n = 0\n return cloneWithNewIds(seededNodeFromManifest(manifest, registry), () => (n++ === 0 ? base : `${base}-${n - 1}`))\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { createRouter, liveRedirectRecords } from '@elytracms/core/cms-core';
1
+ import { createRouter, readLiveRedirects } from '@elytracms/core/cms-core';
2
2
  /**
3
3
  * The studio's URL-map validation (EC-469) — the pure core, READING the one router's
4
4
  * issue set (EC-590; EC-565 Round 3, 3h).
@@ -13,7 +13,7 @@ import { createRouter, liveRedirectRecords } from '@elytracms/core/cms-core';
13
13
  *
14
14
  * The inputs are the two the studio holds: the resolved project's routes + collections
15
15
  * (config, AD-11) and the editorial redirects (data, EC-303). Only ENABLED redirects
16
- * count (`liveRedirectRecords`, the one projection — a paused redirect is not live and
16
+ * count (`readLiveRedirects`, the one live rule — a paused redirect is not live and
17
17
  * is never reported as a loop).
18
18
  *
19
19
  * `unknown-route-target` is deliberately NOT here: the resolver raises it per resolved URL
@@ -57,6 +57,9 @@ function collapseLoops(issues) {
57
57
  * empty result means "no structural problem", not "not checked".
58
58
  */
59
59
  export function routeValidationIssues(project, redirects = []) {
60
- return collapseLoops(createRouter(project, { redirects: liveRedirectRecords(redirects) }).issues);
60
+ // EC-628: the one live rule delivery reads — a stored redirect it cannot read is withheld
61
+ // there, so it is named here, at the rail an editor fixes it in.
62
+ const live = readLiveRedirects(redirects);
63
+ return [...live.issues, ...collapseLoops(createRouter(project, { redirects: live.redirects }).issues)];
61
64
  }
62
65
  //# sourceMappingURL=route-issues.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"route-issues.js","sourceRoot":"","sources":["../../../src/lib/routes/route-issues.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAI5E;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;;;;GAUG;AACH,SAAS,aAAa,CAAC,MAAqC;IAC1D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAyB,EAAE,CAAA;IACpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACnC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACf,SAAQ;QACV,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,CAAA;QAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAC9B,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;YAClD,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACxB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAQ;QAC3B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACb,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACjB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAsB,EACtB,YAAsC,EAAE;IAExC,OAAO,aAAa,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAA;AACnG,CAAC","sourcesContent":["import { createRouter, liveRedirectRecords } from '@elytracms/core/cms-core'\nimport type { CmsValidationIssue, RouterProject } from '@elytracms/core/cms-core'\nimport type { RedirectEntry } from '@elytracms/core/persistence'\n\n/**\n * The studio's URL-map validation (EC-469) — the pure core, READING the one router's\n * issue set (EC-590; EC-565 Round 3, 3h).\n *\n * `createRouter` (cms-core `router.ts`) owns the project's ONE route issue set:\n * `route-conflict` (an exact tie of the one precedence), `redirect-loop` and\n * `invalid-redirect` (the one normalised redirect set — a duplicate `from` after\n * trailing-slash normalisation), hierarchy-mount conflicts, and reachability. The same\n * set is read by `elytra doctor` and surfaced by the delivery host at build/dev — before\n * EC-590 this file was one of three that filtered mounts out and built a router of its\n * own, and the collection-dependent reachability half ran only in the doctor.\n *\n * The inputs are the two the studio holds: the resolved project's routes + collections\n * (config, AD-11) and the editorial redirects (data, EC-303). Only ENABLED redirects\n * count (`liveRedirectRecords`, the one projection — a paused redirect is not live and\n * is never reported as a loop).\n *\n * `unknown-route-target` is deliberately NOT here: the resolver raises it per resolved URL\n * against the documents that exist (`content/client.ts`), which a project-level check\n * does not have.\n */\n\n/**\n * Collapse the several reports of ONE redirect cycle into one finding.\n *\n * `createRouter` walks every redirect and reports a loop per distinct loop *entry\n * point* (`routes.ts`, EC-020) — correct for a machine, but a 2-hop cycle then reads\n * as two separate bugs at the rail foot, and fixing one makes both vanish. Every\n * rotation of a cycle visits the same set of paths, so the sorted member set is a\n * stable identity for the cycle: dedupe on it, keep the first, and two genuinely\n * different cycles still report separately. Presentation only — nothing is hidden, and\n * `createRouter`'s shared behaviour is untouched.\n */\nfunction collapseLoops(issues: readonly CmsValidationIssue[]): CmsValidationIssue[] {\n const seen = new Set<string>()\n const out: CmsValidationIssue[] = []\n for (const issue of issues) {\n if (issue.code !== 'redirect-loop') {\n out.push(issue)\n continue\n }\n const chain = issue.meta?.chain\n const key = Array.isArray(chain)\n ? [...new Set(chain.map(String))].sort().join('|')\n : issue.path.join('/')\n if (seen.has(key)) continue\n seen.add(key)\n out.push(issue)\n }\n return out\n}\n\n/**\n * The project's structural URL-map issues — the one router's issue set over the resolved\n * project's routes and collections and the enabled editorial redirects, with a redirect\n * cycle's rotations collapsed into one finding. Pure and total: it never throws, and an\n * empty result means \"no structural problem\", not \"not checked\".\n */\nexport function routeValidationIssues(\n project: RouterProject,\n redirects: readonly RedirectEntry[] = [],\n): readonly CmsValidationIssue[] {\n return collapseLoops(createRouter(project, { redirects: liveRedirectRecords(redirects) }).issues)\n}\n"]}
1
+ {"version":3,"file":"route-issues.js","sourceRoot":"","sources":["../../../src/lib/routes/route-issues.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAI1E;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;;;;GAUG;AACH,SAAS,aAAa,CAAC,MAAqC;IAC1D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAyB,EAAE,CAAA;IACpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACnC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACf,SAAQ;QACV,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,CAAA;QAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAC9B,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;YAClD,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACxB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAQ;QAC3B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACb,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACjB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAsB,EACtB,YAAsC,EAAE;IAExC,0FAA0F;IAC1F,iEAAiE;IACjE,MAAM,IAAI,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAA;IACzC,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,aAAa,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;AACxG,CAAC","sourcesContent":["import { createRouter, readLiveRedirects } from '@elytracms/core/cms-core'\nimport type { CmsValidationIssue, RouterProject } from '@elytracms/core/cms-core'\nimport type { RedirectEntry } from '@elytracms/core/persistence'\n\n/**\n * The studio's URL-map validation (EC-469) — the pure core, READING the one router's\n * issue set (EC-590; EC-565 Round 3, 3h).\n *\n * `createRouter` (cms-core `router.ts`) owns the project's ONE route issue set:\n * `route-conflict` (an exact tie of the one precedence), `redirect-loop` and\n * `invalid-redirect` (the one normalised redirect set — a duplicate `from` after\n * trailing-slash normalisation), hierarchy-mount conflicts, and reachability. The same\n * set is read by `elytra doctor` and surfaced by the delivery host at build/dev — before\n * EC-590 this file was one of three that filtered mounts out and built a router of its\n * own, and the collection-dependent reachability half ran only in the doctor.\n *\n * The inputs are the two the studio holds: the resolved project's routes + collections\n * (config, AD-11) and the editorial redirects (data, EC-303). Only ENABLED redirects\n * count (`readLiveRedirects`, the one live rule — a paused redirect is not live and\n * is never reported as a loop).\n *\n * `unknown-route-target` is deliberately NOT here: the resolver raises it per resolved URL\n * against the documents that exist (`content/client.ts`), which a project-level check\n * does not have.\n */\n\n/**\n * Collapse the several reports of ONE redirect cycle into one finding.\n *\n * `createRouter` walks every redirect and reports a loop per distinct loop *entry\n * point* (`routes.ts`, EC-020) — correct for a machine, but a 2-hop cycle then reads\n * as two separate bugs at the rail foot, and fixing one makes both vanish. Every\n * rotation of a cycle visits the same set of paths, so the sorted member set is a\n * stable identity for the cycle: dedupe on it, keep the first, and two genuinely\n * different cycles still report separately. Presentation only — nothing is hidden, and\n * `createRouter`'s shared behaviour is untouched.\n */\nfunction collapseLoops(issues: readonly CmsValidationIssue[]): CmsValidationIssue[] {\n const seen = new Set<string>()\n const out: CmsValidationIssue[] = []\n for (const issue of issues) {\n if (issue.code !== 'redirect-loop') {\n out.push(issue)\n continue\n }\n const chain = issue.meta?.chain\n const key = Array.isArray(chain)\n ? [...new Set(chain.map(String))].sort().join('|')\n : issue.path.join('/')\n if (seen.has(key)) continue\n seen.add(key)\n out.push(issue)\n }\n return out\n}\n\n/**\n * The project's structural URL-map issues — the one router's issue set over the resolved\n * project's routes and collections and the enabled editorial redirects, with a redirect\n * cycle's rotations collapsed into one finding. Pure and total: it never throws, and an\n * empty result means \"no structural problem\", not \"not checked\".\n */\nexport function routeValidationIssues(\n project: RouterProject,\n redirects: readonly RedirectEntry[] = [],\n): readonly CmsValidationIssue[] {\n // EC-628: the one live rule delivery reads — a stored redirect it cannot read is withheld\n // there, so it is named here, at the rail an editor fixes it in.\n const live = readLiveRedirects(redirects)\n return [...live.issues, ...collapseLoops(createRouter(project, { redirects: live.redirects }).issues)]\n}\n"]}
@@ -13,13 +13,12 @@ import { extractWorkspaceConfigExport, resolveWorkspaceConfig } from '@elytracms
13
13
  * exactly the latent plumbing EC-376 deleted from this file (see the NOTE above).
14
14
  *
15
15
  * The consequence is a KNOWN, deliberate divergence from `elytra doctor`, which does
16
- * carry the count (`cli/src/doctor.js` `configFactsFromOutcome` sets
17
- * `issueCount: issues.length` unconditionally, and `deployment-ready.ts`'s
18
- * `config-valid` check has a `warn` branch for a valid-config-with-issues that the
19
- * studio can therefore never reach). It is **latent, not live**: with zero producers
20
- * the count on a valid config is always 0, and `deployment-ready.ts` tests it for
21
- * truthiness — so both tools render the same `ok` today. (Read at both sites and
22
- * inferred from them; not exercised end-to-end through the studio gate.)
16
+ * carry the issues on a valid config too (`cli/src/doctor.js` `configFactsFromOutcome`
17
+ * sets `issues` whenever there are any, and `deployment-ready.ts`'s `config-valid` check
18
+ * has a `warn` branch for a valid-config-with-issues that the studio can therefore never
19
+ * reach). It is **latent, not live**: with zero producers a valid config carries no
20
+ * issues, so both tools render the same `ok` today. (Read at both sites and inferred
21
+ * from them; not exercised end-to-end through the studio gate.)
23
22
  *
24
23
  * **Resurface trigger.** The first warning-severity `WorkspaceConfigIssue` makes the
25
24
  * divergence live and this decision expires. At that point EC-506's audience question
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-source.js","sourceRoot":"","sources":["../../../src/lib/workspace/workspace-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAsClG;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAS,SAAS,CAAC,KAAc,EAAE,MAAc;IAC/C,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;IAC9D,OAAO,MAAM,CAAC,EAAE;QACd,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE;QACvD,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAA;AACxD,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,qBAAqB,CAAC,OAAyB;IAC7D,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,IAAI,qBAAqB,CAAA;QACtD,MAAM,SAAS,GAAG,4BAA4B,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;QACxE,IAAI,OAAO,IAAI,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAA;QAC7F,OAAO,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACzC,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACzB,CAAC","sourcesContent":["import { WORKSPACE_CONFIG_FILE } from '@elytracms/core/config'\nimport { extractWorkspaceConfigExport, resolveWorkspaceConfig } from '@elytracms/core/studio-core'\nimport type { ResolvedProject, WorkspaceConfigIssue } from '@elytracms/core/studio-core'\n\n/**\n * Workspace-config resolution for the studio (EC-154, vision AD-8).\n *\n * The studio receives its code-first workspace config as the default export of\n * the consumer's `elytra.config.ts`, handed in at the mount point\n * (`createStudio(config)` in `./index.ts`) and resolved here as pure, testable\n * logic. A bundled `elytra.config.ts` at the builder app root — picked up by\n * Vite (`import.meta.glob`) — is the same channel for studio development and for\n * a self-hosted builder whose repo *is* the workspace.\n *\n * No channel → `none`: the single-project golden path, identical to today's\n * adapter-listed behavior. An invalid config is a deterministic boot-error state\n * (see `DeploymentSetupGate`) — never a silent fallback to unconfigured behavior.\n *\n * NOTE (EC-376): an earlier env-injected-JSON channel (`VITE_ELYTRA_WORKSPACE`)\n * for a hosted builder serving a *separate* host repo was plumbed here but never\n * wired end-to-end — no caller ever set it (EC-357 inventory). It was removed as\n * dead code. If hosted workspace-selection ever returns, wire it deliberately\n * through the EC-357 mount-env seam (`createStudio(config, studioConfig, { env })`\n * → `provideStudioEnv`/`studioEnv()` in `lib/persistence/backend-mode.ts`) rather\n * than resurrecting latent plumbing.\n */\n\nexport type WorkspaceState =\n | { kind: 'none' }\n | { kind: 'config'; source: string; projects: ResolvedProject[] }\n | { kind: 'invalid'; source: string; issues: WorkspaceConfigIssue[] }\n\nexport interface WorkspaceSources {\n /** Module namespace of the bundled `elytra.config.ts`, when one was bundled. */\n fileModule?: unknown\n /** Source label for file-channel issues (defaults to the conventional name). */\n fileName?: string\n}\n\n/**\n * `result.issues` is carried on the `invalid` path and **deliberately dropped on the\n * ok path** (EC-506, decided 2026-08-01) — not overlooked.\n *\n * `ok` means \"no error-severity issue\", so what would be dropped is warnings — and\n * the resolver has **no warning-severity producer** (measured 2026-08-01: every\n * `severity:` literal in `studio-core/workspace-config.ts` is `'error'`, and its one\n * pass-through of a foreign severity cannot carry `validateProjectSettings`'s only\n * warning; both gates are pinned by `workspace-config.test.ts`). Adding an `issues`\n * field here today would be a channel with a writer, no producer and no reader —\n * exactly the latent plumbing EC-376 deleted from this file (see the NOTE above).\n *\n * The consequence is a KNOWN, deliberate divergence from `elytra doctor`, which does\n * carry the count (`cli/src/doctor.js` `configFactsFromOutcome` sets\n * `issueCount: issues.length` unconditionally, and `deployment-ready.ts`'s\n * `config-valid` check has a `warn` branch for a valid-config-with-issues that the\n * studio can therefore never reach). It is **latent, not live**: with zero producers\n * the count on a valid config is always 0, and `deployment-ready.ts` tests it for\n * truthiness — so both tools render the same `ok` today. (Read at both sites and\n * inferred from them; not exercised end-to-end through the studio gate.)\n *\n * **Resurface trigger.** The first warning-severity `WorkspaceConfigIssue` makes the\n * divergence live and this decision expires. At that point EC-506's audience question\n * (\"does an EDITOR need to know?\") returns as a real design call, and the shape is in\n * the EC-506 task file: `issues` here, a mapper in `config-issues.ts`, a rail-foot\n * reader, and a localized catalog key. Do not add the field ahead of the producer.\n */\nfunction fromValue(value: unknown, source: string): WorkspaceState {\n const result = resolveWorkspaceConfig(value, { file: source })\n return result.ok\n ? { kind: 'config', source, projects: result.projects }\n : { kind: 'invalid', source, issues: result.issues }\n}\n\n/** Resolve the workspace state from the bundled config module. Pure and deterministic. */\nexport function resolveWorkspaceState(sources: WorkspaceSources): WorkspaceState {\n if (sources.fileModule !== undefined) {\n const file = sources.fileName ?? WORKSPACE_CONFIG_FILE\n const extracted = extractWorkspaceConfigExport(sources.fileModule, file)\n if ('issue' in extracted) return { kind: 'invalid', source: file, issues: [extracted.issue] }\n return fromValue(extracted.value, file)\n }\n\n return { kind: 'none' }\n}\n"]}
1
+ {"version":3,"file":"workspace-source.js","sourceRoot":"","sources":["../../../src/lib/workspace/workspace-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAsClG;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,SAAS,SAAS,CAAC,KAAc,EAAE,MAAc;IAC/C,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;IAC9D,OAAO,MAAM,CAAC,EAAE;QACd,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE;QACvD,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAA;AACxD,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,qBAAqB,CAAC,OAAyB;IAC7D,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,IAAI,qBAAqB,CAAA;QACtD,MAAM,SAAS,GAAG,4BAA4B,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;QACxE,IAAI,OAAO,IAAI,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAA;QAC7F,OAAO,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IACzC,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACzB,CAAC","sourcesContent":["import { WORKSPACE_CONFIG_FILE } from '@elytracms/core/config'\nimport { extractWorkspaceConfigExport, resolveWorkspaceConfig } from '@elytracms/core/studio-core'\nimport type { ResolvedProject, WorkspaceConfigIssue } from '@elytracms/core/studio-core'\n\n/**\n * Workspace-config resolution for the studio (EC-154, vision AD-8).\n *\n * The studio receives its code-first workspace config as the default export of\n * the consumer's `elytra.config.ts`, handed in at the mount point\n * (`createStudio(config)` in `./index.ts`) and resolved here as pure, testable\n * logic. A bundled `elytra.config.ts` at the builder app root — picked up by\n * Vite (`import.meta.glob`) — is the same channel for studio development and for\n * a self-hosted builder whose repo *is* the workspace.\n *\n * No channel → `none`: the single-project golden path, identical to today's\n * adapter-listed behavior. An invalid config is a deterministic boot-error state\n * (see `DeploymentSetupGate`) — never a silent fallback to unconfigured behavior.\n *\n * NOTE (EC-376): an earlier env-injected-JSON channel (`VITE_ELYTRA_WORKSPACE`)\n * for a hosted builder serving a *separate* host repo was plumbed here but never\n * wired end-to-end — no caller ever set it (EC-357 inventory). It was removed as\n * dead code. If hosted workspace-selection ever returns, wire it deliberately\n * through the EC-357 mount-env seam (`createStudio(config, studioConfig, { env })`\n * → `provideStudioEnv`/`studioEnv()` in `lib/persistence/backend-mode.ts`) rather\n * than resurrecting latent plumbing.\n */\n\nexport type WorkspaceState =\n | { kind: 'none' }\n | { kind: 'config'; source: string; projects: ResolvedProject[] }\n | { kind: 'invalid'; source: string; issues: WorkspaceConfigIssue[] }\n\nexport interface WorkspaceSources {\n /** Module namespace of the bundled `elytra.config.ts`, when one was bundled. */\n fileModule?: unknown\n /** Source label for file-channel issues (defaults to the conventional name). */\n fileName?: string\n}\n\n/**\n * `result.issues` is carried on the `invalid` path and **deliberately dropped on the\n * ok path** (EC-506, decided 2026-08-01) — not overlooked.\n *\n * `ok` means \"no error-severity issue\", so what would be dropped is warnings — and\n * the resolver has **no warning-severity producer** (measured 2026-08-01: every\n * `severity:` literal in `studio-core/workspace-config.ts` is `'error'`, and its one\n * pass-through of a foreign severity cannot carry `validateProjectSettings`'s only\n * warning; both gates are pinned by `workspace-config.test.ts`). Adding an `issues`\n * field here today would be a channel with a writer, no producer and no reader —\n * exactly the latent plumbing EC-376 deleted from this file (see the NOTE above).\n *\n * The consequence is a KNOWN, deliberate divergence from `elytra doctor`, which does\n * carry the issues on a valid config too (`cli/src/doctor.js` `configFactsFromOutcome`\n * sets `issues` whenever there are any, and `deployment-ready.ts`'s `config-valid` check\n * has a `warn` branch for a valid-config-with-issues that the studio can therefore never\n * reach). It is **latent, not live**: with zero producers a valid config carries no\n * issues, so both tools render the same `ok` today. (Read at both sites and inferred\n * from them; not exercised end-to-end through the studio gate.)\n *\n * **Resurface trigger.** The first warning-severity `WorkspaceConfigIssue` makes the\n * divergence live and this decision expires. At that point EC-506's audience question\n * (\"does an EDITOR need to know?\") returns as a real design call, and the shape is in\n * the EC-506 task file: `issues` here, a mapper in `config-issues.ts`, a rail-foot\n * reader, and a localized catalog key. Do not add the field ahead of the producer.\n */\nfunction fromValue(value: unknown, source: string): WorkspaceState {\n const result = resolveWorkspaceConfig(value, { file: source })\n return result.ok\n ? { kind: 'config', source, projects: result.projects }\n : { kind: 'invalid', source, issues: result.issues }\n}\n\n/** Resolve the workspace state from the bundled config module. Pure and deterministic. */\nexport function resolveWorkspaceState(sources: WorkspaceSources): WorkspaceState {\n if (sources.fileModule !== undefined) {\n const file = sources.fileName ?? WORKSPACE_CONFIG_FILE\n const extracted = extractWorkspaceConfigExport(sources.fileModule, file)\n if ('issue' in extracted) return { kind: 'invalid', source: file, issues: [extracted.issue] }\n return fromValue(extracted.value, file)\n }\n\n return { kind: 'none' }\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { ComponentNode } from '@elytracms/core/project-graph';
2
+ import type { StudioRegistry } from '../lib/registry/index.js';
3
+ /**
4
+ * The node one `elytra previews` entry renders — the exact subtree the blocks panel
5
+ * inserts (`fixtureSubtree` for a declared fixture, else the seeded manifest default,
6
+ * EC-439) — the fixture-less `Default` exactly as the Components view previews it
7
+ * ({@link defaultPreviewNode}: distinct ids, EC-624). Pure (no DOM), so the harness's
8
+ * node is testable node-side.
9
+ */
10
+ export declare function previewEntryNode(studio: StudioRegistry, componentId: string, fixture: string): {
11
+ node: ComponentNode;
12
+ } | {
13
+ error: string;
14
+ };
@@ -0,0 +1,22 @@
1
+ import { DEFAULT_VARIANT, fixtureSubtree } from '../components/builder/blocks-model.js';
2
+ import { defaultPreviewNode } from '../lib/registry/preview.js';
3
+ /**
4
+ * The node one `elytra previews` entry renders — the exact subtree the blocks panel
5
+ * inserts (`fixtureSubtree` for a declared fixture, else the seeded manifest default,
6
+ * EC-439) — the fixture-less `Default` exactly as the Components view previews it
7
+ * ({@link defaultPreviewNode}: distinct ids, EC-624). Pure (no DOM), so the harness's
8
+ * node is testable node-side.
9
+ */
10
+ export function previewEntryNode(studio, componentId, fixture) {
11
+ const manifest = studio.registry.getManifest(componentId);
12
+ if (!manifest)
13
+ return { error: `Unknown component "${componentId}".` };
14
+ const declared = manifest.fixtures?.find((f) => f.name === fixture);
15
+ if (declared)
16
+ return { node: fixtureSubtree(studio, manifest, declared, `preview-${componentId}`).node };
17
+ if (fixture === DEFAULT_VARIANT && (manifest.fixtures ?? []).length === 0) {
18
+ return { node: defaultPreviewNode(manifest, studio.registry) };
19
+ }
20
+ return { error: `Unknown fixture "${fixture}" for component "${componentId}".` };
21
+ }
22
+ //# sourceMappingURL=entry-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-node.js","sourceRoot":"","sources":["../../src/previews/entry-node.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;AAEpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAE5D;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAsB,EACtB,WAAmB,EACnB,OAAe;IAEf,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;IACzD,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,sBAAsB,WAAW,IAAI,EAAE,CAAA;IACtE,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAA;IACnE,IAAI,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACxG,IAAI,OAAO,KAAK,eAAe,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1E,OAAO,EAAE,IAAI,EAAE,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAA;IAChE,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,oBAAoB,OAAO,oBAAoB,WAAW,IAAI,EAAE,CAAA;AAClF,CAAC","sourcesContent":["import type { ComponentNode } from '@elytracms/core/project-graph'\nimport { DEFAULT_VARIANT, fixtureSubtree } from '../components/builder/blocks-model'\nimport type { StudioRegistry } from '../lib/registry'\nimport { defaultPreviewNode } from '../lib/registry/preview'\n\n/**\n * The node one `elytra previews` entry renders — the exact subtree the blocks panel\n * inserts (`fixtureSubtree` for a declared fixture, else the seeded manifest default,\n * EC-439) — the fixture-less `Default` exactly as the Components view previews it\n * ({@link defaultPreviewNode}: distinct ids, EC-624). Pure (no DOM), so the harness's\n * node is testable node-side.\n */\nexport function previewEntryNode(\n studio: StudioRegistry,\n componentId: string,\n fixture: string,\n): { node: ComponentNode } | { error: string } {\n const manifest = studio.registry.getManifest(componentId)\n if (!manifest) return { error: `Unknown component \"${componentId}\".` }\n const declared = manifest.fixtures?.find((f) => f.name === fixture)\n if (declared) return { node: fixtureSubtree(studio, manifest, declared, `preview-${componentId}`).node }\n if (fixture === DEFAULT_VARIANT && (manifest.fixtures ?? []).length === 0) {\n return { node: defaultPreviewNode(manifest, studio.registry) }\n }\n return { error: `Unknown fixture \"${fixture}\" for component \"${componentId}\".` }\n}\n"]}
@@ -3,10 +3,9 @@ import { Component } from 'react';
3
3
  import { createRoot } from 'react-dom/client';
4
4
  import { flushSync } from 'react-dom';
5
5
  import { renderNode } from '@elytracms/core/runtime-renderer';
6
- import { DEFAULT_VARIANT, fixtureSubtree } from '../components/builder/blocks-model.js';
7
- import { seededNodeFromManifest } from '../components/builder/node-factory.js';
8
6
  import { previewContext } from '../lib/registry/index.js';
9
7
  import { previewEntriesForConfig, previewRegistryFromConfig } from './enumerate.js';
8
+ import { previewEntryNode } from './entry-node.js';
10
9
  /**
11
10
  * EC-440 — the browser half of the `elytra previews` render harness. The CLI
12
11
  * boots the consumer studio app's OWN Vite (programmatically, with a minimal
@@ -62,19 +61,6 @@ async function settled(doc) {
62
61
  })));
63
62
  await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
64
63
  }
65
- /** Build the node a fixture renders — the exact subtree the blocks panel inserts. */
66
- function nodeForEntry(studio, componentId, fixture) {
67
- const manifest = studio.registry.getManifest(componentId);
68
- if (!manifest)
69
- return { error: `Unknown component "${componentId}".` };
70
- const declared = manifest.fixtures?.find((f) => f.name === fixture);
71
- if (declared)
72
- return { node: fixtureSubtree(studio, manifest, declared, `preview-${componentId}`).node };
73
- if (fixture === DEFAULT_VARIANT && (manifest.fixtures ?? []).length === 0) {
74
- return { node: seededNodeFromManifest(manifest, studio.registry) };
75
- }
76
- return { error: `Unknown fixture "${fixture}" for component "${componentId}".` };
77
- }
78
64
  /**
79
65
  * Mount the harness: build the registry from the consumer config, expose the
80
66
  * driver API on `window.__ELYTRA_PREVIEWS__`. Idempotent per document.
@@ -94,7 +80,7 @@ export function mountPreviewHarness(config) {
94
80
  }
95
81
  let root = null;
96
82
  const render = async (componentId, fixture) => {
97
- const built = nodeForEntry(studio, componentId, fixture);
83
+ const built = previewEntryNode(studio, componentId, fixture);
98
84
  if ('error' in built)
99
85
  return { ok: false, error: built.error };
100
86
  // A fresh root per fixture: no state carryover between shots.
@@ -1 +1 @@
1
- {"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/previews/harness.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjC,OAAO,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAG7D,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;AACpF,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAA;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAEhF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAA;AAgBtD,0FAA0F;AAC1F,MAAM,oBAAqB,SAAQ,SAGlC;IACU,KAAK,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAElC,MAAM,CAAC,wBAAwB;QAC7B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IACzB,CAAC;IAEQ,iBAAiB,CAAC,KAAY;QACrC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAC3B,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAA;IACvD,CAAC;CACF;AAED,gFAAgF;AAChF,KAAK,UAAU,OAAO,CAAC,GAAa;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,CAAA;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;IAC7E,CAAC;IACD,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACjC,GAAG,CAAC,QAAQ;QACV,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE;QACnB,CAAC,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAC5B,GAAG,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YAC7D,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QAChE,CAAC,CAAC,CACP,CACF,CAAA;IACD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAClC,qBAAqB,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CACpE,CAAA;AACH,CAAC;AAED,qFAAqF;AACrF,SAAS,YAAY,CACnB,MAAsB,EACtB,WAAmB,EACnB,OAAe;IAEf,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;IACzD,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,sBAAsB,WAAW,IAAI,EAAE,CAAA;IACtE,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAA;IACnE,IAAI,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACxG,IAAI,OAAO,KAAK,eAAe,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1E,OAAO,EAAE,IAAI,EAAE,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAA;IACpE,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,oBAAoB,OAAO,oBAAoB,WAAW,IAAI,EAAE,CAAA;AAClF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAe;IACjD,MAAM,MAAM,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAAA;IAChD,MAAM,GAAG,GAAkB,cAAc,CAAC,MAAM,CAAC,CAAA;IACjD,MAAM,OAAO,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QACjF,WAAW;QACX,OAAO;KACR,CAAC,CAAC,CAAA;IAEH,IAAI,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAA;IACrD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;QACrC,KAAK,CAAC,EAAE,GAAG,gBAAgB,CAAA;QAC3B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,IAAI,GAAgB,IAAI,CAAA;IAE5B,MAAM,MAAM,GAAG,KAAK,EAAE,WAAmB,EAAE,OAAe,EAAgC,EAAE;QAC1F,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;QACxD,IAAI,OAAO,IAAI,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAA;QAE9D,8DAA8D;QAC9D,IAAI,EAAE,OAAO,EAAE,CAAA;QACf,IAAI,GAAG,UAAU,CAAC,KAAM,CAAC,CAAA;QACzB,IAAI,WAAW,GAAiB,IAAI,CAAA;QACpC,IAAI,CAAC;YACH,SAAS,CAAC,GAAG,EAAE;gBACb,IAAK,CAAC,MAAM,CACV,KAAC,oBAAoB,IACnB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;wBACjB,WAAW,GAAG,KAAK,CAAA;oBACrB,CAAC,YAEA,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,GACP,CACxB,CAAA;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,WAAW,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QACzE,CAAC;QACD,IAAI,WAAW,KAAK,IAAI;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAG,WAAqB,CAAC,OAAO,EAAE,CAAA;QACrF,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAA;QACvB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;IACrB,CAAC,CAAA;IAED,MAAM,GAAG,GAAsB,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;IAC/D,MAAM,CAAC,mBAAmB,GAAG,GAAG,CAAA;IAChC,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["import { Component } from 'react'\nimport type { ReactNode } from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\nimport { flushSync } from 'react-dom'\nimport { renderNode } from '@elytracms/core/runtime-renderer'\nimport type { RenderContext } from '@elytracms/core/runtime-renderer'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { DEFAULT_VARIANT, fixtureSubtree } from '../components/builder/blocks-model'\nimport { seededNodeFromManifest } from '../components/builder/node-factory'\nimport { previewContext } from '../lib/registry'\nimport type { StudioRegistry } from '../lib/registry'\nimport { previewEntriesForConfig, previewRegistryFromConfig } from './enumerate'\n\n/**\n * EC-440 — the browser half of the `elytra previews` render harness. The CLI\n * boots the consumer studio app's OWN Vite (programmatically, with a minimal\n * inline config) and serves a page whose entry module does exactly three\n * things: import the consumer's `elytra.config.ts` (the component registry —\n * code), import the consumer THEME (the same standalone Tailwind entry the\n * canvas iframe and delivery load, EC-414 — so preview == canvas == delivery\n * by construction), and call {@link mountPreviewHarness}.\n *\n * DATA-FREE, deliberately (no new trust boundary): no persistence adapter, no\n * auth, no content data, no env secrets — `createStudio` is never called. The\n * registry is built directly from the config's `components` entries plus the\n * base primitives ({@link previewRegistryFromConfig}), and bindings resolve to\n * `undefined` (`previewContext`), so data-dependent components render their\n * honest empty states.\n *\n * The driving CLI talks to the page through `window.__ELYTRA_PREVIEWS__`:\n * `entries` (the same enumeration the CLI derives node-side — a handshake\n * guard) and `render(componentId, fixture)`, which mounts ONE fixture subtree\n * — the exact subtree the blocks panel inserts (`fixtureSubtree`, else the\n * seeded manifest default, EC-439) — into the stage and resolves once the\n * document has settled (fonts loaded, images complete, two animation frames).\n * The CLI then screenshots the viewport.\n */\n\n/** The id of the stage element fixtures render into (created when absent). */\nexport const PREVIEW_STAGE_ID = 'elytra-preview-stage'\n\nexport type PreviewRenderResult = { ok: true } | { ok: false; error: string }\n\nexport interface PreviewHarnessApi {\n ready: true\n entries: Array<{ componentId: string; fixture: string }>\n render: (componentId: string, fixture: string) => Promise<PreviewRenderResult>\n}\n\ndeclare global {\n interface Window {\n __ELYTRA_PREVIEWS__?: PreviewHarnessApi\n }\n}\n\n/** Catches a fixture whose render throws — the entry fails visibly, the page survives. */\nclass HarnessErrorBoundary extends Component<\n { children: ReactNode; onError: (error: Error) => void },\n { failed: boolean }\n> {\n override state = { failed: false }\n\n static getDerivedStateFromError(): { failed: boolean } {\n return { failed: true }\n }\n\n override componentDidCatch(error: Error): void {\n this.props.onError(error)\n }\n\n override render(): ReactNode {\n return this.state.failed ? null : this.props.children\n }\n}\n\n/** Resolve once the document is visually settled: fonts, images, two frames. */\nasync function settled(doc: Document): Promise<void> {\n try {\n await doc.fonts?.ready\n } catch {\n // Font-loading failures must not wedge the run — the render is still shot.\n }\n await Promise.all(\n Array.from(doc.images).map((img) =>\n img.complete\n ? Promise.resolve()\n : new Promise<void>((resolve) => {\n img.addEventListener('load', () => resolve(), { once: true })\n img.addEventListener('error', () => resolve(), { once: true })\n }),\n ),\n )\n await new Promise<void>((resolve) =>\n requestAnimationFrame(() => requestAnimationFrame(() => resolve())),\n )\n}\n\n/** Build the node a fixture renders — the exact subtree the blocks panel inserts. */\nfunction nodeForEntry(\n studio: StudioRegistry,\n componentId: string,\n fixture: string,\n): { node: ComponentNode } | { error: string } {\n const manifest = studio.registry.getManifest(componentId)\n if (!manifest) return { error: `Unknown component \"${componentId}\".` }\n const declared = manifest.fixtures?.find((f) => f.name === fixture)\n if (declared) return { node: fixtureSubtree(studio, manifest, declared, `preview-${componentId}`).node }\n if (fixture === DEFAULT_VARIANT && (manifest.fixtures ?? []).length === 0) {\n return { node: seededNodeFromManifest(manifest, studio.registry) }\n }\n return { error: `Unknown fixture \"${fixture}\" for component \"${componentId}\".` }\n}\n\n/**\n * Mount the harness: build the registry from the consumer config, expose the\n * driver API on `window.__ELYTRA_PREVIEWS__`. Idempotent per document.\n */\nexport function mountPreviewHarness(config: unknown): PreviewHarnessApi {\n const studio = previewRegistryFromConfig(config)\n const ctx: RenderContext = previewContext(studio)\n const entries = previewEntriesForConfig(config).map(({ componentId, fixture }) => ({\n componentId,\n fixture,\n }))\n\n let stage = document.getElementById(PREVIEW_STAGE_ID)\n if (!stage) {\n stage = document.createElement('div')\n stage.id = PREVIEW_STAGE_ID\n document.body.appendChild(stage)\n }\n\n let root: Root | null = null\n\n const render = async (componentId: string, fixture: string): Promise<PreviewRenderResult> => {\n const built = nodeForEntry(studio, componentId, fixture)\n if ('error' in built) return { ok: false, error: built.error }\n\n // A fresh root per fixture: no state carryover between shots.\n root?.unmount()\n root = createRoot(stage!)\n let renderError: Error | null = null\n try {\n flushSync(() => {\n root!.render(\n <HarnessErrorBoundary\n onError={(error) => {\n renderError = error\n }}\n >\n {renderNode(built.node, ctx)}\n </HarnessErrorBoundary>,\n )\n })\n } catch (error) {\n renderError = error instanceof Error ? error : new Error(String(error))\n }\n if (renderError !== null) return { ok: false, error: (renderError as Error).message }\n await settled(document)\n return { ok: true }\n }\n\n const api: PreviewHarnessApi = { ready: true, entries, render }\n window.__ELYTRA_PREVIEWS__ = api\n return api\n}\n"]}
1
+ {"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/previews/harness.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjC,OAAO,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAA;AAgBtD,0FAA0F;AAC1F,MAAM,oBAAqB,SAAQ,SAGlC;IACU,KAAK,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAElC,MAAM,CAAC,wBAAwB;QAC7B,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IACzB,CAAC;IAEQ,iBAAiB,CAAC,KAAY;QACrC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAC3B,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAA;IACvD,CAAC;CACF;AAED,gFAAgF;AAChF,KAAK,UAAU,OAAO,CAAC,GAAa;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,CAAA;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;IAC7E,CAAC;IACD,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACjC,GAAG,CAAC,QAAQ;QACV,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE;QACnB,CAAC,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAC5B,GAAG,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YAC7D,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QAChE,CAAC,CAAC,CACP,CACF,CAAA;IACD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAClC,qBAAqB,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CACpE,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAe;IACjD,MAAM,MAAM,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAAA;IAChD,MAAM,GAAG,GAAkB,cAAc,CAAC,MAAM,CAAC,CAAA;IACjD,MAAM,OAAO,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QACjF,WAAW;QACX,OAAO;KACR,CAAC,CAAC,CAAA;IAEH,IAAI,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAA;IACrD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;QACrC,KAAK,CAAC,EAAE,GAAG,gBAAgB,CAAA;QAC3B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,IAAI,GAAgB,IAAI,CAAA;IAE5B,MAAM,MAAM,GAAG,KAAK,EAAE,WAAmB,EAAE,OAAe,EAAgC,EAAE;QAC1F,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;QAC5D,IAAI,OAAO,IAAI,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAA;QAE9D,8DAA8D;QAC9D,IAAI,EAAE,OAAO,EAAE,CAAA;QACf,IAAI,GAAG,UAAU,CAAC,KAAM,CAAC,CAAA;QACzB,IAAI,WAAW,GAAiB,IAAI,CAAA;QACpC,IAAI,CAAC;YACH,SAAS,CAAC,GAAG,EAAE;gBACb,IAAK,CAAC,MAAM,CACV,KAAC,oBAAoB,IACnB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;wBACjB,WAAW,GAAG,KAAK,CAAA;oBACrB,CAAC,YAEA,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,GACP,CACxB,CAAA;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,WAAW,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QACzE,CAAC;QACD,IAAI,WAAW,KAAK,IAAI;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAG,WAAqB,CAAC,OAAO,EAAE,CAAA;QACrF,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAA;QACvB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;IACrB,CAAC,CAAA;IAED,MAAM,GAAG,GAAsB,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;IAC/D,MAAM,CAAC,mBAAmB,GAAG,GAAG,CAAA;IAChC,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["import { Component } from 'react'\nimport type { ReactNode } from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\nimport { flushSync } from 'react-dom'\nimport { renderNode } from '@elytracms/core/runtime-renderer'\nimport type { RenderContext } from '@elytracms/core/runtime-renderer'\nimport { previewContext } from '../lib/registry'\nimport { previewEntriesForConfig, previewRegistryFromConfig } from './enumerate'\nimport { previewEntryNode } from './entry-node'\n\n/**\n * EC-440 — the browser half of the `elytra previews` render harness. The CLI\n * boots the consumer studio app's OWN Vite (programmatically, with a minimal\n * inline config) and serves a page whose entry module does exactly three\n * things: import the consumer's `elytra.config.ts` (the component registry —\n * code), import the consumer THEME (the same standalone Tailwind entry the\n * canvas iframe and delivery load, EC-414 — so preview == canvas == delivery\n * by construction), and call {@link mountPreviewHarness}.\n *\n * DATA-FREE, deliberately (no new trust boundary): no persistence adapter, no\n * auth, no content data, no env secrets — `createStudio` is never called. The\n * registry is built directly from the config's `components` entries plus the\n * base primitives ({@link previewRegistryFromConfig}), and bindings resolve to\n * `undefined` (`previewContext`), so data-dependent components render their\n * honest empty states.\n *\n * The driving CLI talks to the page through `window.__ELYTRA_PREVIEWS__`:\n * `entries` (the same enumeration the CLI derives node-side — a handshake\n * guard) and `render(componentId, fixture)`, which mounts ONE fixture subtree\n * — the exact subtree the blocks panel inserts (`fixtureSubtree`, else the\n * seeded manifest default, EC-439) — into the stage and resolves once the\n * document has settled (fonts loaded, images complete, two animation frames).\n * The CLI then screenshots the viewport.\n */\n\n/** The id of the stage element fixtures render into (created when absent). */\nexport const PREVIEW_STAGE_ID = 'elytra-preview-stage'\n\nexport type PreviewRenderResult = { ok: true } | { ok: false; error: string }\n\nexport interface PreviewHarnessApi {\n ready: true\n entries: Array<{ componentId: string; fixture: string }>\n render: (componentId: string, fixture: string) => Promise<PreviewRenderResult>\n}\n\ndeclare global {\n interface Window {\n __ELYTRA_PREVIEWS__?: PreviewHarnessApi\n }\n}\n\n/** Catches a fixture whose render throws — the entry fails visibly, the page survives. */\nclass HarnessErrorBoundary extends Component<\n { children: ReactNode; onError: (error: Error) => void },\n { failed: boolean }\n> {\n override state = { failed: false }\n\n static getDerivedStateFromError(): { failed: boolean } {\n return { failed: true }\n }\n\n override componentDidCatch(error: Error): void {\n this.props.onError(error)\n }\n\n override render(): ReactNode {\n return this.state.failed ? null : this.props.children\n }\n}\n\n/** Resolve once the document is visually settled: fonts, images, two frames. */\nasync function settled(doc: Document): Promise<void> {\n try {\n await doc.fonts?.ready\n } catch {\n // Font-loading failures must not wedge the run — the render is still shot.\n }\n await Promise.all(\n Array.from(doc.images).map((img) =>\n img.complete\n ? Promise.resolve()\n : new Promise<void>((resolve) => {\n img.addEventListener('load', () => resolve(), { once: true })\n img.addEventListener('error', () => resolve(), { once: true })\n }),\n ),\n )\n await new Promise<void>((resolve) =>\n requestAnimationFrame(() => requestAnimationFrame(() => resolve())),\n )\n}\n\n/**\n * Mount the harness: build the registry from the consumer config, expose the\n * driver API on `window.__ELYTRA_PREVIEWS__`. Idempotent per document.\n */\nexport function mountPreviewHarness(config: unknown): PreviewHarnessApi {\n const studio = previewRegistryFromConfig(config)\n const ctx: RenderContext = previewContext(studio)\n const entries = previewEntriesForConfig(config).map(({ componentId, fixture }) => ({\n componentId,\n fixture,\n }))\n\n let stage = document.getElementById(PREVIEW_STAGE_ID)\n if (!stage) {\n stage = document.createElement('div')\n stage.id = PREVIEW_STAGE_ID\n document.body.appendChild(stage)\n }\n\n let root: Root | null = null\n\n const render = async (componentId: string, fixture: string): Promise<PreviewRenderResult> => {\n const built = previewEntryNode(studio, componentId, fixture)\n if ('error' in built) return { ok: false, error: built.error }\n\n // A fresh root per fixture: no state carryover between shots.\n root?.unmount()\n root = createRoot(stage!)\n let renderError: Error | null = null\n try {\n flushSync(() => {\n root!.render(\n <HarnessErrorBoundary\n onError={(error) => {\n renderError = error\n }}\n >\n {renderNode(built.node, ctx)}\n </HarnessErrorBoundary>,\n )\n })\n } catch (error) {\n renderError = error instanceof Error ? error : new Error(String(error))\n }\n if (renderError !== null) return { ok: false, error: (renderError as Error).message }\n await settled(document)\n return { ok: true }\n }\n\n const api: PreviewHarnessApi = { ready: true, entries, render }\n window.__ELYTRA_PREVIEWS__ = api\n return api\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elytracms/studio",
3
3
  "author": "finaldream <hi@finaldream.de>",
4
- "version": "0.0.94",
4
+ "version": "0.0.96",
5
5
  "engines": {
6
6
  "node": ">=22.18.0"
7
7
  },
@@ -153,7 +153,7 @@
153
153
  "lucide-react": "^1.23.0",
154
154
  "tailwind-merge": "^3.6.0",
155
155
  "zod": "^4.4.3",
156
- "@elytracms/core": "^0.0.94"
156
+ "@elytracms/core": "^0.0.96"
157
157
  },
158
158
  "peerDependencies": {
159
159
  "@tanstack/react-router": "^1.170.0",
@@ -177,7 +177,7 @@
177
177
  "react-dom": "^19.2.7",
178
178
  "typescript": "^6.0.3",
179
179
  "vite": "^8.1.3",
180
- "@elytracms/components": "^0.0.94"
180
+ "@elytracms/components": "^0.0.96"
181
181
  },
182
182
  "scripts": {
183
183
  "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && node ../../scripts/fix-esm-extensions.mjs dist && node ../../scripts/check-esm-extensions.mjs dist",