@elytracms/studio 0.0.90 → 0.0.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/convex/asset-revalidate.test.ts +14 -1
  2. package/convex/cms.ts +3 -4
  3. package/convex/document-revalidate.test.ts +14 -1
  4. package/convex/redirect-revalidate.test.ts +14 -1
  5. package/dist/components/assets/asset-selection.d.ts +8 -9
  6. package/dist/components/assets/asset-selection.js +26 -16
  7. package/dist/components/assets/asset-selection.js.map +1 -1
  8. package/dist/components/builder/blocks-model.d.ts +7 -5
  9. package/dist/components/builder/blocks-model.js +11 -9
  10. package/dist/components/builder/blocks-model.js.map +1 -1
  11. package/dist/components/builder/blocks-panel.js +1 -1
  12. package/dist/components/builder/blocks-panel.js.map +1 -1
  13. package/dist/components/builder/builder-store.d.ts +11 -3
  14. package/dist/components/builder/builder-store.js +12 -5
  15. package/dist/components/builder/builder-store.js.map +1 -1
  16. package/dist/components/builder/canvas-dnd.d.ts +7 -1
  17. package/dist/components/builder/canvas-dnd.js +6 -3
  18. package/dist/components/builder/canvas-dnd.js.map +1 -1
  19. package/dist/components/builder/canvas-render-context.d.ts +13 -1
  20. package/dist/components/builder/canvas-render-context.js +28 -18
  21. package/dist/components/builder/canvas-render-context.js.map +1 -1
  22. package/dist/components/builder/canvas.js +1 -1
  23. package/dist/components/builder/canvas.js.map +1 -1
  24. package/dist/components/builder/clipboard-actions.d.ts +0 -6
  25. package/dist/components/builder/clipboard-actions.js +9 -4
  26. package/dist/components/builder/clipboard-actions.js.map +1 -1
  27. package/dist/components/builder/composition-field-editor.d.ts +22 -0
  28. package/dist/components/builder/composition-field-editor.js +47 -8
  29. package/dist/components/builder/composition-field-editor.js.map +1 -1
  30. package/dist/components/builder/inline-edit.d.ts +7 -4
  31. package/dist/components/builder/inline-edit.js +11 -8
  32. package/dist/components/builder/inline-edit.js.map +1 -1
  33. package/dist/components/builder/insert-context.js +9 -5
  34. package/dist/components/builder/insert-context.js.map +1 -1
  35. package/dist/components/builder/layout-inspector.d.ts +2 -2
  36. package/dist/components/builder/layout-inspector.js +8 -6
  37. package/dist/components/builder/layout-inspector.js.map +1 -1
  38. package/dist/components/builder/node-factory.d.ts +31 -14
  39. package/dist/components/builder/node-factory.js +73 -93
  40. package/dist/components/builder/node-factory.js.map +1 -1
  41. package/dist/components/builder/page-tree.d.ts +3 -1
  42. package/dist/components/builder/page-tree.js +6 -4
  43. package/dist/components/builder/page-tree.js.map +1 -1
  44. package/dist/components/builder/property-inspector.js +2 -1
  45. package/dist/components/builder/property-inspector.js.map +1 -1
  46. package/dist/components/builder/reuse.d.ts +1 -1
  47. package/dist/components/builder/reuse.js +8 -5
  48. package/dist/components/builder/reuse.js.map +1 -1
  49. package/dist/components/builder/selection-overlay.d.ts +2 -2
  50. package/dist/components/builder/selection-overlay.js +3 -3
  51. package/dist/components/builder/selection-overlay.js.map +1 -1
  52. package/dist/components/builder/spacing-handles.d.ts +2 -2
  53. package/dist/components/builder/spacing-handles.js +3 -3
  54. package/dist/components/builder/spacing-handles.js.map +1 -1
  55. package/dist/components/cms/asset-field.d.ts +1 -1
  56. package/dist/components/cms/asset-field.js +15 -13
  57. package/dist/components/cms/asset-field.js.map +1 -1
  58. package/dist/components/cms/asset-picker-core.js +3 -4
  59. package/dist/components/cms/asset-picker-core.js.map +1 -1
  60. package/dist/components/cms/detail-editor.js +10 -5
  61. package/dist/components/cms/detail-editor.js.map +1 -1
  62. package/dist/components/cms/discard-draft.d.ts +1 -2
  63. package/dist/components/cms/discard-draft.js +1 -1
  64. package/dist/components/cms/discard-draft.js.map +1 -1
  65. package/dist/components/cms/editable-cell.js +11 -7
  66. package/dist/components/cms/editable-cell.js.map +1 -1
  67. package/dist/components/cms/field-control-lib.d.ts +10 -0
  68. package/dist/components/cms/field-control-lib.js +20 -0
  69. package/dist/components/cms/field-control-lib.js.map +1 -1
  70. package/dist/components/cms/field-control.js +3 -3
  71. package/dist/components/cms/field-control.js.map +1 -1
  72. package/dist/components/cms/object-array-field.js +3 -5
  73. package/dist/components/cms/object-array-field.js.map +1 -1
  74. package/dist/components/cms/prop-fields.d.ts +16 -3
  75. package/dist/components/cms/prop-fields.js +57 -12
  76. package/dist/components/cms/prop-fields.js.map +1 -1
  77. package/dist/components/cms/rich-text/embed-props.d.ts +6 -1
  78. package/dist/components/cms/rich-text/embed-props.js +7 -12
  79. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  80. package/dist/components/cms/rich-text/insert.d.ts +13 -6
  81. package/dist/components/cms/rich-text/insert.js +16 -17
  82. package/dist/components/cms/rich-text/insert.js.map +1 -1
  83. package/dist/components/cms/use-cell-edit.js +7 -12
  84. package/dist/components/cms/use-cell-edit.js.map +1 -1
  85. package/dist/components/cms/use-discard-controller.d.ts +1 -2
  86. package/dist/components/cms/use-discard-controller.js +1 -2
  87. package/dist/components/cms/use-discard-controller.js.map +1 -1
  88. package/dist/components/cms/versions-pane.js +2 -2
  89. package/dist/components/cms/versions-pane.js.map +1 -1
  90. package/dist/components/components/component-detail.d.ts +1 -1
  91. package/dist/components/components/component-detail.js +2 -4
  92. package/dist/components/components/component-detail.js.map +1 -1
  93. package/dist/components/components/component-preview.js +9 -4
  94. package/dist/components/components/component-preview.js.map +1 -1
  95. package/dist/components/components/components-index.d.ts +2 -2
  96. package/dist/components/components/components-index.js +3 -6
  97. package/dist/components/components/components-index.js.map +1 -1
  98. package/dist/components/components/field-inspector.js +5 -2
  99. package/dist/components/components/field-inspector.js.map +1 -1
  100. package/dist/components/components/registry-browser.d.ts +6 -5
  101. package/dist/components/components/registry-browser.js +11 -37
  102. package/dist/components/components/registry-browser.js.map +1 -1
  103. package/dist/components/components/taxonomy.d.ts +14 -21
  104. package/dist/components/components/taxonomy.js +5 -29
  105. package/dist/components/components/taxonomy.js.map +1 -1
  106. package/dist/components/components/transfer.d.ts +10 -8
  107. package/dist/components/components/transfer.js +17 -13
  108. package/dist/components/components/transfer.js.map +1 -1
  109. package/dist/lib/ai/constraints.d.ts +16 -14
  110. package/dist/lib/ai/constraints.js +64 -48
  111. package/dist/lib/ai/constraints.js.map +1 -1
  112. package/dist/lib/ai/update-fields.js +5 -3
  113. package/dist/lib/ai/update-fields.js.map +1 -1
  114. package/dist/lib/assets/asset-placements.d.ts +3 -10
  115. package/dist/lib/assets/asset-placements.js +17 -8
  116. package/dist/lib/assets/asset-placements.js.map +1 -1
  117. package/dist/lib/clipboard/paste-target.js +3 -3
  118. package/dist/lib/clipboard/paste-target.js.map +1 -1
  119. package/dist/lib/cms-views/view-model.js +11 -8
  120. package/dist/lib/cms-views/view-model.js.map +1 -1
  121. package/dist/lib/data/collection-content.d.ts +10 -7
  122. package/dist/lib/data/collection-content.js +34 -15
  123. package/dist/lib/data/collection-content.js.map +1 -1
  124. package/dist/lib/dnd/drop-rules.d.ts +22 -7
  125. package/dist/lib/dnd/drop-rules.js +21 -14
  126. package/dist/lib/dnd/drop-rules.js.map +1 -1
  127. package/dist/lib/engine-version.d.ts +1 -1
  128. package/dist/lib/engine-version.js +1 -1
  129. package/dist/lib/engine-version.js.map +1 -1
  130. package/dist/lib/i18n/messages.de.js +25 -9
  131. package/dist/lib/i18n/messages.de.js.map +1 -1
  132. package/dist/lib/i18n/messages.en.d.ts +21 -7
  133. package/dist/lib/i18n/messages.en.js +28 -9
  134. package/dist/lib/i18n/messages.en.js.map +1 -1
  135. package/dist/lib/operations/cms-persistence.js +7 -4
  136. package/dist/lib/operations/cms-persistence.js.map +1 -1
  137. package/dist/lib/persona/labels.d.ts +4 -9
  138. package/dist/lib/persona/labels.js +5 -12
  139. package/dist/lib/persona/labels.js.map +1 -1
  140. package/dist/lib/references/reference-model.d.ts +0 -2
  141. package/dist/lib/references/reference-model.js +3 -5
  142. package/dist/lib/references/reference-model.js.map +1 -1
  143. package/dist/lib/registry/__fixtures__/demo-components.d.ts +32 -0
  144. package/dist/lib/registry/{project-components.js → __fixtures__/demo-components.js} +18 -5
  145. package/dist/lib/registry/__fixtures__/demo-components.js.map +1 -0
  146. package/dist/lib/registry/index.d.ts +9 -13
  147. package/dist/lib/registry/index.js +34 -61
  148. package/dist/lib/registry/index.js.map +1 -1
  149. package/dist/lib/registry/preview.d.ts +22 -7
  150. package/dist/lib/registry/preview.js +26 -89
  151. package/dist/lib/registry/preview.js.map +1 -1
  152. package/dist/previews/enumerate.d.ts +23 -27
  153. package/dist/previews/enumerate.js +27 -48
  154. package/dist/previews/enumerate.js.map +1 -1
  155. package/dist/previews/harness.js +1 -1
  156. package/dist/previews/harness.js.map +1 -1
  157. package/package.json +4 -3
  158. package/dist/lib/registry/project-components.d.ts +0 -16
  159. package/dist/lib/registry/project-components.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"collection-content.js","sourceRoot":"","sources":["../../../src/lib/data/collection-content.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createContentClient,\n createContentContext,\n queryError,\n validateLimit,\n validateSort,\n validateWhere,\n} from '@elytracms/core/content'\nimport type {\n ContentClient,\n ContentDocumentSource,\n ContentLookup,\n ContentQueryError,\n GetDocumentResult,\n ListDocumentsQuery,\n ListDocumentsResult,\n ResolvedDocument,\n} from '@elytracms/core/content'\nimport {\n documentKey,\n localeSchema,\n parseLocaleConfig,\n resolveDocumentTitle,\n valuesInLocale,\n} from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentRef,\n Locale,\n LocaleConfig,\n OrphanedCollection,\n RouteRecord,\n} from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport type { ListingQuery } from '@elytracms/core/runtime-renderer'\nimport { getStudioLocale, getStudioLocaleConfig } from '../locale/locale-store'\n\n/**\n * The studio's content delivery engine for canvas previews (EC-166).\n *\n * Repeating sections and template pages preview *delivery-shaped* documents —\n * the exact shape `@elytracms/core/content` serves in production — so the canvas\n * shows what publishing will show. This module wires the canonical content\n * client (closed AD-3 query vocabulary, published perspective, EC-018 locale\n * fallback, depth-1 population, resolved assets) over a **content session**:\n *\n * - Convex mode: the project-session gate (`components/studio/project-session`)\n * configures the session with the project's HYDRATED schema + documents, so\n * \"Preview with\" lists and section previews show the live content — never\n * fixture titles (EC-177 gap 1).\n * - Playground mode (EC-361): the SAME hydration loader runs over the seeded\n * in-memory adapter (through operations — AD-17), so canvas listing/relation\n * population resolves from the in-memory session documents with identical\n * semantics. With no session configured (tests, boot instants, no persisted\n * content) the engine serves nothing.\n *\n * Nothing here invents query semantics; both modes go through the same\n * canonical client.\n */\n\n/** The schema + documents the preview engine serves (one project session). */\nexport interface ContentSessionState {\n collections: readonly CollectionDef[]\n documents: readonly CmsDocument[]\n /**\n * Published document keys via the version POINTER (EC-224). When present, the\n * preview's published perspective resolves by the pin — never the flat\n * `state` enum — so it matches what delivery serves. Absent (playground\n * fixtures / tests) → the rows' own `state` gates the perspective, unchanged.\n */\n publishedKeys?: readonly string[]\n /**\n * Locale config of the session content — the project's own, composed by\n * `contentSessionOf` from `elytra.config.ts`. Absent (a kit session, tests) →\n * the ambient studio config, then the single locale being asked for; EC-479\n * retired the engine's `sampleLocaleConfig` fallback here.\n */\n localeConfig?: LocaleConfig\n /**\n * The project's config-sourced routes (EC-207). Wired into the content client so\n * the canvas preview's `resolveLink` (EC-291) derives a rich-text link's URL from\n * the SAME routes delivery serves — the studio-live half of the host link bake.\n * Absent (playground/tests) → no routes, so `resolveLink` returns null and a\n * reference link degrades to the visible `unresolved-ref` issue.\n */\n routes?: readonly RouteRecord[]\n /**\n * The project's asset records (assets table). Wired into the lookup so the\n * content client resolves `asset`-typed FIELDS on listing/relation results\n * (e.g. a ProductGrid product's `gallery`) to delivery shape — the same assets\n * the renderer's `resolveAsset` reads for static props (EC-262). Absent\n * (playground/tests) → assets come only from `asset`-collection fixture docs.\n */\n assets?: readonly AssetRecord[]\n /**\n * EC-606 — the stored rows the session does NOT serve: per undeclared collection,\n * how many rows and how many published (`HydratedCms.orphans`, carried here by\n * `contentSessionOf`'s spread). The preview engine never reads it. It rides this\n * session because this is the one hydrated-content channel that reaches the\n * section rail in both modes: the rail renders outside `ProjectSessionProvider`,\n * so it cannot read the project session context, and a second documents query\n * would be a parallel data flow (AD-17). Read via {@link contentSessionOrphans}.\n * Absent (a kit session, tests) → none.\n */\n orphans?: readonly OrphanedCollection[]\n}\n\n/**\n * Default (no-session) collection schema: EMPTY. The studio ships no demo content\n * (Oli, 2026-06-19) — with no backend/content session configured, there is nothing\n * to preview, and the studio says so rather than serving fixtures. A real project's\n * collections + documents arrive via {@link configureContentSession} in BOTH modes\n * (EC-361): Convex hydration, or the playground's seeded in-memory adapter through\n * the same loader.\n */\nexport const playgroundCollections: readonly CollectionDef[] = []\n\n/**\n * The perspective a canvas preview resolves references against (EC-300/AD-16). The\n * canvas is draft-coherent: the editable Draft view resolves references to their\n * `'draft'` targets (so a just-made reusable shows immediately), the read-only\n * Published view resolves `'published'` (an honest preview of what delivery serves;\n * an unpublished hard ref shows the in-canvas placeholder). Delivery stays\n * published-only — this does not weaken `live-is-published-only`.\n */\nexport type CanvasPerspective = 'draft' | 'published'\n\nlet contentSession: ContentSessionState | null = null\nlet lookup: ContentLookup = createLookup(playgroundCollections, [], [])\n// Keyed by locale + perspective (EC-300) + the resolved locale CONFIG (EC-479) —\n// one deterministic client per distinct resolution context. See `clientFor`.\nconst clients = new Map<string, ContentClient>()\n\n// EC-460: a monotonically increasing session epoch + listener set, so a REACT\n// surface (the canvas render context) can re-render when the session is\n// re-pointed at fresher content. The session itself stays a module global (the\n// EC-361 design); this only makes \"it changed\" observable — `configureContentSession`\n// remains the single setter.\nlet sessionEpoch = 0\nconst sessionListeners = new Set<() => void>()\n\n/** The current content-session epoch — bumps on every `configureContentSession`. */\nexport function contentSessionEpoch(): number {\n return sessionEpoch\n}\n\n/**\n * Subscribe to content-session swaps (EC-460). The listener fires after every\n * `configureContentSession`; returns the unsubscribe. Shaped for\n * `useSyncExternalStore(subscribeContentSession, contentSessionEpoch)`.\n */\nexport function subscribeContentSession(listener: () => void): () => void {\n sessionListeners.add(listener)\n return () => sessionListeners.delete(listener)\n}\n\n/**\n * Point the preview engine at a project's hydrated content — Convex hydration\n * or the playground's in-memory persisted state (EC-361), both through the same\n * `loadHydratedCms` loader — or back at the empty no-session default (`null`).\n */\nexport function configureContentSession(next: ContentSessionState | null): void {\n contentSession = next\n lookup = next\n ? createLookup(next.collections, next.documents, next.publishedKeys, next.assets, next.localeConfig)\n : createLookup(playgroundCollections, [], [])\n clients.clear()\n sessionEpoch += 1\n for (const listener of [...sessionListeners]) listener()\n}\n\n/** Whether a hydrated content session is configured (Convex mode). */\nexport function hasContentSession(): boolean {\n return contentSession !== null\n}\n\nconst NO_ORPHANS: readonly OrphanedCollection[] = []\n\n/**\n * The active session's orphaned collections (EC-606), or an empty list with no\n * session. Referentially stable between `configureContentSession` calls (the\n * session's own array, or one shared empty constant), so it is a valid\n * `useSyncExternalStore(subscribeContentSession, contentSessionOrphans, …)`\n * snapshot — the rail foot reads it exactly that way.\n */\nexport function contentSessionOrphans(): readonly OrphanedCollection[] {\n return contentSession?.orphans ?? NO_ORPHANS\n}\n\n/** The server snapshot for {@link contentSessionOrphans}: no session exists during SSR. */\nexport function serverContentSessionOrphans(): readonly OrphanedCollection[] {\n return NO_ORPHANS\n}\n\n/** The collections the preview engine currently serves (session or fixtures). */\nfunction activeCollections(): readonly CollectionDef[] {\n return contentSession?.collections ?? playgroundCollections\n}\n\n/**\n * The sentinel {@link previewLocale} yields when NOTHING has declared a locale —\n * no ambient studio config, no content session. It is not a locale code and\n * matches no stored key, which is the point: with no session the lookup is empty\n * (`playgroundCollections` is `[]`), so every query already resolves to nothing\n * and this only fills `CacheTags.locale`, which no studio surface reads. EC-479:\n * the engine used to answer this question with its `sampleLocaleConfig` fixture's\n * `en` — a locale nobody chose, standing in for the project's own.\n */\nconst NO_LOCALE = ''\n\n/**\n * The locale a canvas preview resolves in, when the caller names none: the\n * ambient studio locale (installed from the PROJECT's config by `CmsProvider`),\n * else the active content session's own declared default (surfaces that render\n * outside a CMS session — the Components view's previews — still resolve in the\n * project's locale), else {@link NO_LOCALE}. Never an engine fixture.\n */\nfunction previewLocale(): Locale {\n return getStudioLocale() ?? contentSession?.localeConfig?.default ?? NO_LOCALE\n}\n\n/**\n * The locale set a preview resolves fallbacks against (EC-018): the session's own\n * declaration, else the ambient studio config (the project's, via `elytra.config.ts`),\n * else the single locale actually being asked for.\n *\n * That last step is a DERIVATION, not an invention — it says \"the only locale we\n * know of is the one you named\", so the default-locale fallback is the requested\n * locale itself (a no-op) and a localized read falls through to `doc.values`.\n * Before EC-479 this line read `?? sampleLocaleConfig`, which silently declared\n * every locale-less studio bilingual `en/de` and resolved fallbacks through an `en`\n * no project had chosen. Only reached with no session AND no installed config, so\n * no stored key is ever written against it.\n */\nfunction activeLocaleConfig(locale: Locale): LocaleConfig {\n return contentSession?.localeConfig ?? getStudioLocaleConfig() ?? requestedLocaleOnly(locale)\n}\n\n/**\n * The single-locale derivation above, through the one constructor (EC-580:\n * `LocaleConfig` is parse-only). {@link NO_LOCALE} — or anything else that is not\n * a locale code — becomes `und`, BCP 47's own \"undetermined\", which likewise\n * matches no stored key.\n */\nfunction requestedLocaleOnly(locale: Locale): LocaleConfig {\n const code = localeSchema.safeParse(locale).success ? locale : 'und'\n return parseLocaleConfig({ default: code, locales: [code] })\n}\n\n/** Resolve a collection in the active content session (fixtures in playground). */\nexport function playgroundCollection(id: string): CollectionDef | undefined {\n return activeCollections().find((collection) => collection.id === id)\n}\n\n/**\n * Overlay the active session's canonical `filterable` declarations onto a\n * schema that doesn't carry any of its own. The inspector hands the Content\n * tab fixture collection defs; the filter/sort pickers must offer exactly the\n * EC-143 declaration set the delivery engine enforces, so both ends agree. A\n * collection that declares its own filterable fields is left untouched.\n */\nexport function withPlaygroundFilterable(\n collections: readonly CollectionDef[],\n): readonly CollectionDef[] {\n return collections.map((collection) => {\n if (collection.fields.some((field) => field.filterable === true)) return collection\n const counterpart = playgroundCollection(collection.id)\n if (counterpart === undefined) return collection\n const flagged = new Set(\n counterpart.fields.filter((field) => field.filterable === true).map((field) => field.name),\n )\n if (flagged.size === 0) return collection\n return {\n ...collection,\n fields: collection.fields.map((field) =>\n flagged.has(field.name) ? { ...field, filterable: true } : field,\n ),\n }\n })\n}\n\n/**\n * Synthesize the persistence-shaped asset record the asset resolver reads. EC-580:\n * the record's single `alt` is the DEFAULT locale's displayed value\n * (`valuesInLocale`, the project's config default) — it used to read\n * `localized.en.alt` whatever the project's locales were (MODE F5b, \"further\n * evidence\"), so a `de`-default project's previews had no alt and a shared `alt`\n * was never read. Without a locale model (no session config) only the shared bag\n * can be read.\n */\nfunction assetRecordOf(\n doc: CmsDocument,\n collection: CollectionDef | undefined,\n locales: LocaleConfig | undefined,\n): AssetRecord {\n const values = doc.values\n const alt =\n collection && locales\n ? valuesInLocale(collection, doc, locales.default, locales).alt\n : values.alt\n return {\n id: doc.id,\n projectId: 'playground',\n filename: typeof values.filename === 'string' ? values.filename : doc.id,\n contentType: 'image/png',\n byteSize: 0,\n alt: typeof alt === 'string' ? alt : undefined,\n storageKey: typeof values.url === 'string' ? values.url : doc.id,\n createdAt: '2026-01-01T00:00:00.000Z',\n updatedAt: '2026-01-01T00:00:00.000Z',\n }\n}\n\nfunction createLookup(\n collectionDefs: readonly CollectionDef[],\n documents: readonly CmsDocument[],\n publishedKeys?: readonly string[],\n assetRecords?: readonly AssetRecord[],\n locales?: LocaleConfig,\n): ContentLookup {\n const collections = new Map(collectionDefs.map((c) => [c.id, c]))\n // EC-224: published-ness is the version POINTER, not the flat `state` enum.\n // A hydrated session hands `publishedKeys` (the pinned set) → wrap each row as\n // a `DocumentHistory` so the published perspective resolves by the pin (the\n // live row is always a draft now) with ZERO dependence on `state`. Without a\n // pointer set (playground fixtures / tests) we keep bare rows, whose own\n // `state` gates the published perspective — byte-identical to before.\n const published = publishedKeys ? new Set(publishedKeys) : null\n const toSource = (doc: CmsDocument): ContentDocumentSource => {\n if (!published) return doc\n return published.has(documentKey({ collection: doc.collection, id: doc.id }))\n ? { draft: doc, published: doc, versions: [] }\n : { draft: doc, published: undefined, versions: [] }\n }\n const byKey = new Map(\n documents.map((doc) => [`${doc.collection}:${doc.id}`, toSource(doc)] as const),\n )\n // Asset resolution source for `asset`-typed FIELDS on resolved documents\n // (listing/relation results, e.g. a product's `gallery`). Two sources, real\n // records last so they win: (1) `asset`-collection fixture documents — how\n // playground/tests model assets; (2) the project's real asset-table records\n // (EC-262) — a Convex session has its assets here, NOT as `asset` documents,\n // so without these the content client resolves no nested image.\n const assets = new Map<string, AssetRecord>()\n for (const doc of documents) {\n if (doc.collection === 'asset') {\n assets.set(doc.id, assetRecordOf(doc, collections.get(doc.collection), locales))\n }\n }\n for (const record of assetRecords ?? []) assets.set(record.id, record)\n return {\n collection: (id) => collections.get(id),\n document: (ref) => byKey.get(`${ref.collection}:${ref.id}`),\n documents: (collectionId) =>\n documents.filter((doc) => doc.collection === collectionId).map(toSource),\n asset: (id) => assets.get(id),\n // Prefer the record's resolved servable `url` — the one the renderer's\n // resolveAsset reads (EC-262). Real asset-table records carry a Convex blob\n // locator in `storageKey` and the servable URL in `url`; fixture/`asset`-document\n // records (assetRecordOf) leave `url` unset and store the servable url in\n // `storageKey`. Falling back keeps both correct.\n assetUrl: (asset) => asset.url ?? asset.storageKey,\n }\n}\n\n/**\n * One deterministic client per (locale, perspective): EC-018 locale fallback,\n * draft-or-published perspective (EC-300/AD-16, default `'published'`). The studio's\n * ambient active locale (EC-176, `lib/locale`) picks the locale at query time, so the\n * canvas previews follow the locale switcher exactly like delivery would; the canvas's\n * Draft/Published view picks the perspective. The cache resets whenever the content\n * session changes.\n */\nfunction clientFor(locale: Locale, perspective: CanvasPerspective = 'published'): ContentClient {\n const locales = activeLocaleConfig(locale)\n // EC-479: the locale CONFIG is part of the client's identity now — it may come\n // from the ambient studio store, which changes independently of the session the\n // cache is cleared on, so keying by locale alone could serve a client built\n // against a superseded locale set.\n const key = `${locale}:${perspective}:${locales.default}|${locales.locales.join(',')}`\n const existing = clients.get(key)\n if (existing) return existing\n const client = createContentClient({\n lookup,\n context: createContentContext({\n locale,\n perspective,\n locales,\n }),\n // EC-291: the routes a rich-text link resolves against (config-sourced) — the\n // studio-live half of the host link bake.\n routes: contentSession?.routes ?? [],\n })\n clients.set(key, client)\n return client\n}\n\n/**\n * Run a closed-vocabulary list query (AD-3: eq/in/lt/lte/gt/gte, AND-only,\n * filterable fields only) and return the delivery-shaped result. Invalid\n * queries come back as structured errors — never thrown, never silently\n * widened. Resolves in the studio's active locale unless one is passed.\n */\nexport function runCollectionListQuery(\n collectionId: string,\n query: ListDocumentsQuery = {},\n locale: Locale = previewLocale(),\n): ListDocumentsResult {\n return clientFor(locale).listDocuments(collectionId, query)\n}\n\n/** Resolve one document (by id or slug) into delivery shape. */\nexport function getCollectionDocument(\n collectionId: string,\n idOrSlug: string,\n locale: Locale = previewLocale(),\n): GetDocumentResult {\n return clientFor(locale).getDocument(collectionId, idOrSlug)\n}\n\n/**\n * Populate a composition-node `relation` prop to delivery shape for the canvas\n * (EC-254) — the studio analog of the host's baked relations map. Resolves the\n * ref through the active session's client (published perspective, active locale),\n * so the canvas previews a block's picked documents (a ProductGrid's products, a\n * Reusable's element) exactly as delivery renders them. `null` when the target is\n * missing or not visible → the block degrades to its own empty state.\n */\nexport function resolveCanvasRelation(\n ref: DocumentRef,\n locale: Locale = previewLocale(),\n perspective: CanvasPerspective = 'published',\n): ResolvedDocument | null {\n return clientFor(locale, perspective).resolveRelation(ref)\n}\n\n/**\n * Resolve a listing block's query to its documents for the canvas (EC-255) — the\n * studio analog of the host's baked listings map. Runs the query (collection +\n * AND-only filter, optional sort/limit) through the active session's client\n * (published perspective, active locale), so the canvas previews a dynamic grid (a\n * ProductGrid keyed to a category) exactly as delivery renders it. An invalid query\n * (structured, never thrown) or a non-list lookup yields `[]` → the block's empty\n * state. Reuses the same `listDocuments` path as `runCollectionListQuery`.\n */\nexport function resolveCanvasListing(\n query: ListingQuery,\n locale: Locale = previewLocale(),\n perspective: CanvasPerspective = 'published',\n): ResolvedDocument[] {\n const result = clientFor(locale, perspective).listDocuments(query.collection, {\n where: query.where,\n ...(query.sort ? { sort: query.sort } : {}),\n ...(query.limit !== undefined ? { limit: query.limit } : {}),\n })\n return result.ok ? result.documents : []\n}\n\n/**\n * Statically validate a listing block's query against the ACTIVE session schema\n * (EC-460, the never-silent rule): the exact `validateWhere`/`validateSort`/\n * `validateLimit` checks `listDocuments` runs, WITHOUT executing the query — so\n * the canvas can surface a config-invalid listing (a filter/sort field the\n * collection doesn't declare `filterable`, an unknown collection) as a visible\n * notice instead of the block's indistinguishable empty state. With no content\n * session configured (boot instants, fixtures) there is no schema to judge\n * against, so it reports nothing rather than false-flagging every listing.\n */\nexport function validateCanvasListingQuery(query: ListingQuery): ContentQueryError[] {\n if (contentSession === null) return []\n const collection = playgroundCollection(query.collection)\n if (!collection) {\n return [\n queryError(\n 'unknown-collection',\n `Collection \"${query.collection}\" is not defined.`,\n ['collection'],\n undefined,\n { key: 'content.queryError.unknownCollection', vars: { collection: query.collection } },\n ),\n ]\n }\n return [\n ...validateWhere(collection, query.where),\n ...(query.sort ? validateSort(collection, query.sort) : []),\n ...(query.limit !== undefined ? validateLimit(query.limit) : []),\n ]\n}\n\n/**\n * Resolve a rich-text internal link's target document to its URL path for the canvas\n * (EC-291) — the studio analog of the host's baked links map. Derives the URL through\n * the active session's client (published perspective, active locale, config-sourced\n * routes), so the canvas previews a `{type:'reference'}` link's `href` exactly as\n * delivery serves it. `null` when the target is unpublished, missing, or addressed by\n * no route → the rich-text renderer shows the EC-290 `unresolved-ref` issue. Reuses\n * the same `resolveLink` path as the host bake, so both agree on the derived URL.\n */\nexport function resolveCanvasLink(\n ref: DocumentRef,\n locale: Locale = previewLocale(),\n perspective: CanvasPerspective = 'published',\n): string | null {\n return clientFor(locale, perspective).resolveLink(ref)\n}\n\n/** A pickable entry of a collection (the template-page preview switcher). */\nexport interface CollectionEntrySummary {\n id: string\n label: string\n}\n\n/** Published entries of a collection, labeled by the schema's title field. */\nexport function listCollectionEntries(collectionId: string): CollectionEntrySummary[] {\n const collection = playgroundCollection(collectionId)\n const result = runCollectionListQuery(collectionId)\n if (!result.ok) return []\n // EC-242: label via the shared identity resolver (string `titleField` or a\n // function pattern), over the delivery-shape values; falls back to the id.\n return result.documents.map((doc) => ({\n id: doc.id,\n label: collection ? resolveDocumentTitle(collection, doc, doc.id) : doc.id,\n }))\n}\n"]}
1
+ {"version":3,"file":"collection-content.js","sourceRoot":"","sources":["../../../src/lib/data/collection-content.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createContentClient,\n createContentContext,\n queryError,\n validateLimit,\n validateSort,\n validateWhere,\n} from '@elytracms/core/content'\nimport type {\n ContentClient,\n ContentDocumentSource,\n ContentLookup,\n ContentQueryError,\n GetDocumentResult,\n ListDocumentsQuery,\n ListDocumentsResult,\n ResolvedDocument,\n} from '@elytracms/core/content'\nimport {\n documentKey,\n localeSchema,\n parseLocaleConfig,\n resolveDocumentTitle,\n valuesInLocale,\n} from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentRef,\n Locale,\n LocaleConfig,\n OrphanedCollection,\n RouteRecord,\n} from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { listingRequestOf } from '@elytracms/core/runtime-renderer'\nimport type { ListingQuery } from '@elytracms/core/runtime-renderer'\nimport type { Clock } from '@elytracms/core/determinism'\nimport { getStudioLocale, getStudioLocaleConfig } from '../locale/locale-store'\n\n/**\n * The studio's content delivery engine for canvas previews (EC-166).\n *\n * Repeating sections and template pages preview *delivery-shaped* documents —\n * the exact shape `@elytracms/core/content` serves in production — so the canvas\n * shows what publishing will show. This module wires the canonical content\n * client (closed AD-3 query vocabulary, published perspective, EC-018 locale\n * fallback, depth-1 population, resolved assets) over a **content session**:\n *\n * - Convex mode: the project-session gate (`components/studio/project-session`)\n * configures the session with the project's HYDRATED schema + documents, so\n * \"Preview with\" lists and section previews show the live content — never\n * fixture titles (EC-177 gap 1).\n * - Playground mode (EC-361): the SAME hydration loader runs over the seeded\n * in-memory adapter (through operations — AD-17), so canvas listing/relation\n * population resolves from the in-memory session documents with identical\n * semantics. With no session configured (tests, boot instants, no persisted\n * content) the engine serves nothing.\n *\n * Nothing here invents query semantics; both modes go through the same\n * canonical client.\n */\n\n/** The schema + documents the preview engine serves (one project session). */\nexport interface ContentSessionState {\n collections: readonly CollectionDef[]\n documents: readonly CmsDocument[]\n /**\n * Published document keys via the version POINTER (EC-224). When present, the\n * preview's published perspective resolves by the pin — never the flat\n * `state` enum — so it matches what delivery serves. Absent (playground\n * fixtures / tests) → the rows' own `state` gates the perspective, unchanged.\n */\n publishedKeys?: readonly string[]\n /**\n * Locale config of the session content — the project's own, composed by\n * `contentSessionOf` from `elytra.config.ts`. Absent (a kit session, tests) →\n * the ambient studio config, then the single locale being asked for; EC-479\n * retired the engine's `sampleLocaleConfig` fallback here.\n */\n localeConfig?: LocaleConfig\n /**\n * The project's config-sourced routes (EC-207). Wired into the content client so\n * the canvas preview's `resolveLink` (EC-291) derives a rich-text link's URL from\n * the SAME routes delivery serves — the studio-live half of the host link bake.\n * Absent (playground/tests) → no routes, so `resolveLink` returns null and a\n * reference link degrades to the visible `unresolved-ref` issue.\n */\n routes?: readonly RouteRecord[]\n /**\n * The project's asset records (assets table). Wired into the lookup so the\n * content client resolves `asset`-typed FIELDS on listing/relation results\n * (e.g. a ProductGrid product's `gallery`) to delivery shape — the same assets\n * the renderer's `resolveAsset` reads for static props (EC-262). Absent\n * (playground/tests) → assets come only from `asset`-collection fixture docs.\n */\n assets?: readonly AssetRecord[]\n /**\n * EC-606 — the stored rows the session does NOT serve: per undeclared collection,\n * how many rows and how many published (`HydratedCms.orphans`, carried here by\n * `contentSessionOf`'s spread). The preview engine never reads it. It rides this\n * session because this is the one hydrated-content channel that reaches the\n * section rail in both modes: the rail renders outside `ProjectSessionProvider`,\n * so it cannot read the project session context, and a second documents query\n * would be a parallel data flow (AD-17). Read via {@link contentSessionOrphans}.\n * Absent (a kit session, tests) → none.\n */\n orphans?: readonly OrphanedCollection[]\n}\n\n/**\n * Default (no-session) collection schema: EMPTY. The studio ships no demo content\n * (Oli, 2026-06-19) — with no backend/content session configured, there is nothing\n * to preview, and the studio says so rather than serving fixtures. A real project's\n * collections + documents arrive via {@link configureContentSession} in BOTH modes\n * (EC-361): Convex hydration, or the playground's seeded in-memory adapter through\n * the same loader.\n */\nexport const playgroundCollections: readonly CollectionDef[] = []\n\n/**\n * The perspective a canvas preview resolves references against (EC-300/AD-16). The\n * canvas is draft-coherent: the editable Draft view resolves references to their\n * `'draft'` targets (so a just-made reusable shows immediately), the read-only\n * Published view resolves `'published'` (an honest preview of what delivery serves;\n * an unpublished hard ref shows the in-canvas placeholder). Delivery stays\n * published-only — this does not weaken `live-is-published-only`.\n */\nexport type CanvasPerspective = 'draft' | 'published'\n\nlet contentSession: ContentSessionState | null = null\nlet lookup: ContentLookup = createLookup(playgroundCollections, [], [])\n// Keyed by locale + perspective (EC-300) + the resolved locale CONFIG (EC-479) —\n// one deterministic client per distinct resolution context. See `clientFor`.\nconst clients = new Map<string, ContentClient>()\n\n// EC-460: a monotonically increasing session epoch + listener set, so a REACT\n// surface (the canvas render context) can re-render when the session is\n// re-pointed at fresher content. The session itself stays a module global (the\n// EC-361 design); this only makes \"it changed\" observable — `configureContentSession`\n// remains the single setter.\nlet sessionEpoch = 0\nconst sessionListeners = new Set<() => void>()\n\n/** The current content-session epoch — bumps on every `configureContentSession`. */\nexport function contentSessionEpoch(): number {\n return sessionEpoch\n}\n\n/**\n * Subscribe to content-session swaps (EC-460). The listener fires after every\n * `configureContentSession`; returns the unsubscribe. Shaped for\n * `useSyncExternalStore(subscribeContentSession, contentSessionEpoch)`.\n */\nexport function subscribeContentSession(listener: () => void): () => void {\n sessionListeners.add(listener)\n return () => sessionListeners.delete(listener)\n}\n\n/**\n * Point the preview engine at a project's hydrated content — Convex hydration\n * or the playground's in-memory persisted state (EC-361), both through the same\n * `loadHydratedCms` loader — or back at the empty no-session default (`null`).\n */\nexport function configureContentSession(next: ContentSessionState | null): void {\n contentSession = next\n lookup = next\n ? createLookup(next.collections, next.documents, next.publishedKeys, next.assets, next.localeConfig)\n : createLookup(playgroundCollections, [], [])\n clients.clear()\n sessionEpoch += 1\n for (const listener of [...sessionListeners]) listener()\n}\n\n/** Whether a hydrated content session is configured (Convex mode). */\nexport function hasContentSession(): boolean {\n return contentSession !== null\n}\n\nconst NO_ORPHANS: readonly OrphanedCollection[] = []\n\n/**\n * The active session's orphaned collections (EC-606), or an empty list with no\n * session. Referentially stable between `configureContentSession` calls (the\n * session's own array, or one shared empty constant), so it is a valid\n * `useSyncExternalStore(subscribeContentSession, contentSessionOrphans, …)`\n * snapshot — the rail foot reads it exactly that way.\n */\nexport function contentSessionOrphans(): readonly OrphanedCollection[] {\n return contentSession?.orphans ?? NO_ORPHANS\n}\n\n/** The server snapshot for {@link contentSessionOrphans}: no session exists during SSR. */\nexport function serverContentSessionOrphans(): readonly OrphanedCollection[] {\n return NO_ORPHANS\n}\n\n/** The collections the preview engine currently serves (session or fixtures). */\nfunction activeCollections(): readonly CollectionDef[] {\n return contentSession?.collections ?? playgroundCollections\n}\n\n/**\n * The sentinel {@link previewLocale} yields when NOTHING has declared a locale —\n * no ambient studio config, no content session. It is not a locale code and\n * matches no stored key, which is the point: with no session the lookup is empty\n * (`playgroundCollections` is `[]`), so every query already resolves to nothing\n * and this only fills `CacheTags.locale`, which no studio surface reads. EC-479:\n * the engine used to answer this question with its `sampleLocaleConfig` fixture's\n * `en` — a locale nobody chose, standing in for the project's own.\n */\nconst NO_LOCALE = ''\n\n/**\n * The locale a canvas preview resolves in, when the caller names none: the\n * ambient studio locale (installed from the PROJECT's config by `CmsProvider`),\n * else the active content session's own declared default (surfaces that render\n * outside a CMS session — the Components view's previews — still resolve in the\n * project's locale), else {@link NO_LOCALE}. Never an engine fixture.\n */\nfunction previewLocale(): Locale {\n return getStudioLocale() ?? contentSession?.localeConfig?.default ?? NO_LOCALE\n}\n\n/**\n * The locale set a preview resolves fallbacks against (EC-018): the session's own\n * declaration, else the ambient studio config (the project's, via `elytra.config.ts`),\n * else the single locale actually being asked for.\n *\n * That last step is a DERIVATION, not an invention — it says \"the only locale we\n * know of is the one you named\", so the default-locale fallback is the requested\n * locale itself (a no-op) and a localized read falls through to `doc.values`.\n * Before EC-479 this line read `?? sampleLocaleConfig`, which silently declared\n * every locale-less studio bilingual `en/de` and resolved fallbacks through an `en`\n * no project had chosen. Only reached with no session AND no installed config, so\n * no stored key is ever written against it.\n */\nfunction activeLocaleConfig(locale: Locale): LocaleConfig {\n return contentSession?.localeConfig ?? getStudioLocaleConfig() ?? requestedLocaleOnly(locale)\n}\n\n/**\n * The single-locale derivation above, through the one constructor (EC-580:\n * `LocaleConfig` is parse-only). {@link NO_LOCALE} — or anything else that is not\n * a locale code — becomes `und`, BCP 47's own \"undetermined\", which likewise\n * matches no stored key.\n */\nfunction requestedLocaleOnly(locale: Locale): LocaleConfig {\n const code = localeSchema.safeParse(locale).success ? locale : 'und'\n return parseLocaleConfig({ default: code, locales: [code] })\n}\n\n/** Resolve a collection in the active content session (fixtures in playground). */\nexport function playgroundCollection(id: string): CollectionDef | undefined {\n return activeCollections().find((collection) => collection.id === id)\n}\n\n/**\n * Overlay the active session's canonical `filterable` declarations onto a\n * schema that doesn't carry any of its own. The inspector hands the Content\n * tab fixture collection defs; the filter/sort pickers must offer exactly the\n * EC-143 declaration set the delivery engine enforces, so both ends agree. A\n * collection that declares its own filterable fields is left untouched.\n */\nexport function withPlaygroundFilterable(\n collections: readonly CollectionDef[],\n): readonly CollectionDef[] {\n return collections.map((collection) => {\n if (collection.fields.some((field) => field.filterable === true)) return collection\n const counterpart = playgroundCollection(collection.id)\n if (counterpart === undefined) return collection\n const flagged = new Set(\n counterpart.fields.filter((field) => field.filterable === true).map((field) => field.name),\n )\n if (flagged.size === 0) return collection\n return {\n ...collection,\n fields: collection.fields.map((field) =>\n flagged.has(field.name) ? { ...field, filterable: true } : field,\n ),\n }\n })\n}\n\n/**\n * Synthesize the persistence-shaped asset record the asset resolver reads. EC-580:\n * the record's single `alt` is the DEFAULT locale's displayed value\n * (`valuesInLocale`, the project's config default) — it used to read\n * `localized.en.alt` whatever the project's locales were (MODE F5b, \"further\n * evidence\"), so a `de`-default project's previews had no alt and a shared `alt`\n * was never read. Without a locale model (no session config) only the shared bag\n * can be read.\n */\nfunction assetRecordOf(\n doc: CmsDocument,\n collection: CollectionDef | undefined,\n locales: LocaleConfig | undefined,\n): AssetRecord {\n const values = doc.values\n const alt =\n collection && locales\n ? valuesInLocale(collection, doc, locales.default, locales).alt\n : values.alt\n return {\n id: doc.id,\n projectId: 'playground',\n filename: typeof values.filename === 'string' ? values.filename : doc.id,\n contentType: 'image/png',\n byteSize: 0,\n alt: typeof alt === 'string' ? alt : undefined,\n storageKey: typeof values.url === 'string' ? values.url : doc.id,\n createdAt: '2026-01-01T00:00:00.000Z',\n updatedAt: '2026-01-01T00:00:00.000Z',\n }\n}\n\nfunction createLookup(\n collectionDefs: readonly CollectionDef[],\n documents: readonly CmsDocument[],\n publishedKeys?: readonly string[],\n assetRecords?: readonly AssetRecord[],\n locales?: LocaleConfig,\n): ContentLookup {\n const collections = new Map(collectionDefs.map((c) => [c.id, c]))\n // EC-224: published-ness is the version POINTER, not the flat `state` enum.\n // A hydrated session hands `publishedKeys` (the pinned set) → wrap each row as\n // a `DocumentHistory` so the published perspective resolves by the pin (the\n // live row is always a draft now) with ZERO dependence on `state`. Without a\n // pointer set (playground fixtures / tests) we keep bare rows, whose own\n // `state` gates the published perspective — byte-identical to before.\n const published = publishedKeys ? new Set(publishedKeys) : null\n const toSource = (doc: CmsDocument): ContentDocumentSource => {\n if (!published) return doc\n return published.has(documentKey({ collection: doc.collection, id: doc.id }))\n ? { draft: doc, published: doc, versions: [] }\n : { draft: doc, published: undefined, versions: [] }\n }\n const byKey = new Map(\n documents.map((doc) => [`${doc.collection}:${doc.id}`, toSource(doc)] as const),\n )\n // Asset resolution source for `asset`-typed FIELDS on resolved documents\n // (listing/relation results, e.g. a product's `gallery`). Two sources, real\n // records last so they win: (1) `asset`-collection fixture documents — how\n // playground/tests model assets; (2) the project's real asset-table records\n // (EC-262) — a Convex session has its assets here, NOT as `asset` documents,\n // so without these the content client resolves no nested image.\n const assets = new Map<string, AssetRecord>()\n for (const doc of documents) {\n if (doc.collection === 'asset') {\n assets.set(doc.id, assetRecordOf(doc, collections.get(doc.collection), locales))\n }\n }\n for (const record of assetRecords ?? []) assets.set(record.id, record)\n return {\n collection: (id) => collections.get(id),\n document: (ref) => byKey.get(`${ref.collection}:${ref.id}`),\n documents: (collectionId) =>\n documents.filter((doc) => doc.collection === collectionId).map(toSource),\n asset: (id) => assets.get(id),\n // Prefer the record's resolved servable `url` — the one the renderer's\n // resolveAsset reads (EC-262). Real asset-table records carry a Convex blob\n // locator in `storageKey` and the servable URL in `url`; fixture/`asset`-document\n // records (assetRecordOf) leave `url` unset and store the servable url in\n // `storageKey`. Falling back keeps both correct.\n assetUrl: (asset) => asset.url ?? asset.storageKey,\n }\n}\n\n/**\n * One deterministic client per (locale, perspective): EC-018 locale fallback,\n * draft-or-published perspective (EC-300/AD-16, default `'published'`). The studio's\n * ambient active locale (EC-176, `lib/locale`) picks the locale at query time, so the\n * canvas previews follow the locale switcher exactly like delivery would; the canvas's\n * Draft/Published view picks the perspective. The cache resets whenever the content\n * session changes.\n */\nfunction clientFor(locale: Locale, perspective: CanvasPerspective = 'published'): ContentClient {\n const locales = activeLocaleConfig(locale)\n // EC-479: the locale CONFIG is part of the client's identity now — it may come\n // from the ambient studio store, which changes independently of the session the\n // cache is cleared on, so keying by locale alone could serve a client built\n // against a superseded locale set.\n const key = `${locale}:${perspective}:${locales.default}|${locales.locales.join(',')}`\n const existing = clients.get(key)\n if (existing) return existing\n const client = createContentClient({\n lookup,\n context: createContentContext({\n locale,\n perspective,\n locales,\n }),\n // EC-291: the routes a rich-text link resolves against (config-sourced) — the\n // studio-live half of the host link bake.\n routes: contentSession?.routes ?? [],\n })\n clients.set(key, client)\n return client\n}\n\n/**\n * Run a closed-vocabulary list query (AD-3: eq/in/lt/lte/gt/gte, AND-only,\n * filterable fields only) and return the delivery-shaped result. Invalid\n * queries come back as structured errors — never thrown, never silently\n * widened. Resolves in the studio's active locale unless one is passed.\n */\nexport function runCollectionListQuery(\n collectionId: string,\n query: ListDocumentsQuery = {},\n locale: Locale = previewLocale(),\n): ListDocumentsResult {\n return clientFor(locale).listDocuments(collectionId, query)\n}\n\n/** Resolve one document (by id or slug) into delivery shape. */\nexport function getCollectionDocument(\n collectionId: string,\n idOrSlug: string,\n locale: Locale = previewLocale(),\n): GetDocumentResult {\n return clientFor(locale).getDocument(collectionId, idOrSlug)\n}\n\n/**\n * Populate a composition-node `relation` prop to delivery shape for the canvas\n * (EC-254) — the studio analog of the host's baked relations map. Resolves the\n * ref through the active session's client (published perspective, active locale),\n * so the canvas previews a block's picked documents (a ProductGrid's products, a\n * Reusable's element) exactly as delivery renders them. `null` when the target is\n * missing or not visible → the block degrades to its own empty state.\n */\nexport function resolveCanvasRelation(\n ref: DocumentRef,\n locale: Locale = previewLocale(),\n perspective: CanvasPerspective = 'published',\n): ResolvedDocument | null {\n return clientFor(locale, perspective).resolveRelation(ref)\n}\n\n/** The real clock — the canvas boundary's default; tests inject a fixed one (EC-581 6a). */\nconst systemClock: Clock = () => new Date().toISOString()\n\n/**\n * A listing query as `listDocuments` input (EC-581 6a): executed through\n * `listingRequestOf` — the phase the delivery host runs too, so canvas and site\n * build the identical request — with `{ now: true }` read from `clock`. The\n * operands are JSON bound from authored props; the cast hands them to the one\n * validator (`validateWhere`/`validateLimit`), which reports anything invalid.\n */\nfunction listDocumentsQueryOf(query: ListingQuery, clock: Clock): ListDocumentsQuery {\n const request = listingRequestOf(query, clock)\n return {\n where: request.where as ListDocumentsQuery['where'],\n ...(request.sort ? { sort: request.sort } : {}),\n ...(request.limit !== undefined ? { limit: request.limit as number } : {}),\n }\n}\n\n/**\n * Resolve a listing block's query to its documents for the canvas (EC-255) — the\n * studio analog of the host's baked listings map. Runs the query (collection +\n * AND-only `where`, optional sort/limit; EC-581 6a: `now` read from `clock`)\n * through the active session's client (published perspective, active locale), so\n * the canvas previews a dynamic grid (a ProductGrid keyed to a category) exactly as\n * delivery renders it. An invalid query (structured, never thrown) or a non-list\n * lookup yields `[]` → the block's empty state, and the canvas notice\n * ({@link validateCanvasListingQuery}) says why. Reuses the same `listDocuments`\n * path as `runCollectionListQuery`.\n */\nexport function resolveCanvasListing(\n query: ListingQuery,\n locale: Locale = previewLocale(),\n perspective: CanvasPerspective = 'published',\n clock: Clock = systemClock,\n): ResolvedDocument[] {\n const result = clientFor(locale, perspective).listDocuments(\n query.collection,\n listDocumentsQueryOf(query, clock),\n )\n return result.ok ? result.documents : []\n}\n\n/**\n * Statically validate a listing block's query against the ACTIVE session schema\n * (EC-460, the never-silent rule): the exact `validateWhere`/`validateSort`/\n * `validateLimit` checks `listDocuments` runs, WITHOUT executing the query — so\n * the canvas can surface a config-invalid listing (a filter/sort field the\n * collection doesn't declare `filterable`, an unknown collection) as a visible\n * notice instead of the block's indistinguishable empty state. With no content\n * session configured (boot instants, fixtures) there is no schema to judge\n * against, so it reports nothing rather than false-flagging every listing.\n */\nexport function validateCanvasListingQuery(\n query: ListingQuery,\n clock: Clock = systemClock,\n): ContentQueryError[] {\n if (contentSession === null) return []\n const collection = playgroundCollection(query.collection)\n if (!collection) {\n return [\n queryError(\n 'unknown-collection',\n `Collection \"${query.collection}\" is not defined.`,\n ['collection'],\n undefined,\n { key: 'content.queryError.unknownCollection', vars: { collection: query.collection } },\n ),\n ]\n }\n // EC-581 6a: judge the request the query EXECUTES as (`now` bound), with the\n // same three validators `listDocuments` runs — notice and execution agree.\n const request = listDocumentsQueryOf(query, clock)\n return [\n ...(request.where ? validateWhere(collection, request.where) : []),\n ...(request.sort ? validateSort(collection, request.sort) : []),\n ...(request.limit !== undefined ? validateLimit(request.limit) : []),\n ]\n}\n\n/**\n * Resolve a rich-text internal link's target document to its URL path for the canvas\n * (EC-291) — the studio analog of the host's baked links map. Derives the URL through\n * the active session's client (published perspective, active locale, config-sourced\n * routes), so the canvas previews a `{type:'reference'}` link's `href` exactly as\n * delivery serves it. `null` when the target is unpublished, missing, or addressed by\n * no route → the rich-text renderer shows the EC-290 `unresolved-ref` issue. Reuses\n * the same `resolveLink` path as the host bake, so both agree on the derived URL.\n */\nexport function resolveCanvasLink(\n ref: DocumentRef,\n locale: Locale = previewLocale(),\n perspective: CanvasPerspective = 'published',\n): string | null {\n return clientFor(locale, perspective).resolveLink(ref)\n}\n\n/** A pickable entry of a collection (the template-page preview switcher). */\nexport interface CollectionEntrySummary {\n id: string\n label: string\n}\n\n/** Published entries of a collection, labeled by the schema's title field. */\nexport function listCollectionEntries(collectionId: string): CollectionEntrySummary[] {\n const collection = playgroundCollection(collectionId)\n const result = runCollectionListQuery(collectionId)\n if (!result.ok) return []\n // EC-242: label via the shared identity resolver (string `titleField` or a\n // function pattern), over the delivery-shape values; falls back to the id.\n return result.documents.map((doc) => ({\n id: doc.id,\n label: collection ? resolveDocumentTitle(collection, doc, doc.id) : doc.id,\n }))\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { ComponentNode, GridContainerOptions, InsertTarget } from '@elytracms/core/project-graph';
1
+ import type { ComponentNode, GridContainerOptions, InsertTarget, LayoutContainerIds } from '@elytracms/core/project-graph';
2
2
  import type { ComponentManifest } from '@elytracms/core/component-registry';
3
3
  /**
4
4
  * Drag & drop eligibility rules (EC-160 + EC-162, vision §3.1) — pure
@@ -11,7 +11,8 @@ import type { ComponentManifest } from '@elytracms/core/component-registry';
11
11
  * The drop-zone model is position-based: `into` a slot, `before`/`after` a
12
12
  * sibling along the parent's flow axis, and the EC-162 gesture positions
13
13
  * `left`/`right` — dropping *beside* an element wraps both in a two-column
14
- * `Grid` container (see `applyWrap`).
14
+ * `Grid` container (see `applyWrap`) — the component that fills the
15
+ * `layout.grid` role (EC-591); with no such component the gesture is off.
15
16
  */
16
17
  /**
17
18
  * The manifest slice the rules evaluate. Structural (not the full
@@ -83,7 +84,13 @@ export type DropRejectionCode =
83
84
  /** The page root cannot be moved, and nothing can drop beside it. */
84
85
  | 'root'
85
86
  /** Structurally valid but changes nothing (dropping a node onto its own position). */
86
- | 'no-op';
87
+ | 'no-op'
88
+ /**
89
+ * EC-591: the wrap-beside gesture needs the component that fills the
90
+ * `layout.grid` role, and the project's component list has none — the gesture
91
+ * is off (the canvas's role notice says so), never a hardcoded fallback id.
92
+ */
93
+ | 'no-grid';
87
94
  export interface DropRejection {
88
95
  eligible: false;
89
96
  code: DropRejectionCode;
@@ -136,9 +143,9 @@ export declare function slotAllows(slot: SlotRule | undefined, componentId: stri
136
143
  * composition root, mirroring `validateCompositionValue` — so this returns the
137
144
  * field vocabulary when `parentId` IS the root node and `undefined` (no field
138
145
  * constraint) anywhere deeper. Keyed on node IDENTITY, never on the component id:
139
- * the composition editor's synthetic root is a `base.Stack`, which is also a
140
- * nestable block, and a rule keyed on the component id would leash every nested
141
- * Stack. EC-544: an omitted `allow` is `undefined` here (any registered
146
+ * the composition editor's synthetic root is the `layout.stack` component, which
147
+ * is also a nestable block, and a rule keyed on the component id would leash
148
+ * every nested Stack. EC-544: an omitted `allow` is `undefined` here (any registered
142
149
  * component); a declared list is a closed set.
143
150
  */
144
151
  export declare function fieldAllowAt(root: ComponentNode, parentId: string, fieldAllow: readonly string[] | undefined): readonly string[] | undefined;
@@ -165,6 +172,12 @@ export declare function placementRefusal(args: {
165
172
  export declare function resolveDrop(args: {
166
173
  root: ComponentNode;
167
174
  getManifest: ManifestLookup;
175
+ /**
176
+ * The components filling the layout roles (EC-591, `ComponentRegistry.layout`) —
177
+ * the wrap gesture builds the `layout.grid` one, and is OFF when nobody fills it.
178
+ * Required so no surface can forget it.
179
+ */
180
+ layout: LayoutContainerIds;
168
181
  /**
169
182
  * The composition field's top-level `allow` (EC-554) — `undefined` when the
170
183
  * field declares none. Required so no surface can forget to pass it; judged
@@ -213,6 +226,8 @@ export interface WrapResult {
213
226
  */
214
227
  export declare function applyWrap(args: {
215
228
  root: ComponentNode;
229
+ /** The `layout.grid` component's id (EC-591) — the container the gesture builds. */
230
+ gridComponentId: string;
216
231
  referenceId: string;
217
232
  side: WrapSide;
218
233
  partner: WrapPartner;
@@ -228,7 +243,7 @@ export type FlowAxis = 'vertical' | 'horizontal';
228
243
  * horizontally when it actually has ≥ 2 column tracks (an absent/invalid
229
244
  * `columns` renders one column). Everything else is block flow (vertical).
230
245
  */
231
- export declare function containerFlowAxis(node: ComponentNode | undefined): FlowAxis;
246
+ export declare function containerFlowAxis(node: ComponentNode | undefined, layout: LayoutContainerIds): FlowAxis;
232
247
  /**
233
248
  * Map a 2D pointer offset within a drop target's box onto a `DropPosition`
234
249
  * (EC-162). `flow` is the flow axis of the target's *parent* — before/after
@@ -1,4 +1,4 @@
1
- import { DEFAULT_SLOT, GRID_COMPONENT_ID, OUTLET_SLOT, STACK_COMPONENT_ID, getNode, getParentInfo, gridContainerNode, isDescendantOf, isStaticProp, removeNode, updateNode, } from '@elytracms/core/project-graph';
1
+ import { DEFAULT_SLOT, OUTLET_SLOT, getNode, getParentInfo, gridContainerNode, isDescendantOf, isStaticProp, layoutContainerKind, removeNode, updateNode, } from '@elytracms/core/project-graph';
2
2
  import { manifestSlots } from '@elytracms/core/component-registry';
3
3
  /**
4
4
  * The canonical `ManifestRules` view of a full manifest (EC-386): the declared
@@ -59,9 +59,9 @@ export function slotAllows(slot, componentId) {
59
59
  * composition root, mirroring `validateCompositionValue` — so this returns the
60
60
  * field vocabulary when `parentId` IS the root node and `undefined` (no field
61
61
  * constraint) anywhere deeper. Keyed on node IDENTITY, never on the component id:
62
- * the composition editor's synthetic root is a `base.Stack`, which is also a
63
- * nestable block, and a rule keyed on the component id would leash every nested
64
- * Stack. EC-544: an omitted `allow` is `undefined` here (any registered
62
+ * the composition editor's synthetic root is the `layout.stack` component, which
63
+ * is also a nestable block, and a rule keyed on the component id would leash
64
+ * every nested Stack. EC-544: an omitted `allow` is `undefined` here (any registered
65
65
  * component); a declared list is a closed set.
66
66
  */
67
67
  export function fieldAllowAt(root, parentId, fieldAllow) {
@@ -91,7 +91,7 @@ export function placementRefusal(args) {
91
91
  * same answer, on both surfaces.
92
92
  */
93
93
  export function resolveDrop(args) {
94
- const { root, getManifest, fieldAllow, subject, location } = args;
94
+ const { root, getManifest, layout, fieldAllow, subject, location } = args;
95
95
  if (subject.kind === 'node' && subject.nodeId === root.id) {
96
96
  return rejected('root', 'The page root cannot be moved.');
97
97
  }
@@ -101,7 +101,7 @@ export function resolveDrop(args) {
101
101
  }
102
102
  // EC-162: dropping beside the reference wraps both in a two-column Grid.
103
103
  if (location.position === 'left' || location.position === 'right') {
104
- return resolveWrap({ root, getManifest, fieldAllow, subject, reference, side: location.position });
104
+ return resolveWrap({ root, getManifest, layout, fieldAllow, subject, reference, side: location.position });
105
105
  }
106
106
  // Resolve the concrete insertion point: parent node, slot, index.
107
107
  let parentId;
@@ -172,7 +172,13 @@ export function resolveDrop(args) {
172
172
  export const WRAP_GRID_OPTIONS = { columns: 2, gap: '4' };
173
173
  /** Eligibility for a `left`/`right` (wrap-beside) drop. */
174
174
  function resolveWrap(args) {
175
- const { root, getManifest, fieldAllow, subject, reference, side } = args;
175
+ const { root, getManifest, layout, fieldAllow, subject, reference, side } = args;
176
+ // EC-591: the gesture builds the `layout.grid` component — a role nobody fills
177
+ // switches it off (the canvas shows the role notice), never a hidden default id.
178
+ const gridId = layout.grid;
179
+ if (gridId === undefined) {
180
+ return rejected('no-grid', 'No component fills the layout.grid role — side-by-side columns are unavailable.');
181
+ }
176
182
  // The Grid takes the reference's place in its parent slot — so the
177
183
  // reference needs a parent (the root is the page anchor, never wrapped)
178
184
  // and that slot's allow-list must accept a Grid. EC-554: beside a direct
@@ -188,7 +194,7 @@ function resolveWrap(args) {
188
194
  parentId: info.parent.id,
189
195
  slot: parentSlotRule,
190
196
  fieldAllow,
191
- componentId: GRID_COMPONENT_ID,
197
+ componentId: gridId,
192
198
  });
193
199
  if (refusal === 'field') {
194
200
  return rejected('field-not-allowed', "The field's top-level vocabulary does not allow a Grid container.");
@@ -197,7 +203,7 @@ function resolveWrap(args) {
197
203
  return rejected('not-allowed', `Slot "${info.slot}" does not allow a Grid container.`);
198
204
  }
199
205
  // Both columns land in the Grid's children slot.
200
- const gridSlot = primaryInsertSlot(getManifest(GRID_COMPONENT_ID));
206
+ const gridSlot = primaryInsertSlot(getManifest(gridId));
201
207
  for (const componentId of [reference.componentId, subject.componentId]) {
202
208
  if (!slotAllows(gridSlot, componentId)) {
203
209
  return rejected('not-allowed', `A Grid container does not allow "${componentId}".`);
@@ -230,7 +236,7 @@ function resolveWrap(args) {
230
236
  * `gridContainerNode` constructor output — identical to manual composition.
231
237
  */
232
238
  export function applyWrap(args) {
233
- const { root, referenceId, side, partner, containerId } = args;
239
+ const { root, gridComponentId, referenceId, side, partner, containerId } = args;
234
240
  if (referenceId === root.id || !getNode(root, referenceId))
235
241
  return null;
236
242
  let working = root;
@@ -247,7 +253,7 @@ export function applyWrap(args) {
247
253
  else {
248
254
  partnerNode = partner.node;
249
255
  }
250
- const next = updateNode(working, referenceId, (reference) => gridContainerNode(containerId, WRAP_GRID_OPTIONS, side === 'left' ? [partnerNode, reference] : [reference, partnerNode]));
256
+ const next = updateNode(working, referenceId, (reference) => gridContainerNode(gridComponentId, containerId, WRAP_GRID_OPTIONS, side === 'left' ? [partnerNode, reference] : [reference, partnerNode]));
251
257
  return { root: next, containerId };
252
258
  }
253
259
  function staticPropValue(node, name) {
@@ -262,13 +268,14 @@ function staticPropValue(node, name) {
262
268
  * horizontally when it actually has ≥ 2 column tracks (an absent/invalid
263
269
  * `columns` renders one column). Everything else is block flow (vertical).
264
270
  */
265
- export function containerFlowAxis(node) {
271
+ export function containerFlowAxis(node, layout) {
266
272
  if (!node)
267
273
  return 'vertical';
268
- if (node.componentId === STACK_COMPONENT_ID) {
274
+ const kind = layoutContainerKind(node.componentId, layout);
275
+ if (kind === 'stack') {
269
276
  return staticPropValue(node, 'direction') === 'horizontal' ? 'horizontal' : 'vertical';
270
277
  }
271
- if (node.componentId === GRID_COMPONENT_ID) {
278
+ if (kind === 'grid') {
272
279
  const columns = staticPropValue(node, 'columns');
273
280
  return typeof columns === 'number' && columns >= 2 ? 'horizontal' : 'vertical';
274
281
  }
@@ -1 +1 @@
1
- {"version":3,"file":"drop-rules.js","sourceRoot":"","sources":["../../../src/lib/dnd/drop-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_SLOT,\n GRID_COMPONENT_ID,\n OUTLET_SLOT,\n STACK_COMPONENT_ID,\n getNode,\n getParentInfo,\n gridContainerNode,\n isDescendantOf,\n isStaticProp,\n removeNode,\n updateNode,\n} from '@elytracms/core/project-graph'\nimport type { ComponentNode, GridContainerOptions, InsertTarget } from '@elytracms/core/project-graph'\nimport { manifestSlots } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\n\n/**\n * Drag & drop eligibility rules (EC-160 + EC-162, vision §3.1) — pure\n * graph-level functions with **no DOM or UI dependencies**. The canvas and the\n * page tree both resolve drops through `resolveDrop`, so the two surfaces can\n * never disagree about what a manifest slot allow-list permits, what the\n * composition field's top-level vocabulary permits at the root (EC-554), which\n * children a component accepts, or whether a move would create a cycle.\n *\n * The drop-zone model is position-based: `into` a slot, `before`/`after` a\n * sibling along the parent's flow axis, and the EC-162 gesture positions\n * `left`/`right` — dropping *beside* an element wraps both in a two-column\n * `Grid` container (see `applyWrap`).\n */\n\n// ---------------------------------------------------------------------------\n// Inputs\n// ---------------------------------------------------------------------------\n\n/**\n * The manifest slice the rules evaluate. Structural (not the full\n * `ComponentManifest`) so tests and future callers can feed minimal lookups.\n */\nexport interface SlotRule {\n name: string\n /** Restrict which component ids may be inserted (omit to allow any). */\n allow?: string[]\n}\n\nexport interface ManifestRules {\n slots: SlotRule[]\n}\n\n/** Resolve a component id to its manifest slot rules (undefined = unknown component). */\nexport type ManifestLookup = (componentId: string) => ManifestRules | undefined\n\n/**\n * The canonical `ManifestRules` view of a full manifest (EC-386): the declared\n * `slots` merged with the blocks-prop-derived slots (`manifestSlots`, EC-190\n * \"slots are blocks props\" — a `blocks`-type prop IS a named child region whose\n * subtrees live in `node.slots[propName]`, carrying the prop's `allow` leash).\n * Every surface that feeds a real `ComponentManifest` into a {@link ManifestLookup}\n * or {@link primaryInsertSlot} must adapt through this — resolving against raw\n * `manifest.slots` silently degrades a `project.Section.body`-style region to a\n * sibling insert. One derivation, so tree insert, DnD, paste, and the canvas\n * affordances can never disagree about which regions exist or what they allow.\n */\nexport function manifestRules(manifest: ComponentManifest | undefined): ManifestRules | undefined {\n return manifest ? { slots: manifestSlots(manifest) } : undefined\n}\n\n/** What is being dragged. */\nexport type DragSubject =\n /** An existing node of the current page — a completed drop is a `move`. */\n | { kind: 'node'; nodeId: string; componentId: string }\n /** A new subtree (blocks-panel tile) — a completed drop is an `insert`. */\n | { kind: 'component'; componentId: string }\n\n/**\n * Where, relative to a reference node, the drop would land. `into`, `before`\n * and `after` are the EC-160 positions; `left`/`right` are the EC-162 gesture\n * positions — \"beside\" the reference, wrapping both elements in a two-column\n * `Grid` container.\n */\nexport type DropPosition = 'into' | 'before' | 'after' | 'left' | 'right'\n\n/** The side of the reference an EC-162 wrap gesture drops on. */\nexport type WrapSide = 'left' | 'right'\n\nexport interface DropLocation {\n /** The reference node id (the hovered node/row). */\n nodeId: string\n position: DropPosition\n}\n\n// ---------------------------------------------------------------------------\n// Outcomes\n// ---------------------------------------------------------------------------\n\nexport type DropRejectionCode =\n /** The reference node does not exist in the tree. */\n | 'missing-target'\n /** `into` on a component that declares no insertable slot. */\n | 'no-slot'\n /** The target slot's manifest allow-list rejects the dragged component. */\n | 'not-allowed'\n /**\n * EC-554: the composition FIELD's top-level `allow` rejects the component\n * landing as a direct child of the composition root (see {@link fieldAllowAt}).\n */\n | 'field-not-allowed'\n /** Moving a node into itself or its own subtree (cycle). */\n | 'own-subtree'\n /** The page root cannot be moved, and nothing can drop beside it. */\n | 'root'\n /** Structurally valid but changes nothing (dropping a node onto its own position). */\n | 'no-op'\n\nexport interface DropRejection {\n eligible: false\n code: DropRejectionCode\n reason: string\n}\n\n/** An eligible drop: exactly one builder action (move, insert, or wrap) commits it. */\nexport type DropPlan =\n | { eligible: true; kind: 'move'; nodeId: string; target: InsertTarget }\n | { eligible: true; kind: 'insert'; target: InsertTarget }\n /**\n * EC-162 wrap gesture: the reference node and the dragged subject become the\n * two columns of a new `Grid` container, created atomically (`applyWrap`).\n * `nodeId` is present when the subject is an existing node (a move-wrap);\n * absent for blocks-panel components (an insert-wrap via `buildNode`).\n */\n | { eligible: true; kind: 'wrap'; referenceId: string; side: WrapSide; nodeId?: string }\n\nexport type DropResolution = DropPlan | DropRejection\n\nfunction rejected(code: DropRejectionCode, reason: string): DropRejection {\n return { eligible: false, code, reason }\n}\n\n// ---------------------------------------------------------------------------\n// Slot rules\n// ---------------------------------------------------------------------------\n\n/**\n * The slot an `into` drop inserts into: the default `children` slot when\n * declared, else the first authored slot that is not the layout `outlet`.\n * Mirrors the shell's insert-context resolution so click-insert and\n * drag-insert land in the same place.\n */\nexport function primaryInsertSlot(manifest: ManifestRules | undefined): SlotRule | undefined {\n if (!manifest) return undefined\n return (\n manifest.slots.find((s) => s.name === DEFAULT_SLOT) ??\n manifest.slots.find((s) => s.name !== OUTLET_SLOT)\n )\n}\n\n/** True when a component can receive `into` drops at all (declares an insertable slot). */\nexport function canNestInto(getManifest: ManifestLookup, componentId: string): boolean {\n return primaryInsertSlot(getManifest(componentId)) !== undefined\n}\n\n/**\n * True when an allow-list permits the component id — absent = any, a present\n * list (even an empty one) is a closed set (EC-544). The one membership rule the\n * slot leash and the field leash share.\n */\nfunction allowListPermits(allow: readonly string[] | undefined, componentId: string): boolean {\n return !allow || allow.includes(componentId)\n}\n\n/** True when a slot's allow-list (if any) permits the component id. */\nexport function slotAllows(slot: SlotRule | undefined, componentId: string): boolean {\n return allowListPermits(slot?.allow, componentId)\n}\n\n/**\n * The composition FIELD's top-level vocabulary as it applies to a placement under\n * `parentId` (EC-188, EC-554) — the ONE reader of a field's `allow` on the studio\n * side: every insertion surface (the insert offer in `insert-context`, drag-and-drop\n * on the canvas and the page tree incl. the wrap gesture, paste) asks here, so no\n * surface can apply the field leash differently or not at all.\n *\n * A field `allow` governs only the value's TOP level — a direct child of the\n * composition root, mirroring `validateCompositionValue` — so this returns the\n * field vocabulary when `parentId` IS the root node and `undefined` (no field\n * constraint) anywhere deeper. Keyed on node IDENTITY, never on the component id:\n * the composition editor's synthetic root is a `base.Stack`, which is also a\n * nestable block, and a rule keyed on the component id would leash every nested\n * Stack. EC-544: an omitted `allow` is `undefined` here (any registered\n * component); a declared list is a closed set.\n */\nexport function fieldAllowAt(\n root: ComponentNode,\n parentId: string,\n fieldAllow: readonly string[] | undefined,\n): readonly string[] | undefined {\n return parentId === root.id ? fieldAllow : undefined\n}\n\n/**\n * Which declared leash refuses `componentId` landing under `parentId` in `slot`\n * (EC-545 slot leash, EC-554 field leash) — `null` when both admit it. The field\n * is judged first: it can only refuse at the composition root, where the slot is\n * the invisible synthetic root's and the field is the declaration the author sees.\n */\nexport function placementRefusal(args: {\n root: ComponentNode\n /** The node the placement lands under (the insertion target's `parentId`). */\n parentId: string\n slot: SlotRule | undefined\n fieldAllow: readonly string[] | undefined\n componentId: string\n}): 'field' | 'slot' | null {\n const { root, parentId, slot, fieldAllow, componentId } = args\n if (!allowListPermits(fieldAllowAt(root, parentId, fieldAllow), componentId)) return 'field'\n if (!slotAllows(slot, componentId)) return 'slot'\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a prospective drop into either an executable plan (one `move` or\n * `insert` builder action → one operations command → one undo step) or a\n * structured rejection the UI shows *during* the drag. Pure: same inputs,\n * same answer, on both surfaces.\n */\nexport function resolveDrop(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n /**\n * The composition field's top-level `allow` (EC-554) — `undefined` when the\n * field declares none. Required so no surface can forget to pass it; judged\n * through {@link fieldAllowAt} for drops whose parent is the root.\n */\n fieldAllow: readonly string[] | undefined\n subject: DragSubject\n location: DropLocation\n}): DropResolution {\n const { root, getManifest, fieldAllow, subject, location } = args\n\n if (subject.kind === 'node' && subject.nodeId === root.id) {\n return rejected('root', 'The page root cannot be moved.')\n }\n\n const reference = getNode(root, location.nodeId)\n if (!reference) {\n return rejected('missing-target', `Node \"${location.nodeId}\" is not on this page.`)\n }\n\n // EC-162: dropping beside the reference wraps both in a two-column Grid.\n if (location.position === 'left' || location.position === 'right') {\n return resolveWrap({ root, getManifest, fieldAllow, subject, reference, side: location.position })\n }\n\n // Resolve the concrete insertion point: parent node, slot, index.\n let parentId: string\n let slotRule: SlotRule | undefined\n let slotName: string\n let index: number\n\n if (location.position === 'into') {\n const manifest = getManifest(reference.componentId)\n const slot = primaryInsertSlot(manifest)\n if (!slot) {\n return rejected('no-slot', `\"${reference.componentId}\" does not accept children.`)\n }\n parentId = reference.id\n slotRule = slot\n slotName = slot.name\n index = (reference.slots?.[slot.name] ?? []).length\n } else {\n const info = getParentInfo(root, reference.id)\n if (!info) {\n return rejected('root', 'Nothing can be dropped beside the page root.')\n }\n parentId = info.parent.id\n slotName = info.slot\n // Unknown parent manifests impose no allow-list; validation owns that issue.\n slotRule = getManifest(info.parent.componentId)?.slots.find((s) => s.name === info.slot)\n index = info.index + (location.position === 'after' ? 1 : 0)\n }\n\n const refusal = placementRefusal({ root, parentId, slot: slotRule, fieldAllow, componentId: subject.componentId })\n if (refusal === 'field') {\n return rejected(\n 'field-not-allowed',\n `The field's top-level vocabulary does not allow \"${subject.componentId}\".`,\n )\n }\n if (refusal === 'slot') {\n return rejected(\n 'not-allowed',\n `Slot \"${slotName}\" does not allow \"${subject.componentId}\".`,\n )\n }\n\n const target: InsertTarget = { parentId, slot: slotName, index }\n\n if (subject.kind === 'component') {\n return { eligible: true, kind: 'insert', target }\n }\n\n // Cycle prevention: a node can never land inside its own subtree.\n if (subject.nodeId === parentId || isDescendantOf(root, subject.nodeId, parentId)) {\n return rejected('own-subtree', 'A node cannot be dropped into its own subtree.')\n }\n\n // No-op detection: the final position (after same-slot removal compensation,\n // matching `moveNode`) equals the node's current position.\n const current = getParentInfo(root, subject.nodeId)\n if (current && current.parent.id === parentId && current.slot === slotName) {\n const finalIndex = index > current.index ? index - 1 : index\n if (finalIndex === current.index) {\n return rejected('no-op', 'Dropping here would not move the node.')\n }\n }\n\n return { eligible: true, kind: 'move', nodeId: subject.nodeId, target }\n}\n\n// ---------------------------------------------------------------------------\n// Wrap gesture (EC-162): drop beside → two-column Grid container\n// ---------------------------------------------------------------------------\n\n/**\n * The container a \"drop beside\" gesture creates. Canonical choice (EC-161\n * semantics): a `Grid` with two columns, **not** a horizontal `Stack` —\n * EC-161 gives `Grid` the canonical column vocabulary (`columns` is a\n * validated graph value the contextual inspector refines from 2 → n), while\n * a horizontal Stack expresses flow order, not column tracks. The values\n * mirror the Grid manifest defaults (`columns: 2`, `gap: '4'`), so the\n * gesture's graph is exactly what composing the same layout manually would\n * produce.\n */\nexport const WRAP_GRID_OPTIONS: GridContainerOptions = { columns: 2, gap: '4' }\n\n/** Eligibility for a `left`/`right` (wrap-beside) drop. */\nfunction resolveWrap(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n fieldAllow: readonly string[] | undefined\n subject: DragSubject\n reference: ComponentNode\n side: WrapSide\n}): DropResolution {\n const { root, getManifest, fieldAllow, subject, reference, side } = args\n\n // The Grid takes the reference's place in its parent slot — so the\n // reference needs a parent (the root is the page anchor, never wrapped)\n // and that slot's allow-list must accept a Grid. EC-554: beside a direct\n // child of the composition root the Grid lands AT the root, so the field's\n // top-level vocabulary must accept it too.\n const info = getParentInfo(root, reference.id)\n if (!info) {\n return rejected('root', 'The page root cannot be wrapped in a container.')\n }\n const parentSlotRule = getManifest(info.parent.componentId)?.slots.find(\n (s) => s.name === info.slot,\n )\n const refusal = placementRefusal({\n root,\n parentId: info.parent.id,\n slot: parentSlotRule,\n fieldAllow,\n componentId: GRID_COMPONENT_ID,\n })\n if (refusal === 'field') {\n return rejected('field-not-allowed', \"The field's top-level vocabulary does not allow a Grid container.\")\n }\n if (refusal === 'slot') {\n return rejected('not-allowed', `Slot \"${info.slot}\" does not allow a Grid container.`)\n }\n\n // Both columns land in the Grid's children slot.\n const gridSlot = primaryInsertSlot(getManifest(GRID_COMPONENT_ID))\n for (const componentId of [reference.componentId, subject.componentId]) {\n if (!slotAllows(gridSlot, componentId)) {\n return rejected('not-allowed', `A Grid container does not allow \"${componentId}\".`)\n }\n }\n\n if (subject.kind === 'component') {\n return { eligible: true, kind: 'wrap', referenceId: reference.id, side }\n }\n if (subject.nodeId === reference.id) {\n return rejected('no-op', 'A node cannot be placed beside itself.')\n }\n // The reference must not live inside the dragged subtree (the subject is\n // lifted out first — taking the reference with it would orphan the wrap).\n if (isDescendantOf(root, subject.nodeId, reference.id)) {\n return rejected('own-subtree', 'A node cannot wrap an element of its own subtree.')\n }\n return { eligible: true, kind: 'wrap', referenceId: reference.id, side, nodeId: subject.nodeId }\n}\n\n/** What lands beside the reference: an existing node (moved) or a built subtree (inserted). */\nexport type WrapPartner =\n | { kind: 'node'; nodeId: string }\n | { kind: 'subtree'; node: ComponentNode }\n\nexport interface WrapResult {\n root: ComponentNode\n /** The id of the created Grid container. */\n containerId: string\n}\n\n/**\n * Execute a wrap plan as **one** structural edit: lift the partner out of the\n * tree (when it is an existing node) and replace the reference with\n * `gridContainerNode(containerId, WRAP_GRID_OPTIONS, [left, right])` in a\n * single pass. The caller commits the result as one builder action → one\n * operations command → one undo step; EC-161 normalization then runs on the\n * *committed* tree, so the two-child container can never be observed (or\n * persisted) in a half-built state. Returns `null` for structurally invalid\n * input (missing nodes, root reference, reference inside the partner).\n *\n * Determinism (EC-162): the produced container subtree is exactly the\n * `gridContainerNode` constructor output — identical to manual composition.\n */\nexport function applyWrap(args: {\n root: ComponentNode\n referenceId: string\n side: WrapSide\n partner: WrapPartner\n containerId: string\n}): WrapResult | null {\n const { root, referenceId, side, partner, containerId } = args\n if (referenceId === root.id || !getNode(root, referenceId)) return null\n\n let working = root\n let partnerNode: ComponentNode\n if (partner.kind === 'node') {\n if (partner.nodeId === referenceId || partner.nodeId === root.id) return null\n const node = getNode(root, partner.nodeId)\n if (!node || isDescendantOf(root, partner.nodeId, referenceId)) return null\n partnerNode = node\n working = removeNode(root, partner.nodeId)\n } else {\n partnerNode = partner.node\n }\n\n const next = updateNode(working, referenceId, (reference) =>\n gridContainerNode(\n containerId,\n WRAP_GRID_OPTIONS,\n side === 'left' ? [partnerNode, reference] : [reference, partnerNode],\n ),\n )\n return { root: next, containerId }\n}\n\n// ---------------------------------------------------------------------------\n// Pointer → position bands (shared hit-testing model)\n// ---------------------------------------------------------------------------\n\n/** The axis along which a container lays out its children. */\nexport type FlowAxis = 'vertical' | 'horizontal'\n\nfunction staticPropValue(node: ComponentNode, name: string): unknown {\n const prop = node.props?.[name]\n return isStaticProp(prop) ? prop.value : undefined\n}\n\n/**\n * The flow axis of a container — the axis its children stack along, and\n * therefore the hit-band axis for drops over those children (EC-162 ledger\n * fix: bands follow the parent's flow direction, on both surfaces). Mirrors\n * the renderer: a horizontal Stack flows horizontally; a Grid flows\n * horizontally when it actually has ≥ 2 column tracks (an absent/invalid\n * `columns` renders one column). Everything else is block flow (vertical).\n */\nexport function containerFlowAxis(node: ComponentNode | undefined): FlowAxis {\n if (!node) return 'vertical'\n if (node.componentId === STACK_COMPONENT_ID) {\n return staticPropValue(node, 'direction') === 'horizontal' ? 'horizontal' : 'vertical'\n }\n if (node.componentId === GRID_COMPONENT_ID) {\n const columns = staticPropValue(node, 'columns')\n return typeof columns === 'number' && columns >= 2 ? 'horizontal' : 'vertical'\n }\n return 'vertical'\n}\n\n/** Cross-axis side-band width (fraction of the box) that triggers the wrap gesture. */\nconst WRAP_BAND = 0.18\n\n/**\n * Map a 2D pointer offset within a drop target's box onto a `DropPosition`\n * (EC-162). `flow` is the flow axis of the target's *parent* — before/after\n * bands run along it. On vertically flowing targets, narrow side bands on the\n * cross axis produce `left`/`right` (the wrap-into-columns gesture, when\n * `wrappable`); children of horizontal containers get left/right bands for\n * plain reorder instead, so \"beside\" already means before/after there.\n */\nexport function dropPositionForPoint(args: {\n /** Pointer offset across the target's width, 0..1. */\n x: number\n /** Pointer offset across the target's height, 0..1. */\n y: number\n /** Flow axis of the target's parent (the axis siblings stack along). */\n flow: FlowAxis\n nestable: boolean\n edges: boolean\n /** Enable the wrap side-bands (the gesture surface opts in). */\n wrappable?: boolean\n}): DropPosition {\n const { x, y, flow, nestable, edges, wrappable = false } = args\n if (!edges) return 'into'\n if (wrappable && flow === 'vertical') {\n if (x < WRAP_BAND) return 'left'\n if (x > 1 - WRAP_BAND) return 'right'\n }\n return dropPositionForPointer({ ratio: flow === 'horizontal' ? x : y, nestable, edges })\n}\n\n/**\n * Map a pointer offset within a drop target's box onto a `DropPosition`.\n * `ratio` is the pointer's offset along the target's main axis, 0..1 — the\n * caller picks the axis (the parent's flow axis; see `containerFlowAxis`).\n *\n * - nestable targets with siblings: edge quarters insert before/after, the\n * middle half nests into the slot\n * - leaf targets: top/bottom halves insert before/after\n * - targets without siblings (the root): always `into`\n */\nexport function dropPositionForPointer(args: {\n ratio: number\n /** Whether `into` is structurally possible (target declares an insert slot). */\n nestable: boolean\n /** Whether before/after exist (target has a parent slot). */\n edges: boolean\n}): DropPosition {\n const { ratio, nestable, edges } = args\n if (!edges) return 'into'\n if (nestable) {\n if (ratio < 0.25) return 'before'\n if (ratio > 0.75) return 'after'\n return 'into'\n }\n return ratio < 0.5 ? 'before' : 'after'\n}\n\n// ---------------------------------------------------------------------------\n// Indicator state (shared visual contract)\n// ---------------------------------------------------------------------------\n\n/**\n * The visual state a surface shows for the current resolution. `blocked` is\n * the explicit rejection state (never a silent failed drop); `null` means show\n * nothing (a no-op drop is inert, not an error).\n */\nexport type DropIndicator = DropPosition | 'blocked' | null\n\nexport function dropIndicatorFor(\n resolution: DropResolution,\n position: DropPosition,\n): DropIndicator {\n if (resolution.eligible) return position\n return resolution.code === 'no-op' ? null : 'blocked'\n}\n"]}
1
+ {"version":3,"file":"drop-rules.js","sourceRoot":"","sources":["../../../src/lib/dnd/drop-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_SLOT,\n OUTLET_SLOT,\n getNode,\n getParentInfo,\n gridContainerNode,\n isDescendantOf,\n isStaticProp,\n layoutContainerKind,\n removeNode,\n updateNode,\n} from '@elytracms/core/project-graph'\nimport type {\n ComponentNode,\n GridContainerOptions,\n InsertTarget,\n LayoutContainerIds,\n} from '@elytracms/core/project-graph'\nimport { manifestSlots } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\n\n/**\n * Drag & drop eligibility rules (EC-160 + EC-162, vision §3.1) — pure\n * graph-level functions with **no DOM or UI dependencies**. The canvas and the\n * page tree both resolve drops through `resolveDrop`, so the two surfaces can\n * never disagree about what a manifest slot allow-list permits, what the\n * composition field's top-level vocabulary permits at the root (EC-554), which\n * children a component accepts, or whether a move would create a cycle.\n *\n * The drop-zone model is position-based: `into` a slot, `before`/`after` a\n * sibling along the parent's flow axis, and the EC-162 gesture positions\n * `left`/`right` — dropping *beside* an element wraps both in a two-column\n * `Grid` container (see `applyWrap`) — the component that fills the\n * `layout.grid` role (EC-591); with no such component the gesture is off.\n */\n\n// ---------------------------------------------------------------------------\n// Inputs\n// ---------------------------------------------------------------------------\n\n/**\n * The manifest slice the rules evaluate. Structural (not the full\n * `ComponentManifest`) so tests and future callers can feed minimal lookups.\n */\nexport interface SlotRule {\n name: string\n /** Restrict which component ids may be inserted (omit to allow any). */\n allow?: string[]\n}\n\nexport interface ManifestRules {\n slots: SlotRule[]\n}\n\n/** Resolve a component id to its manifest slot rules (undefined = unknown component). */\nexport type ManifestLookup = (componentId: string) => ManifestRules | undefined\n\n/**\n * The canonical `ManifestRules` view of a full manifest (EC-386): the declared\n * `slots` merged with the blocks-prop-derived slots (`manifestSlots`, EC-190\n * \"slots are blocks props\" — a `blocks`-type prop IS a named child region whose\n * subtrees live in `node.slots[propName]`, carrying the prop's `allow` leash).\n * Every surface that feeds a real `ComponentManifest` into a {@link ManifestLookup}\n * or {@link primaryInsertSlot} must adapt through this — resolving against raw\n * `manifest.slots` silently degrades a `project.Section.body`-style region to a\n * sibling insert. One derivation, so tree insert, DnD, paste, and the canvas\n * affordances can never disagree about which regions exist or what they allow.\n */\nexport function manifestRules(manifest: ComponentManifest | undefined): ManifestRules | undefined {\n return manifest ? { slots: manifestSlots(manifest) } : undefined\n}\n\n/** What is being dragged. */\nexport type DragSubject =\n /** An existing node of the current page — a completed drop is a `move`. */\n | { kind: 'node'; nodeId: string; componentId: string }\n /** A new subtree (blocks-panel tile) — a completed drop is an `insert`. */\n | { kind: 'component'; componentId: string }\n\n/**\n * Where, relative to a reference node, the drop would land. `into`, `before`\n * and `after` are the EC-160 positions; `left`/`right` are the EC-162 gesture\n * positions — \"beside\" the reference, wrapping both elements in a two-column\n * `Grid` container.\n */\nexport type DropPosition = 'into' | 'before' | 'after' | 'left' | 'right'\n\n/** The side of the reference an EC-162 wrap gesture drops on. */\nexport type WrapSide = 'left' | 'right'\n\nexport interface DropLocation {\n /** The reference node id (the hovered node/row). */\n nodeId: string\n position: DropPosition\n}\n\n// ---------------------------------------------------------------------------\n// Outcomes\n// ---------------------------------------------------------------------------\n\nexport type DropRejectionCode =\n /** The reference node does not exist in the tree. */\n | 'missing-target'\n /** `into` on a component that declares no insertable slot. */\n | 'no-slot'\n /** The target slot's manifest allow-list rejects the dragged component. */\n | 'not-allowed'\n /**\n * EC-554: the composition FIELD's top-level `allow` rejects the component\n * landing as a direct child of the composition root (see {@link fieldAllowAt}).\n */\n | 'field-not-allowed'\n /** Moving a node into itself or its own subtree (cycle). */\n | 'own-subtree'\n /** The page root cannot be moved, and nothing can drop beside it. */\n | 'root'\n /** Structurally valid but changes nothing (dropping a node onto its own position). */\n | 'no-op'\n /**\n * EC-591: the wrap-beside gesture needs the component that fills the\n * `layout.grid` role, and the project's component list has none — the gesture\n * is off (the canvas's role notice says so), never a hardcoded fallback id.\n */\n | 'no-grid'\n\nexport interface DropRejection {\n eligible: false\n code: DropRejectionCode\n reason: string\n}\n\n/** An eligible drop: exactly one builder action (move, insert, or wrap) commits it. */\nexport type DropPlan =\n | { eligible: true; kind: 'move'; nodeId: string; target: InsertTarget }\n | { eligible: true; kind: 'insert'; target: InsertTarget }\n /**\n * EC-162 wrap gesture: the reference node and the dragged subject become the\n * two columns of a new `Grid` container, created atomically (`applyWrap`).\n * `nodeId` is present when the subject is an existing node (a move-wrap);\n * absent for blocks-panel components (an insert-wrap via `buildNode`).\n */\n | { eligible: true; kind: 'wrap'; referenceId: string; side: WrapSide; nodeId?: string }\n\nexport type DropResolution = DropPlan | DropRejection\n\nfunction rejected(code: DropRejectionCode, reason: string): DropRejection {\n return { eligible: false, code, reason }\n}\n\n// ---------------------------------------------------------------------------\n// Slot rules\n// ---------------------------------------------------------------------------\n\n/**\n * The slot an `into` drop inserts into: the default `children` slot when\n * declared, else the first authored slot that is not the layout `outlet`.\n * Mirrors the shell's insert-context resolution so click-insert and\n * drag-insert land in the same place.\n */\nexport function primaryInsertSlot(manifest: ManifestRules | undefined): SlotRule | undefined {\n if (!manifest) return undefined\n return (\n manifest.slots.find((s) => s.name === DEFAULT_SLOT) ??\n manifest.slots.find((s) => s.name !== OUTLET_SLOT)\n )\n}\n\n/** True when a component can receive `into` drops at all (declares an insertable slot). */\nexport function canNestInto(getManifest: ManifestLookup, componentId: string): boolean {\n return primaryInsertSlot(getManifest(componentId)) !== undefined\n}\n\n/**\n * True when an allow-list permits the component id — absent = any, a present\n * list (even an empty one) is a closed set (EC-544). The one membership rule the\n * slot leash and the field leash share.\n */\nfunction allowListPermits(allow: readonly string[] | undefined, componentId: string): boolean {\n return !allow || allow.includes(componentId)\n}\n\n/** True when a slot's allow-list (if any) permits the component id. */\nexport function slotAllows(slot: SlotRule | undefined, componentId: string): boolean {\n return allowListPermits(slot?.allow, componentId)\n}\n\n/**\n * The composition FIELD's top-level vocabulary as it applies to a placement under\n * `parentId` (EC-188, EC-554) — the ONE reader of a field's `allow` on the studio\n * side: every insertion surface (the insert offer in `insert-context`, drag-and-drop\n * on the canvas and the page tree incl. the wrap gesture, paste) asks here, so no\n * surface can apply the field leash differently or not at all.\n *\n * A field `allow` governs only the value's TOP level — a direct child of the\n * composition root, mirroring `validateCompositionValue` — so this returns the\n * field vocabulary when `parentId` IS the root node and `undefined` (no field\n * constraint) anywhere deeper. Keyed on node IDENTITY, never on the component id:\n * the composition editor's synthetic root is the `layout.stack` component, which\n * is also a nestable block, and a rule keyed on the component id would leash\n * every nested Stack. EC-544: an omitted `allow` is `undefined` here (any registered\n * component); a declared list is a closed set.\n */\nexport function fieldAllowAt(\n root: ComponentNode,\n parentId: string,\n fieldAllow: readonly string[] | undefined,\n): readonly string[] | undefined {\n return parentId === root.id ? fieldAllow : undefined\n}\n\n/**\n * Which declared leash refuses `componentId` landing under `parentId` in `slot`\n * (EC-545 slot leash, EC-554 field leash) — `null` when both admit it. The field\n * is judged first: it can only refuse at the composition root, where the slot is\n * the invisible synthetic root's and the field is the declaration the author sees.\n */\nexport function placementRefusal(args: {\n root: ComponentNode\n /** The node the placement lands under (the insertion target's `parentId`). */\n parentId: string\n slot: SlotRule | undefined\n fieldAllow: readonly string[] | undefined\n componentId: string\n}): 'field' | 'slot' | null {\n const { root, parentId, slot, fieldAllow, componentId } = args\n if (!allowListPermits(fieldAllowAt(root, parentId, fieldAllow), componentId)) return 'field'\n if (!slotAllows(slot, componentId)) return 'slot'\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a prospective drop into either an executable plan (one `move` or\n * `insert` builder action → one operations command → one undo step) or a\n * structured rejection the UI shows *during* the drag. Pure: same inputs,\n * same answer, on both surfaces.\n */\nexport function resolveDrop(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n /**\n * The components filling the layout roles (EC-591, `ComponentRegistry.layout`) —\n * the wrap gesture builds the `layout.grid` one, and is OFF when nobody fills it.\n * Required so no surface can forget it.\n */\n layout: LayoutContainerIds\n /**\n * The composition field's top-level `allow` (EC-554) — `undefined` when the\n * field declares none. Required so no surface can forget to pass it; judged\n * through {@link fieldAllowAt} for drops whose parent is the root.\n */\n fieldAllow: readonly string[] | undefined\n subject: DragSubject\n location: DropLocation\n}): DropResolution {\n const { root, getManifest, layout, fieldAllow, subject, location } = args\n\n if (subject.kind === 'node' && subject.nodeId === root.id) {\n return rejected('root', 'The page root cannot be moved.')\n }\n\n const reference = getNode(root, location.nodeId)\n if (!reference) {\n return rejected('missing-target', `Node \"${location.nodeId}\" is not on this page.`)\n }\n\n // EC-162: dropping beside the reference wraps both in a two-column Grid.\n if (location.position === 'left' || location.position === 'right') {\n return resolveWrap({ root, getManifest, layout, fieldAllow, subject, reference, side: location.position })\n }\n\n // Resolve the concrete insertion point: parent node, slot, index.\n let parentId: string\n let slotRule: SlotRule | undefined\n let slotName: string\n let index: number\n\n if (location.position === 'into') {\n const manifest = getManifest(reference.componentId)\n const slot = primaryInsertSlot(manifest)\n if (!slot) {\n return rejected('no-slot', `\"${reference.componentId}\" does not accept children.`)\n }\n parentId = reference.id\n slotRule = slot\n slotName = slot.name\n index = (reference.slots?.[slot.name] ?? []).length\n } else {\n const info = getParentInfo(root, reference.id)\n if (!info) {\n return rejected('root', 'Nothing can be dropped beside the page root.')\n }\n parentId = info.parent.id\n slotName = info.slot\n // Unknown parent manifests impose no allow-list; validation owns that issue.\n slotRule = getManifest(info.parent.componentId)?.slots.find((s) => s.name === info.slot)\n index = info.index + (location.position === 'after' ? 1 : 0)\n }\n\n const refusal = placementRefusal({ root, parentId, slot: slotRule, fieldAllow, componentId: subject.componentId })\n if (refusal === 'field') {\n return rejected(\n 'field-not-allowed',\n `The field's top-level vocabulary does not allow \"${subject.componentId}\".`,\n )\n }\n if (refusal === 'slot') {\n return rejected(\n 'not-allowed',\n `Slot \"${slotName}\" does not allow \"${subject.componentId}\".`,\n )\n }\n\n const target: InsertTarget = { parentId, slot: slotName, index }\n\n if (subject.kind === 'component') {\n return { eligible: true, kind: 'insert', target }\n }\n\n // Cycle prevention: a node can never land inside its own subtree.\n if (subject.nodeId === parentId || isDescendantOf(root, subject.nodeId, parentId)) {\n return rejected('own-subtree', 'A node cannot be dropped into its own subtree.')\n }\n\n // No-op detection: the final position (after same-slot removal compensation,\n // matching `moveNode`) equals the node's current position.\n const current = getParentInfo(root, subject.nodeId)\n if (current && current.parent.id === parentId && current.slot === slotName) {\n const finalIndex = index > current.index ? index - 1 : index\n if (finalIndex === current.index) {\n return rejected('no-op', 'Dropping here would not move the node.')\n }\n }\n\n return { eligible: true, kind: 'move', nodeId: subject.nodeId, target }\n}\n\n// ---------------------------------------------------------------------------\n// Wrap gesture (EC-162): drop beside → two-column Grid container\n// ---------------------------------------------------------------------------\n\n/**\n * The container a \"drop beside\" gesture creates. Canonical choice (EC-161\n * semantics): a `Grid` with two columns, **not** a horizontal `Stack` —\n * EC-161 gives `Grid` the canonical column vocabulary (`columns` is a\n * validated graph value the contextual inspector refines from 2 → n), while\n * a horizontal Stack expresses flow order, not column tracks. The values\n * mirror the Grid manifest defaults (`columns: 2`, `gap: '4'`), so the\n * gesture's graph is exactly what composing the same layout manually would\n * produce.\n */\nexport const WRAP_GRID_OPTIONS: GridContainerOptions = { columns: 2, gap: '4' }\n\n/** Eligibility for a `left`/`right` (wrap-beside) drop. */\nfunction resolveWrap(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n layout: LayoutContainerIds\n fieldAllow: readonly string[] | undefined\n subject: DragSubject\n reference: ComponentNode\n side: WrapSide\n}): DropResolution {\n const { root, getManifest, layout, fieldAllow, subject, reference, side } = args\n\n // EC-591: the gesture builds the `layout.grid` component — a role nobody fills\n // switches it off (the canvas shows the role notice), never a hidden default id.\n const gridId = layout.grid\n if (gridId === undefined) {\n return rejected('no-grid', 'No component fills the layout.grid role — side-by-side columns are unavailable.')\n }\n\n // The Grid takes the reference's place in its parent slot — so the\n // reference needs a parent (the root is the page anchor, never wrapped)\n // and that slot's allow-list must accept a Grid. EC-554: beside a direct\n // child of the composition root the Grid lands AT the root, so the field's\n // top-level vocabulary must accept it too.\n const info = getParentInfo(root, reference.id)\n if (!info) {\n return rejected('root', 'The page root cannot be wrapped in a container.')\n }\n const parentSlotRule = getManifest(info.parent.componentId)?.slots.find(\n (s) => s.name === info.slot,\n )\n const refusal = placementRefusal({\n root,\n parentId: info.parent.id,\n slot: parentSlotRule,\n fieldAllow,\n componentId: gridId,\n })\n if (refusal === 'field') {\n return rejected('field-not-allowed', \"The field's top-level vocabulary does not allow a Grid container.\")\n }\n if (refusal === 'slot') {\n return rejected('not-allowed', `Slot \"${info.slot}\" does not allow a Grid container.`)\n }\n\n // Both columns land in the Grid's children slot.\n const gridSlot = primaryInsertSlot(getManifest(gridId))\n for (const componentId of [reference.componentId, subject.componentId]) {\n if (!slotAllows(gridSlot, componentId)) {\n return rejected('not-allowed', `A Grid container does not allow \"${componentId}\".`)\n }\n }\n\n if (subject.kind === 'component') {\n return { eligible: true, kind: 'wrap', referenceId: reference.id, side }\n }\n if (subject.nodeId === reference.id) {\n return rejected('no-op', 'A node cannot be placed beside itself.')\n }\n // The reference must not live inside the dragged subtree (the subject is\n // lifted out first — taking the reference with it would orphan the wrap).\n if (isDescendantOf(root, subject.nodeId, reference.id)) {\n return rejected('own-subtree', 'A node cannot wrap an element of its own subtree.')\n }\n return { eligible: true, kind: 'wrap', referenceId: reference.id, side, nodeId: subject.nodeId }\n}\n\n/** What lands beside the reference: an existing node (moved) or a built subtree (inserted). */\nexport type WrapPartner =\n | { kind: 'node'; nodeId: string }\n | { kind: 'subtree'; node: ComponentNode }\n\nexport interface WrapResult {\n root: ComponentNode\n /** The id of the created Grid container. */\n containerId: string\n}\n\n/**\n * Execute a wrap plan as **one** structural edit: lift the partner out of the\n * tree (when it is an existing node) and replace the reference with\n * `gridContainerNode(containerId, WRAP_GRID_OPTIONS, [left, right])` in a\n * single pass. The caller commits the result as one builder action → one\n * operations command → one undo step; EC-161 normalization then runs on the\n * *committed* tree, so the two-child container can never be observed (or\n * persisted) in a half-built state. Returns `null` for structurally invalid\n * input (missing nodes, root reference, reference inside the partner).\n *\n * Determinism (EC-162): the produced container subtree is exactly the\n * `gridContainerNode` constructor output — identical to manual composition.\n */\nexport function applyWrap(args: {\n root: ComponentNode\n /** The `layout.grid` component's id (EC-591) — the container the gesture builds. */\n gridComponentId: string\n referenceId: string\n side: WrapSide\n partner: WrapPartner\n containerId: string\n}): WrapResult | null {\n const { root, gridComponentId, referenceId, side, partner, containerId } = args\n if (referenceId === root.id || !getNode(root, referenceId)) return null\n\n let working = root\n let partnerNode: ComponentNode\n if (partner.kind === 'node') {\n if (partner.nodeId === referenceId || partner.nodeId === root.id) return null\n const node = getNode(root, partner.nodeId)\n if (!node || isDescendantOf(root, partner.nodeId, referenceId)) return null\n partnerNode = node\n working = removeNode(root, partner.nodeId)\n } else {\n partnerNode = partner.node\n }\n\n const next = updateNode(working, referenceId, (reference) =>\n gridContainerNode(\n gridComponentId,\n containerId,\n WRAP_GRID_OPTIONS,\n side === 'left' ? [partnerNode, reference] : [reference, partnerNode],\n ),\n )\n return { root: next, containerId }\n}\n\n// ---------------------------------------------------------------------------\n// Pointer → position bands (shared hit-testing model)\n// ---------------------------------------------------------------------------\n\n/** The axis along which a container lays out its children. */\nexport type FlowAxis = 'vertical' | 'horizontal'\n\nfunction staticPropValue(node: ComponentNode, name: string): unknown {\n const prop = node.props?.[name]\n return isStaticProp(prop) ? prop.value : undefined\n}\n\n/**\n * The flow axis of a container — the axis its children stack along, and\n * therefore the hit-band axis for drops over those children (EC-162 ledger\n * fix: bands follow the parent's flow direction, on both surfaces). Mirrors\n * the renderer: a horizontal Stack flows horizontally; a Grid flows\n * horizontally when it actually has ≥ 2 column tracks (an absent/invalid\n * `columns` renders one column). Everything else is block flow (vertical).\n */\nexport function containerFlowAxis(node: ComponentNode | undefined, layout: LayoutContainerIds): FlowAxis {\n if (!node) return 'vertical'\n const kind = layoutContainerKind(node.componentId, layout)\n if (kind === 'stack') {\n return staticPropValue(node, 'direction') === 'horizontal' ? 'horizontal' : 'vertical'\n }\n if (kind === 'grid') {\n const columns = staticPropValue(node, 'columns')\n return typeof columns === 'number' && columns >= 2 ? 'horizontal' : 'vertical'\n }\n return 'vertical'\n}\n\n/** Cross-axis side-band width (fraction of the box) that triggers the wrap gesture. */\nconst WRAP_BAND = 0.18\n\n/**\n * Map a 2D pointer offset within a drop target's box onto a `DropPosition`\n * (EC-162). `flow` is the flow axis of the target's *parent* — before/after\n * bands run along it. On vertically flowing targets, narrow side bands on the\n * cross axis produce `left`/`right` (the wrap-into-columns gesture, when\n * `wrappable`); children of horizontal containers get left/right bands for\n * plain reorder instead, so \"beside\" already means before/after there.\n */\nexport function dropPositionForPoint(args: {\n /** Pointer offset across the target's width, 0..1. */\n x: number\n /** Pointer offset across the target's height, 0..1. */\n y: number\n /** Flow axis of the target's parent (the axis siblings stack along). */\n flow: FlowAxis\n nestable: boolean\n edges: boolean\n /** Enable the wrap side-bands (the gesture surface opts in). */\n wrappable?: boolean\n}): DropPosition {\n const { x, y, flow, nestable, edges, wrappable = false } = args\n if (!edges) return 'into'\n if (wrappable && flow === 'vertical') {\n if (x < WRAP_BAND) return 'left'\n if (x > 1 - WRAP_BAND) return 'right'\n }\n return dropPositionForPointer({ ratio: flow === 'horizontal' ? x : y, nestable, edges })\n}\n\n/**\n * Map a pointer offset within a drop target's box onto a `DropPosition`.\n * `ratio` is the pointer's offset along the target's main axis, 0..1 — the\n * caller picks the axis (the parent's flow axis; see `containerFlowAxis`).\n *\n * - nestable targets with siblings: edge quarters insert before/after, the\n * middle half nests into the slot\n * - leaf targets: top/bottom halves insert before/after\n * - targets without siblings (the root): always `into`\n */\nexport function dropPositionForPointer(args: {\n ratio: number\n /** Whether `into` is structurally possible (target declares an insert slot). */\n nestable: boolean\n /** Whether before/after exist (target has a parent slot). */\n edges: boolean\n}): DropPosition {\n const { ratio, nestable, edges } = args\n if (!edges) return 'into'\n if (nestable) {\n if (ratio < 0.25) return 'before'\n if (ratio > 0.75) return 'after'\n return 'into'\n }\n return ratio < 0.5 ? 'before' : 'after'\n}\n\n// ---------------------------------------------------------------------------\n// Indicator state (shared visual contract)\n// ---------------------------------------------------------------------------\n\n/**\n * The visual state a surface shows for the current resolution. `blocked` is\n * the explicit rejection state (never a silent failed drop); `null` means show\n * nothing (a no-op drop is inert, not an error).\n */\nexport type DropIndicator = DropPosition | 'blocked' | null\n\nexport function dropIndicatorFor(\n resolution: DropResolution,\n position: DropPosition,\n): DropIndicator {\n if (resolution.eligible) return position\n return resolution.code === 'no-op' ? null : 'blocked'\n}\n"]}
@@ -13,4 +13,4 @@
13
13
  * (The convex-side stamp does NOT read this mirror — `convex/delivery.ts` imports the
14
14
  * package.json declaration directly; its bundler allows what tsc's rootDir forbids.)
15
15
  */
16
- export declare const ENGINE_VERSION = "0.0.90";
16
+ export declare const ENGINE_VERSION = "0.0.91";
@@ -13,5 +13,5 @@
13
13
  * (The convex-side stamp does NOT read this mirror — `convex/delivery.ts` imports the
14
14
  * package.json declaration directly; its bundler allows what tsc's rootDir forbids.)
15
15
  */
16
- export const ENGINE_VERSION = '0.0.90';
16
+ export const ENGINE_VERSION = '0.0.91';
17
17
  //# sourceMappingURL=engine-version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../../src/lib/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAA","sourcesContent":["/**\n * The engine version this studio build was compiled from (EC-552) — the client-side\n * half of the deployed-functions version handshake: the boot gate compares it against\n * what `delivery:engineVersion` answers from the deployment.\n *\n * A MIRROR of `package.json`'s `version`, not the declaration itself: client source\n * cannot import `../../package.json` (outside the tsc build's `rootDir`; moving the\n * root would reshape `dist` and every export path). The parity test beside this file\n * fs-reads the real declaration and fails `pnpm verify` the moment the two diverge,\n * so a release cannot ship with a stale mirror. `pnpm release:bump` writes this\n * alongside the five `package.json` versions (EC-608).\n *\n * (The convex-side stamp does NOT read this mirror — `convex/delivery.ts` imports the\n * package.json declaration directly; its bundler allows what tsc's rootDir forbids.)\n */\nexport const ENGINE_VERSION = '0.0.90'\n"]}
1
+ {"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../../src/lib/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAA","sourcesContent":["/**\n * The engine version this studio build was compiled from (EC-552) — the client-side\n * half of the deployed-functions version handshake: the boot gate compares it against\n * what `delivery:engineVersion` answers from the deployment.\n *\n * A MIRROR of `package.json`'s `version`, not the declaration itself: client source\n * cannot import `../../package.json` (outside the tsc build's `rootDir`; moving the\n * root would reshape `dist` and every export path). The parity test beside this file\n * fs-reads the real declaration and fails `pnpm verify` the moment the two diverge,\n * so a release cannot ship with a stale mirror. `pnpm release:bump` writes this\n * alongside the five `package.json` versions (EC-608).\n *\n * (The convex-side stamp does NOT read this mirror — `convex/delivery.ts` imports the\n * package.json declaration directly; its bundler allows what tsc's rootDir forbids.)\n */\nexport const ENGINE_VERSION = '0.0.91'\n"]}