@elytracms/studio 0.0.63 → 0.0.65

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 (146) hide show
  1. package/convex/assets.ts +11 -0
  2. package/convex/delivery.ts +5 -0
  3. package/convex/schema.ts +23 -0
  4. package/dist/components/assets/asset-thumb.d.ts +4 -2
  5. package/dist/components/assets/asset-thumb.js +26 -4
  6. package/dist/components/assets/asset-thumb.js.map +1 -1
  7. package/dist/components/assets/assets-module.js +48 -2
  8. package/dist/components/assets/assets-module.js.map +1 -1
  9. package/dist/components/assets/assets-store.d.ts +11 -1
  10. package/dist/components/assets/assets-store.js +18 -1
  11. package/dist/components/assets/assets-store.js.map +1 -1
  12. package/dist/components/builder/blocks-model.d.ts +11 -0
  13. package/dist/components/builder/blocks-model.js +26 -8
  14. package/dist/components/builder/blocks-model.js.map +1 -1
  15. package/dist/components/builder/canvas-chrome-css.d.ts +54 -0
  16. package/dist/components/builder/canvas-chrome-css.js +246 -0
  17. package/dist/components/builder/canvas-chrome-css.js.map +1 -0
  18. package/dist/components/builder/canvas-chrome.d.ts +122 -18
  19. package/dist/components/builder/canvas-chrome.js +148 -82
  20. package/dist/components/builder/canvas-chrome.js.map +1 -1
  21. package/dist/components/builder/canvas-inline-editing.d.ts +7 -0
  22. package/dist/components/builder/canvas-inline-editing.js +3 -1
  23. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  24. package/dist/components/builder/canvas-overlay-geometry.d.ts +72 -0
  25. package/dist/components/builder/canvas-overlay-geometry.js +80 -0
  26. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -0
  27. package/dist/components/builder/canvas-rect-tracking.d.ts +124 -0
  28. package/dist/components/builder/canvas-rect-tracking.js +198 -0
  29. package/dist/components/builder/canvas-rect-tracking.js.map +1 -0
  30. package/dist/components/builder/canvas-render-context.d.ts +13 -0
  31. package/dist/components/builder/canvas-render-context.js +7 -5
  32. package/dist/components/builder/canvas-render-context.js.map +1 -1
  33. package/dist/components/builder/canvas-stylesheet.d.ts +1 -1
  34. package/dist/components/builder/canvas-stylesheet.js +16 -9
  35. package/dist/components/builder/canvas-stylesheet.js.map +1 -1
  36. package/dist/components/builder/canvas-theme-notice.d.ts +6 -0
  37. package/dist/components/builder/canvas-theme-notice.js +46 -0
  38. package/dist/components/builder/canvas-theme-notice.js.map +1 -0
  39. package/dist/components/builder/canvas.js +78 -22
  40. package/dist/components/builder/canvas.js.map +1 -1
  41. package/dist/components/builder/composition-field-editor.js +22 -3
  42. package/dist/components/builder/composition-field-editor.js.map +1 -1
  43. package/dist/components/builder/delete-key.d.ts +38 -0
  44. package/dist/components/builder/delete-key.js +46 -0
  45. package/dist/components/builder/delete-key.js.map +1 -0
  46. package/dist/components/builder/drag-session.d.ts +8 -0
  47. package/dist/components/builder/drag-session.js +15 -0
  48. package/dist/components/builder/drag-session.js.map +1 -1
  49. package/dist/components/builder/insert-affordance-context.d.ts +19 -0
  50. package/dist/components/builder/insert-affordance-context.js +7 -0
  51. package/dist/components/builder/insert-affordance-context.js.map +1 -0
  52. package/dist/components/builder/insert-context.d.ts +40 -0
  53. package/dist/components/builder/insert-context.js +86 -3
  54. package/dist/components/builder/insert-context.js.map +1 -1
  55. package/dist/components/builder/node-title.d.ts +60 -0
  56. package/dist/components/builder/node-title.js +135 -0
  57. package/dist/components/builder/node-title.js.map +1 -0
  58. package/dist/components/builder/page-tree.js +21 -3
  59. package/dist/components/builder/page-tree.js.map +1 -1
  60. package/dist/components/builder/prop-selection.d.ts +52 -0
  61. package/dist/components/builder/prop-selection.js +38 -0
  62. package/dist/components/builder/prop-selection.js.map +1 -0
  63. package/dist/components/builder/prop-sync-context.d.ts +49 -0
  64. package/dist/components/builder/prop-sync-context.js +23 -0
  65. package/dist/components/builder/prop-sync-context.js.map +1 -0
  66. package/dist/components/builder/property-inspector.js +23 -1
  67. package/dist/components/builder/property-inspector.js.map +1 -1
  68. package/dist/components/builder/reuse-context.js +1 -1
  69. package/dist/components/builder/reuse-context.js.map +1 -1
  70. package/dist/components/builder/rich-text-toolbar.d.ts +23 -0
  71. package/dist/components/builder/rich-text-toolbar.js +24 -17
  72. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  73. package/dist/components/builder/selection-overlay.d.ts +79 -0
  74. package/dist/components/builder/selection-overlay.js +244 -0
  75. package/dist/components/builder/selection-overlay.js.map +1 -0
  76. package/dist/components/builder/spacing-handles.d.ts +88 -1
  77. package/dist/components/builder/spacing-handles.js +191 -63
  78. package/dist/components/builder/spacing-handles.js.map +1 -1
  79. package/dist/components/cms/asset-picker.js +11 -5
  80. package/dist/components/cms/asset-picker.js.map +1 -1
  81. package/dist/components/cms/rich-text/block-style.d.ts +48 -0
  82. package/dist/components/cms/rich-text/block-style.js +30 -0
  83. package/dist/components/cms/rich-text/block-style.js.map +1 -0
  84. package/dist/components/cms/rich-text/embed-picker.js +4 -3
  85. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  86. package/dist/components/cms/rich-text/extensions.d.ts +2 -0
  87. package/dist/components/cms/rich-text/extensions.js +4 -0
  88. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  89. package/dist/components/cms/rich-text/font-size-control.d.ts +31 -0
  90. package/dist/components/cms/rich-text/font-size-control.js +40 -0
  91. package/dist/components/cms/rich-text/font-size-control.js.map +1 -0
  92. package/dist/components/cms/rich-text/font-size.d.ts +16 -0
  93. package/dist/components/cms/rich-text/font-size.js +40 -0
  94. package/dist/components/cms/rich-text/font-size.js.map +1 -0
  95. package/dist/components/cms/rich-text/toolbar-state.d.ts +3 -0
  96. package/dist/components/cms/rich-text/toolbar-state.js +3 -0
  97. package/dist/components/cms/rich-text/toolbar-state.js.map +1 -1
  98. package/dist/components/cms/rich-text/toolbar.d.ts +13 -0
  99. package/dist/components/cms/rich-text/toolbar.js +17 -5
  100. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  101. package/dist/components/components/component-detail.js +3 -3
  102. package/dist/components/components/component-detail.js.map +1 -1
  103. package/dist/components/components/component-preview.d.ts +2 -1
  104. package/dist/components/components/component-preview.js +25 -26
  105. package/dist/components/components/component-preview.js.map +1 -1
  106. package/dist/components/components/registry-browser.js +2 -2
  107. package/dist/components/components/registry-browser.js.map +1 -1
  108. package/dist/components/components/taxonomy.js +3 -2
  109. package/dist/components/components/taxonomy.js.map +1 -1
  110. package/dist/components/components/transfer-dialog.js +1 -1
  111. package/dist/components/components/transfer-dialog.js.map +1 -1
  112. package/dist/lib/assets/backfill-thumbs.d.ts +67 -0
  113. package/dist/lib/assets/backfill-thumbs.js +120 -0
  114. package/dist/lib/assets/backfill-thumbs.js.map +1 -0
  115. package/dist/lib/assets/delete-asset.d.ts +2 -1
  116. package/dist/lib/assets/delete-asset.js +12 -0
  117. package/dist/lib/assets/delete-asset.js.map +1 -1
  118. package/dist/lib/assets/index.d.ts +2 -0
  119. package/dist/lib/assets/index.js +2 -0
  120. package/dist/lib/assets/index.js.map +1 -1
  121. package/dist/lib/assets/thumbnail.d.ts +145 -0
  122. package/dist/lib/assets/thumbnail.js +220 -0
  123. package/dist/lib/assets/thumbnail.js.map +1 -0
  124. package/dist/lib/assets/upload.d.ts +9 -0
  125. package/dist/lib/assets/upload.js +31 -0
  126. package/dist/lib/assets/upload.js.map +1 -1
  127. package/dist/lib/i18n/messages.de.js +20 -0
  128. package/dist/lib/i18n/messages.de.js.map +1 -1
  129. package/dist/lib/i18n/messages.en.d.ts +18 -0
  130. package/dist/lib/i18n/messages.en.js +21 -0
  131. package/dist/lib/i18n/messages.en.js.map +1 -1
  132. package/dist/lib/persistence/backend-mode.d.ts +7 -0
  133. package/dist/lib/persistence/backend-mode.js +9 -0
  134. package/dist/lib/persistence/backend-mode.js.map +1 -1
  135. package/dist/lib/persistence/convex-adapter.js +4 -0
  136. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  137. package/dist/lib/persistence/index.d.ts +1 -1
  138. package/dist/lib/persistence/index.js +1 -1
  139. package/dist/lib/persistence/index.js.map +1 -1
  140. package/dist/lib/persona/labels.d.ts +9 -5
  141. package/dist/lib/persona/labels.js +13 -8
  142. package/dist/lib/persona/labels.js.map +1 -1
  143. package/dist/lib/references/references-store.js +7 -1
  144. package/dist/lib/references/references-store.js.map +1 -1
  145. package/package.json +2 -2
  146. package/src/styles/studio.css +13 -0
@@ -1 +1 @@
1
- {"version":3,"file":"blocks-model.js","sourceRoot":"","sources":["../../../src/components/builder/blocks-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { cloneWithNewIds, isLayoutContainerId } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry'\nimport { isCanvasEligible, propValueValidator } from '@elytracms/core/component-registry'\nimport { componentStatus } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { nodeFromProps, previewSpecs } from '@elytracms/studio/lib/registry/preview'\nimport { LAYOUT_PRESETS, nodeFromManifest, seededNodeFromManifest } from './node-factory'\n\n/**\n * Blocks panel model (EC-164) — the pure, React-free derivation behind the\n * visual insert surface. Every registry manifest yields one tile per declared\n * fixture (or a single default-props tile), grouped by manifest `category`\n * and filtered by a free-text query over name / description / category — no\n * hand-maintained panel config. Broken fixtures degrade to tiles carrying\n * explicit `issues`, never to a missing entry or a crash.\n */\n\n/** One insertable tile in the blocks panel. */\nexport interface BlockTile {\n /** Stable identity (`<componentId>::<variant>` or `preset::<key>`). */\n key: string\n /** Component id of the node the tile inserts. */\n componentId: string\n title: string\n /** Variation label: the fixture name, `Default`, or a preset description. */\n variant: string\n description?: string\n /**\n * Validation problems detected while building the preview (unknown child\n * component, undeclared slot, invalid fixture prop, missing\n * implementation). A non-empty list marks the tile as degraded — it still\n * renders and still inserts.\n */\n issues: string[]\n /** Deterministic-id subtree rendered live in the tile thumbnail. */\n previewNode: ComponentNode\n /**\n * Curated preview image URL (EC-409, `manifest.previewImage`). When set, the\n * tile renders this image instead of the live `previewNode` render — the\n * escape hatch for components whose real render needs data/context (e.g.\n * listings) and previews as \"just text\". Insertion is unaffected.\n */\n previewImage?: string\n /** Build the subtree to insert (ids are reassigned by the builder store). */\n build: () => ComponentNode\n}\n\n/** Tiles of one manifest `category`. */\nexport interface BlockGroup {\n category: string\n tiles: BlockTile[]\n}\n\nexport interface BlockFilter {\n /** Case-insensitive match over id, title, description, and category. */\n query?: string\n /** When set, only these component ids may be inserted (slot.allow). */\n slotAllow?: string[]\n}\n\n/**\n * Intersect two component-id allow-lists. `undefined` means \"no constraint from\n * this source\", so the result is the other list (copied); two present lists yield\n * their set intersection, preserving the first list's order. This layers a\n * composition field's top-level `allow` vocabulary over a slot's positional\n * `allow` at the canvas root (EC-188): a component must satisfy BOTH leashes to be\n * offered. An empty array stays empty (\"allow nothing\"); callers wanting the\n * validator's \"empty = allow any\" must coalesce `[]` to `undefined` first (which\n * `resolveInsertContext` does via its `length > 0` guard, mirroring\n * `validateCompositionValue`).\n */\nexport function intersectAllow(\n slotAllow: readonly string[] | undefined,\n fieldAllow: readonly string[] | undefined,\n): string[] | undefined {\n if (!slotAllow) return fieldAllow ? [...fieldAllow] : undefined\n if (!fieldAllow) return [...slotAllow]\n const inField = new Set(fieldAllow)\n return slotAllow.filter((id) => inField.has(id))\n}\n\nconst UNCATEGORIZED = 'other'\n\nfunction prefixedCounter(prefix: string): () => string {\n let n = 0\n return () => {\n n += 1\n return `${prefix}-${n}`\n }\n}\n\n/** A node built from a manifest's defaults with an explicit id. */\nfunction defaultNode(manifest: ComponentManifest, id: string): ComponentNode {\n return { ...nodeFromManifest(manifest), id }\n}\n\n/** Structured issues for a component that cannot render (EC-055 statuses). */\nfunction manifestIssues(studio: StudioRegistry, manifest: ComponentManifest): string[] {\n const status = componentStatus(studio, manifest)\n if (status === 'incompatible') {\n return [`No implementation is registered for \"${manifest.id}\".`]\n }\n if (status === 'invalid') {\n return studio.registry.issues\n .filter((issue) => issue.componentId === manifest.id)\n .map((issue) => issue.message)\n }\n return []\n}\n\n/**\n * Build the configured subtree a fixture describes: manifest defaults overlaid\n * with the fixture's props, plus one default-props child node per\n * `slotChildren` component id. Never throws — unknown components, undeclared\n * slots, and invalid prop values become entries in `issues` while the node is\n * still produced (the renderer shows its own visible fallbacks for them).\n */\nexport function fixtureSubtree(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n fixture: ComponentFixture,\n baseId: string,\n): { node: ComponentNode; issues: string[] } {\n const issues: string[] = []\n const fixtureProps = fixture.props ?? {}\n\n for (const [name, value] of Object.entries(fixtureProps)) {\n const spec = manifest.props[name]\n if (!spec) {\n issues.push(`Fixture \"${fixture.name}\" sets unknown prop \"${name}\".`)\n continue\n }\n // EC-190: validate the fixture value with the prop's field-def validator.\n if (!(propValueValidator(manifest, name)?.(value) ?? true)) {\n issues.push(`Fixture \"${fixture.name}\" sets an invalid value for prop \"${name}\".`)\n }\n }\n\n const node = nodeFromProps(manifest, fixtureProps, baseId)\n\n if (fixture.slotChildren) {\n const declared = new Set(manifest.slots.map((slot) => slot.name))\n const slots: NonNullable<ComponentNode['slots']> = {}\n for (const [slotName, childIds] of Object.entries(fixture.slotChildren)) {\n if (!declared.has(slotName)) {\n issues.push(`Fixture \"${fixture.name}\" fills undeclared slot \"${slotName}\".`)\n }\n slots[slotName] = childIds.map((childId, index) => {\n const id = `${baseId}-${slotName}-${index}`\n const childManifest = studio.registry.getManifest(childId)\n if (!childManifest) {\n issues.push(\n `Fixture \"${fixture.name}\" references unknown component \"${childId}\" in slot \"${slotName}\".`,\n )\n // Keep the reference: the renderer shows a visible missing-component fallback.\n return { id, componentId: childId }\n }\n return defaultNode(childManifest, id)\n })\n }\n if (Object.keys(slots).length > 0) node.slots = slots\n }\n\n return { node, issues }\n}\n\n/** All tiles for one manifest: one per fixture, else a single default tile. */\nexport function tilesForManifest(studio: StudioRegistry, manifest: ComponentManifest): BlockTile[] {\n const title = manifest.title ?? manifest.id\n const baseIssues = manifestIssues(studio, manifest)\n\n if (!manifest.fixtures || manifest.fixtures.length === 0) {\n // No declared fixtures: insert the manifest defaults, seeding layout\n // containers so a bare Stack/Grid survives EC-161 normalization instead of\n // vanishing on commit (EC-383). Preview from the seeded node for containers\n // (WYSIWYG with the insert) and from the shared preview specs otherwise\n // (authored primitive previews or plain defaults).\n const previewNode = isLayoutContainerId(manifest.id)\n ? cloneWithNewIds(seededNodeFromManifest(manifest), prefixedCounter(`preview-${manifest.id}`))\n : previewSpecs(manifest)[0]!.node\n return [\n {\n key: `${manifest.id}::default`,\n componentId: manifest.id,\n title,\n variant: 'Default',\n description: manifest.description,\n issues: baseIssues,\n previewNode,\n // EC-409: the curated preview image (when declared) replaces the live\n // render on EVERY tile of the manifest — it previews the component,\n // not one fixture variation.\n ...(manifest.previewImage ? { previewImage: manifest.previewImage } : {}),\n build: () => seededNodeFromManifest(manifest),\n },\n ]\n }\n\n return manifest.fixtures.map((fixture, index) => {\n const { node, issues } = fixtureSubtree(\n studio,\n manifest,\n fixture,\n `preview-${manifest.id}-${index}`,\n )\n return {\n key: `${manifest.id}::${fixture.name}`,\n componentId: manifest.id,\n title,\n variant: fixture.name,\n description: manifest.description,\n issues: [...baseIssues, ...issues],\n previewNode: node,\n ...(manifest.previewImage ? { previewImage: manifest.previewImage } : {}),\n build: () => fixtureSubtree(studio, manifest, fixture, 'tmp').node,\n }\n })\n}\n\nfunction matches(manifest: ComponentManifest, query: string): boolean {\n const hay = [manifest.id, manifest.title, manifest.description, manifest.category]\n .filter(Boolean)\n .join(' ')\n .toLowerCase()\n return hay.includes(query)\n}\n\n/**\n * Derive the panel's category groups from the registry: filter by slot allow\n * list and query, expand manifests into tiles, group by `category`, and sort\n * categories alphabetically (registry order within a group). Deterministic.\n */\nexport function blockGroups(studio: StudioRegistry, filter: BlockFilter = {}): BlockGroup[] {\n const query = filter.query?.trim().toLowerCase()\n const allow = filter.slotAllow\n\n const groups = new Map<string, BlockTile[]>()\n for (const manifest of studio.registry.list()) {\n // EC-191: the canvas is client-rendered, so only canvas-eligible\n // (client-renderable) components are offered for insertion; server-only / RSC\n // components are usable in dev frames, not placed on the canvas.\n if (!isCanvasEligible(manifest)) continue\n if (allow && !allow.includes(manifest.id)) continue\n if (query && !matches(manifest, query)) continue\n const category = manifest.category ?? UNCATEGORIZED\n const tiles = groups.get(category) ?? []\n tiles.push(...tilesForManifest(studio, manifest))\n groups.set(category, tiles)\n }\n\n return [...groups.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([category, tiles]) => ({ category, tiles }))\n}\n\n/**\n * Layout helper presets (EC-067) as panel tiles. Preview nodes are re-id'd\n * deterministically so sibling React keys stay unique.\n */\nexport function presetTiles(slotAllow?: string[]): BlockTile[] {\n return LAYOUT_PRESETS.filter(\n (preset) => !slotAllow || slotAllow.includes(preset.build().componentId),\n ).map((preset) => ({\n key: `preset::${preset.key}`,\n componentId: preset.build().componentId,\n title: preset.label,\n variant: preset.description,\n description: preset.description,\n issues: [],\n previewNode: cloneWithNewIds(preset.build(), prefixedCounter(`preview-preset-${preset.key}`)),\n build: () => preset.build(),\n }))\n}\n"]}
1
+ {"version":3,"file":"blocks-model.js","sourceRoot":"","sources":["../../../src/components/builder/blocks-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { cloneWithNewIds, isLayoutContainerId } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { ComponentFixture, ComponentManifest } from '@elytracms/core/component-registry'\nimport { isCanvasEligible, propValueValidator } from '@elytracms/core/component-registry'\nimport { localizedStringVariants } from '@elytracms/core/cms-core'\nimport { localizedLabel } from '@elytracms/studio/lib/i18n'\nimport { componentStatus } from '@elytracms/studio/lib/registry'\nimport type { StudioRegistry } from '@elytracms/studio/lib/registry'\nimport { nodeFromProps, previewSpecs } from '@elytracms/studio/lib/registry/preview'\nimport { LAYOUT_PRESETS, nodeFromManifest, seededNodeFromManifest } from './node-factory'\n\n/**\n * Blocks panel model (EC-164) — the pure, React-free derivation behind the\n * visual insert surface. Every registry manifest yields one tile per declared\n * fixture (or a single default-props tile), grouped by manifest `category`\n * and filtered by a free-text query over name / description / category — no\n * hand-maintained panel config. Broken fixtures degrade to tiles carrying\n * explicit `issues`, never to a missing entry or a crash.\n */\n\n/** One insertable tile in the blocks panel. */\nexport interface BlockTile {\n /** Stable identity (`<componentId>::<variant>` or `preset::<key>`). */\n key: string\n /** Component id of the node the tile inserts. */\n componentId: string\n title: string\n /** Variation label: the fixture name, `Default`, or a preset description. */\n variant: string\n description?: string\n /**\n * Validation problems detected while building the preview (unknown child\n * component, undeclared slot, invalid fixture prop, missing\n * implementation). A non-empty list marks the tile as degraded — it still\n * renders and still inserts.\n */\n issues: string[]\n /** Deterministic-id subtree rendered live in the tile thumbnail. */\n previewNode: ComponentNode\n /**\n * Curated preview image URL (EC-409, `manifest.previewImage`). When set, the\n * tile renders this image instead of the live `previewNode` render — the\n * escape hatch for components whose real render needs data/context (e.g.\n * listings) and previews as \"just text\". Insertion is unaffected.\n */\n previewImage?: string\n /** Build the subtree to insert (ids are reassigned by the builder store). */\n build: () => ComponentNode\n}\n\n/** Tiles of one manifest `category`. */\nexport interface BlockGroup {\n category: string\n tiles: BlockTile[]\n}\n\nexport interface BlockFilter {\n /** Case-insensitive match over id, title, description, and category. */\n query?: string\n /** When set, only these component ids may be inserted (slot.allow). */\n slotAllow?: string[]\n}\n\n/**\n * Intersect two component-id allow-lists. `undefined` means \"no constraint from\n * this source\", so the result is the other list (copied); two present lists yield\n * their set intersection, preserving the first list's order. This layers a\n * composition field's top-level `allow` vocabulary over a slot's positional\n * `allow` at the canvas root (EC-188): a component must satisfy BOTH leashes to be\n * offered. An empty array stays empty (\"allow nothing\"); callers wanting the\n * validator's \"empty = allow any\" must coalesce `[]` to `undefined` first (which\n * `resolveInsertContext` does via its `length > 0` guard, mirroring\n * `validateCompositionValue`).\n */\nexport function intersectAllow(\n slotAllow: readonly string[] | undefined,\n fieldAllow: readonly string[] | undefined,\n): string[] | undefined {\n if (!slotAllow) return fieldAllow ? [...fieldAllow] : undefined\n if (!fieldAllow) return [...slotAllow]\n const inField = new Set(fieldAllow)\n return slotAllow.filter((id) => inField.has(id))\n}\n\nconst UNCATEGORIZED = 'other'\n\nfunction prefixedCounter(prefix: string): () => string {\n let n = 0\n return () => {\n n += 1\n return `${prefix}-${n}`\n }\n}\n\n/** A node built from a manifest's defaults with an explicit id. */\nfunction defaultNode(manifest: ComponentManifest, id: string): ComponentNode {\n return { ...nodeFromManifest(manifest), id }\n}\n\n/** Structured issues for a component that cannot render (EC-055 statuses). */\nfunction manifestIssues(studio: StudioRegistry, manifest: ComponentManifest): string[] {\n const status = componentStatus(studio, manifest)\n if (status === 'incompatible') {\n return [`No implementation is registered for \"${manifest.id}\".`]\n }\n if (status === 'invalid') {\n return studio.registry.issues.filter((issue) => issue.componentId === manifest.id).map((issue) => issue.message)\n }\n return []\n}\n\n/**\n * Build the configured subtree a fixture describes: manifest defaults overlaid\n * with the fixture's props, plus one default-props child node per\n * `slotChildren` component id. Never throws — unknown components, undeclared\n * slots, and invalid prop values become entries in `issues` while the node is\n * still produced (the renderer shows its own visible fallbacks for them).\n */\nexport function fixtureSubtree(\n studio: StudioRegistry,\n manifest: ComponentManifest,\n fixture: ComponentFixture,\n baseId: string,\n): { node: ComponentNode; issues: string[] } {\n const issues: string[] = []\n const fixtureProps = fixture.props ?? {}\n\n for (const [name, value] of Object.entries(fixtureProps)) {\n const spec = manifest.props[name]\n if (!spec) {\n issues.push(`Fixture \"${fixture.name}\" sets unknown prop \"${name}\".`)\n continue\n }\n // EC-190: validate the fixture value with the prop's field-def validator.\n if (!(propValueValidator(manifest, name)?.(value) ?? true)) {\n issues.push(`Fixture \"${fixture.name}\" sets an invalid value for prop \"${name}\".`)\n }\n }\n\n const node = nodeFromProps(manifest, fixtureProps, baseId)\n\n if (fixture.slotChildren) {\n const declared = new Set(manifest.slots.map((slot) => slot.name))\n const slots: NonNullable<ComponentNode['slots']> = {}\n for (const [slotName, childIds] of Object.entries(fixture.slotChildren)) {\n if (!declared.has(slotName)) {\n issues.push(`Fixture \"${fixture.name}\" fills undeclared slot \"${slotName}\".`)\n }\n slots[slotName] = childIds.map((childId, index) => {\n const id = `${baseId}-${slotName}-${index}`\n const childManifest = studio.registry.getManifest(childId)\n if (!childManifest) {\n issues.push(`Fixture \"${fixture.name}\" references unknown component \"${childId}\" in slot \"${slotName}\".`)\n // Keep the reference: the renderer shows a visible missing-component fallback.\n return { id, componentId: childId }\n }\n return defaultNode(childManifest, id)\n })\n }\n if (Object.keys(slots).length > 0) node.slots = slots\n }\n\n return { node, issues }\n}\n\n/** All tiles for one manifest: one per fixture, else a single default tile. */\nexport function tilesForManifest(studio: StudioRegistry, manifest: ComponentManifest): BlockTile[] {\n const title = localizedLabel(manifest.title, manifest.id)\n const baseIssues = manifestIssues(studio, manifest)\n\n if (!manifest.fixtures || manifest.fixtures.length === 0) {\n // No declared fixtures: insert the manifest defaults, seeding layout\n // containers so a bare Stack/Grid survives EC-161 normalization instead of\n // vanishing on commit (EC-383). Preview from the seeded node for containers\n // (WYSIWYG with the insert) and from the shared preview specs otherwise\n // (authored primitive previews or plain defaults).\n const previewNode = isLayoutContainerId(manifest.id)\n ? cloneWithNewIds(seededNodeFromManifest(manifest), prefixedCounter(`preview-${manifest.id}`))\n : previewSpecs(manifest)[0]!.node\n return [\n {\n key: `${manifest.id}::default`,\n componentId: manifest.id,\n title,\n variant: 'Default',\n description: manifest.description,\n issues: baseIssues,\n previewNode,\n // EC-409: the curated preview image (when declared) replaces the live\n // render on EVERY tile of the manifest — it previews the component,\n // not one fixture variation.\n ...(manifest.previewImage ? { previewImage: manifest.previewImage } : {}),\n build: () => seededNodeFromManifest(manifest),\n },\n ]\n }\n\n return manifest.fixtures.map((fixture, index) => {\n const { node, issues } = fixtureSubtree(studio, manifest, fixture, `preview-${manifest.id}-${index}`)\n return {\n key: `${manifest.id}::${fixture.name}`,\n componentId: manifest.id,\n title,\n variant: fixture.name,\n description: manifest.description,\n issues: [...baseIssues, ...issues],\n previewNode: node,\n ...(manifest.previewImage ? { previewImage: manifest.previewImage } : {}),\n build: () => fixtureSubtree(studio, manifest, fixture, 'tmp').node,\n }\n })\n}\n\nfunction matches(manifest: ComponentManifest, query: string): boolean {\n const hay = [manifest.id, ...localizedStringVariants(manifest.title), manifest.description, manifest.category]\n .filter(Boolean)\n .join(' ')\n .toLowerCase()\n return hay.includes(query)\n}\n\n/**\n * Derive the panel's category groups from the registry: filter by slot allow\n * list and query, expand manifests into tiles, group by `category`, and sort\n * categories alphabetically (registry order within a group). Deterministic.\n */\nexport function blockGroups(studio: StudioRegistry, filter: BlockFilter = {}): BlockGroup[] {\n const query = filter.query?.trim().toLowerCase()\n const allow = filter.slotAllow\n\n const groups = new Map<string, BlockTile[]>()\n for (const manifest of studio.registry.list()) {\n // EC-191: the canvas is client-rendered, so only canvas-eligible\n // (client-renderable) components are offered for insertion; server-only / RSC\n // components are usable in dev frames, not placed on the canvas.\n if (!isCanvasEligible(manifest)) continue\n if (allow && !allow.includes(manifest.id)) continue\n if (query && !matches(manifest, query)) continue\n const category = manifest.category ?? UNCATEGORIZED\n const tiles = groups.get(category) ?? []\n tiles.push(...tilesForManifest(studio, manifest))\n groups.set(category, tiles)\n }\n\n return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, tiles]) => ({ category, tiles }))\n}\n\n/**\n * Whether ANY component could be inserted under a slot allow-list (EC-405) — the\n * cheap existence check behind the canvas insertion affordances: a \"+\" only shows\n * at a position where the blocks panel would offer at least one tile. Mirrors the\n * exact filter {@link blockGroups} applies (canvas-eligible AND, when an allow-list\n * is present, on that list), short-circuiting on the first match instead of\n * building tiles; falls back to the layout-helper presets (which are also\n * insertable there). `undefined`/absent `slotAllow` means \"allow any\" → true when\n * the registry has any canvas-eligible component.\n */\nexport function hasInsertableComponent(studio: StudioRegistry, slotAllow?: string[]): boolean {\n for (const manifest of studio.registry.list()) {\n if (!isCanvasEligible(manifest)) continue\n if (slotAllow && !slotAllow.includes(manifest.id)) continue\n return true\n }\n return presetTiles(slotAllow).length > 0\n}\n\n/**\n * Layout helper presets (EC-067) as panel tiles. Preview nodes are re-id'd\n * deterministically so sibling React keys stay unique.\n */\nexport function presetTiles(slotAllow?: string[]): BlockTile[] {\n return LAYOUT_PRESETS.filter((preset) => !slotAllow || slotAllow.includes(preset.build().componentId)).map(\n (preset) => ({\n key: `preset::${preset.key}`,\n componentId: preset.build().componentId,\n title: preset.label,\n variant: preset.description,\n description: preset.description,\n issues: [],\n previewNode: cloneWithNewIds(preset.build(), prefixedCounter(`preview-preset-${preset.key}`)),\n build: () => preset.build(),\n }),\n )\n}\n"]}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The canvas chrome STYLESHEET (EC-414 / AD-B) — the studio's complete in-frame
3
+ * styling, hand-written. This is deliberately NOT a second Tailwind world: the
4
+ * chrome inside the canvas iframe styles itself with `ec-*` classes, `data-ec-*`
5
+ * attributes, and inline styles only, so the consumer's theme is the ONLY design
6
+ * system in the canvas document and the canvas resolves exactly what delivery
7
+ * resolves (AD-A).
8
+ *
9
+ * The chrome contract (binding on ALL in-frame studio UI — EC-404/405/408 build
10
+ * on this):
11
+ *
12
+ * 1. Only `ec-*` class names / `data-ec-*` attributes / inline styles — never
13
+ * studio utility classes, never studio React UI primitives.
14
+ * 2. Chrome roots carry the containment recipe (`.ec-chrome`): `all: initial`
15
+ * plus explicit font/color/line-height/color-scheme re-establishment, so
16
+ * consumer element rules and inherited typography cannot restyle chrome.
17
+ * NOTE (verified, MDN): `all: initial` does NOT stop custom-property
18
+ * inheritance — the real protection is that chrome never CONSUMES a var()
19
+ * name a consumer could define (this file uses no var() at all; the
20
+ * contract test enforces it).
21
+ * 3. Where a consumer ELEMENT selector could still match chrome descendants
22
+ * (`div`, `span`, `button`, `input` rules in a site theme), chrome rules
23
+ * use `!important` deliberately — a small, documented surface: the
24
+ * selection/DnD indicators, the rich-text toolbar, and the locale chip.
25
+ * The `.ec-chrome` recipe itself stays normal-strength on purpose: inline
26
+ * styles are the top styling layer for chrome-owned looks (placeholders,
27
+ * spacing handles) and stylesheet-`!important` would beat them.
28
+ * 4. Escape hatch (documented only, NOT built): if chrome ever outgrows
29
+ * hand-written CSS, the verified prefixed-bundle recipe is the upgrade
30
+ * path — granular Tailwind imports `theme.css layer(theme) prefix(ec)` +
31
+ * `utilities.css layer(utilities) prefix(ec) important source(none)`
32
+ * compiled into an engine-provided entry (compiles clean on 4.3.2).
33
+ */
34
+ /** The two canvas stylesheet worlds (AD-C): `legacy` = the pre-EC-414 merged
35
+ * canvas.css (studio design system + consumer theme in one build); `new` = the
36
+ * consumer's standalone full-Tailwind theme entry, studio-free. */
37
+ export type CanvasWorld = 'legacy' | 'new';
38
+ /**
39
+ * Injected FIRST into the canvas head (before the `ec-app-css` link): a bare
40
+ * layer STATEMENT that pins `ec-canvas-backdrop` to the very bottom of the
41
+ * document's layer order (layer order is first-occurrence order). The new-world
42
+ * backdrop rules live in this layer, so they lose to EVERYTHING the consumer
43
+ * ships — `@layer base` body rules, unlayered rules, later layers — and only
44
+ * paint when the consumer paints nothing. (An unlayered backdrop would ALWAYS
45
+ * beat the consumer's layered `@layer base body { background }` — the exact
46
+ * NK-004 mechanism this task retires.) Inert in the legacy world: the legacy
47
+ * chrome CSS puts nothing in the layer.
48
+ */
49
+ export declare const CANVAS_BACKDROP_LAYER_STATEMENT = "@layer ec-canvas-backdrop;";
50
+ /**
51
+ * The complete chrome stylesheet for a world — the `ec-outline-css` element's
52
+ * content (id kept from EC-389; the cascade position is pinned by test).
53
+ */
54
+ export declare function canvasChromeCss(world: CanvasWorld): string;
@@ -0,0 +1,246 @@
1
+ import { PRESERVE_NODE_ATTR } from '@elytracms/core/rich-text';
2
+ /**
3
+ * Injected FIRST into the canvas head (before the `ec-app-css` link): a bare
4
+ * layer STATEMENT that pins `ec-canvas-backdrop` to the very bottom of the
5
+ * document's layer order (layer order is first-occurrence order). The new-world
6
+ * backdrop rules live in this layer, so they lose to EVERYTHING the consumer
7
+ * ships — `@layer base` body rules, unlayered rules, later layers — and only
8
+ * paint when the consumer paints nothing. (An unlayered backdrop would ALWAYS
9
+ * beat the consumer's layered `@layer base body { background }` — the exact
10
+ * NK-004 mechanism this task retires.) Inert in the legacy world: the legacy
11
+ * chrome CSS puts nothing in the layer.
12
+ */
13
+ export const CANVAS_BACKDROP_LAYER_STATEMENT = '@layer ec-canvas-backdrop;';
14
+ /**
15
+ * NEW-world backdrop (AD-B item 4): the studio-utility body-class default is
16
+ * retired; these neutral hand-written rules give the canvas a light, site-like
17
+ * page when the consumer theme paints nothing, in the weakest possible cascade
18
+ * position (see {@link CANVAS_BACKDROP_LAYER_STATEMENT}). Background sits on
19
+ * BODY (not html) so a consumer `body { background }` keeps its normal
20
+ * canvas-propagation behavior — short pages paint edge-to-edge exactly as the
21
+ * delivery build does.
22
+ */
23
+ const NEW_WORLD_BACKDROP_CSS = `
24
+ @layer ec-canvas-backdrop {
25
+ html { color-scheme: light; }
26
+ body { background-color: #ffffff; }
27
+ }
28
+ `;
29
+ /**
30
+ * LEGACY-world backdrop — today's rule, verbatim and unlayered (EC-389-era
31
+ * behavior applies EXACTLY in the legacy world, per AD-C): the canvas backdrop
32
+ * is deliberately always light/site-like, so the native form-control/scrollbar
33
+ * theme is forced to match and a page with real inputs never shows dark native
34
+ * chrome floating on a light page. The light page itself comes from the studio
35
+ * body-class default (`DEFAULT_CANVAS_BODY_CLASS`) resolving against the merged
36
+ * stylesheet's studio tokens.
37
+ */
38
+ const LEGACY_BACKDROP_CSS = `
39
+ html { color-scheme: light; }
40
+ `;
41
+ /**
42
+ * The chrome UI rules — identical in both worlds. Sections:
43
+ * page frame, containment recipe, editable wrappers, selection/hover overlay
44
+ * (EC-404), inline editing, rich-text toolbar, locale-fallback chips, drag &
45
+ * drop indicators.
46
+ */
47
+ const CHROME_UI_CSS = `
48
+ /* ── Page frame ─────────────────────────────────────────────────────────
49
+ The canvas gutter — unlayered on purpose (identical in both worlds, and
50
+ it must beat a consumer body margin: the 16px frame is editor ergonomics,
51
+ not page styling). */
52
+ body { margin: 0; padding: 16px; }
53
+
54
+ /* ── Containment recipe (chrome contract item 2) ────────────────────────
55
+ Every chrome ROOT (toolbar, spacing handles, in-canvas placeholders)
56
+ carries .ec-chrome: reset everything, then re-establish the chrome's own
57
+ typography and color-scheme explicitly. Inherited custom properties pierce
58
+ 'all: initial' (and even shadow DOM) — that is why chrome never consumes
59
+ var() names (contract-tested), not something this recipe could fix.
60
+ The :where(div, span, p) guard re-links descendant text to the chrome
61
+ root's typography with class-level specificity, so a consumer's bare
62
+ 'div, span { color: … }' element rules cannot restyle chrome text; chrome
63
+ elements styled inline stay inline-styled (inline always wins here). */
64
+ .ec-chrome {
65
+ all: initial;
66
+ display: block;
67
+ box-sizing: border-box;
68
+ font-family: ui-sans-serif, system-ui, sans-serif;
69
+ font-size: 13px;
70
+ line-height: 1.5;
71
+ color: rgb(30, 35, 45);
72
+ color-scheme: light;
73
+ }
74
+ .ec-chrome *,
75
+ .ec-chrome *::before,
76
+ .ec-chrome *::after { box-sizing: border-box; }
77
+ .ec-chrome :where(div, span, p) { color: inherit; font: inherit; }
78
+
79
+ /* ── Editable wrappers ──────────────────────────────────────────────────
80
+ Scoped to the per-node editable wrappers. The wrappers WRAP consumer
81
+ content, so they are deliberately NOT contained (no .ec-chrome — they must
82
+ stay layout-transparent). EC-404 RETIRED the in-document selection/hover
83
+ outlines (data-selected / data-hovered): an outline paints in the
84
+ element's own stacking order and any full-bleed background or higher
85
+ z-index sibling paints over it — a structural occlusion, not a z-index bug.
86
+ Selection + hover are now drawn by the topmost .ec-overlay layer below.
87
+ The outline-offset stays for the editing outline (and DnD 'into' overrides
88
+ it); its !important (contract item 3) keeps a site theme's element rules
89
+ from hiding those states. */
90
+ .ec-editable { outline-offset: -1px !important; cursor: default; }
91
+
92
+ /* ── Selection/hover overlay (EC-404) ───────────────────────────────────
93
+ The topmost editing layer, portaled into the iframe body as a sibling of
94
+ the page composition and pinned to the maximum stacking level, so NOTHING
95
+ the page paints can occlude the selection frame. The root carries the
96
+ .ec-chrome containment recipe; geometry (left/top/width/height) is inline,
97
+ everything visual lives here. Element-matching declarations carry
98
+ !important (contract item 3): the frame boxes are <div>s and the controls
99
+ are <button>s a site theme's element rules would otherwise restyle. Frames
100
+ are click-through (pointer-events:none); only the bar re-enables events. */
101
+ .ec-overlay {
102
+ position: absolute !important; top: 0 !important; left: 0 !important;
103
+ z-index: 2147483647 !important; pointer-events: none !important;
104
+ width: 0 !important; height: 0 !important;
105
+ }
106
+ .ec-overlay-frame {
107
+ position: absolute !important; box-sizing: border-box !important;
108
+ pointer-events: none !important; border-radius: 2px !important;
109
+ }
110
+ .ec-overlay-frame[data-ec-hover] { z-index: 1 !important; border: 1px dashed rgba(130,150,190,0.85) !important; }
111
+ .ec-overlay-frame[data-ec-selected] { z-index: 2 !important; border: 2px solid #5b8cff !important; }
112
+ /* ── Prop highlight (EC-408 reverse: field focus → sub-part) ─────────────
113
+ A quiet FILLED tint (no hard border) on the focused prop's element,
114
+ deliberately distinct from the selection frame (solid border) and the
115
+ hover frame (dashed border). Sits below both frames so their borders stay
116
+ crisp; click-through like the frames. */
117
+ .ec-overlay-prop {
118
+ position: absolute !important; z-index: 1 !important; box-sizing: border-box !important;
119
+ pointer-events: none !important; border-radius: 3px !important;
120
+ background: rgba(91,140,255,0.16) !important;
121
+ box-shadow: inset 0 0 0 1px rgba(91,140,255,0.45) !important;
122
+ }
123
+ .ec-overlay-bar {
124
+ position: absolute !important; z-index: 3 !important; pointer-events: auto !important;
125
+ display: flex !important; align-items: center !important; gap: 1px !important;
126
+ height: 22px !important; max-width: 320px !important; padding: 0 2px 0 6px !important;
127
+ border-radius: 6px !important; background: #5b8cff !important;
128
+ box-shadow: 0 2px 8px rgba(8,12,24,0.35) !important;
129
+ font: 600 11px/1 ui-sans-serif, system-ui, sans-serif !important;
130
+ color: #ffffff !important; white-space: nowrap !important;
131
+ }
132
+ .ec-overlay-label {
133
+ overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important;
134
+ max-width: 180px !important; padding-right: 2px !important;
135
+ font: inherit !important; color: #ffffff !important;
136
+ }
137
+ .ec-overlay-sep {
138
+ width: 1px !important; height: 12px !important; margin: 0 3px !important;
139
+ background: rgba(255,255,255,0.4) !important;
140
+ }
141
+ .ec-overlay-btn {
142
+ display: inline-flex !important; align-items: center !important; justify-content: center !important;
143
+ width: 20px !important; height: 18px !important; padding: 0 !important; margin: 0 !important;
144
+ border: 0 !important; border-radius: 4px !important; background: transparent !important;
145
+ color: #ffffff !important; cursor: pointer !important; -webkit-appearance: none !important; appearance: none !important;
146
+ }
147
+ .ec-overlay-btn:hover { background: rgba(255,255,255,0.22) !important; }
148
+ .ec-overlay-btn:disabled { opacity: 0.4 !important; cursor: default !important; }
149
+
150
+ /* ── Insertion affordances (EC-405) ─────────────────────────────────────
151
+ A "+" button centered ON a block boundary (before/after edge, or the center
152
+ of an empty container) — the geometry is inline (the anchor point), the
153
+ translate here centers the button on it. Appears only on hover/selection
154
+ (never during a drag or inline edit); pointer-events re-enabled so it is
155
+ clickable while the overlay root stays click-through. Element-matching
156
+ declarations !important (contract item 3): it is a <button> a site theme's
157
+ element rules would otherwise restyle. */
158
+ .ec-overlay-add {
159
+ position: absolute !important; z-index: 4 !important; pointer-events: auto !important;
160
+ transform: translate(-50%, -50%) !important;
161
+ display: inline-flex !important; align-items: center !important; justify-content: center !important;
162
+ width: 20px !important; height: 20px !important; padding: 0 !important; margin: 0 !important;
163
+ border: 1.5px solid #ffffff !important; border-radius: 999px !important;
164
+ background: #5b8cff !important; color: #ffffff !important; cursor: pointer !important;
165
+ box-shadow: 0 1px 5px rgba(8,12,24,0.4) !important;
166
+ -webkit-appearance: none !important; appearance: none !important;
167
+ }
168
+ .ec-overlay-add:hover { background: #3f74f5 !important; }
169
+
170
+ /* ── Inline editing (EC-158 text, EC-159 rich text) ─────────────────────
171
+ The rendered element itself becomes the editor — no overlay, no
172
+ replacement input — so typography, alignment, and layout are
173
+ pixel-identical between edit and render states. The wrapper keeps the
174
+ selection outline; the host only gets a caret. pre-wrap applies to PLAIN
175
+ text hosts only — rich-text markup must keep its rendered whitespace
176
+ handling (no layout shift). */
177
+ .ec-editable[data-ec-editing] { outline: 2px solid #5b8cff !important; }
178
+ .ec-editable [data-ec-edit] { outline: none; cursor: text; }
179
+ .ec-editable [data-ec-edit='text'] { white-space: pre-wrap; }
180
+ /* Preserved atoms during a rich-text session (custom blocks, unknown nodes,
181
+ images, embeds): visible and rendered, but not text-editable. */
182
+ .ec-editable [data-ec-edit='rich'] [${PRESERVE_NODE_ATTR}] { user-select: none; }
183
+
184
+ /* ── Floating mark toolbar (EC-159), portaled into the iframe ───────────
185
+ All visual declarations !important (contract item 3): the toolbar is
186
+ stylesheet-styled chrome whose button/input elements a site theme's
187
+ element rules would otherwise match. */
188
+ .ec-rt-toolbar { color-scheme: dark !important; }
189
+ .ec-rt-toolbar-row { display: flex !important; align-items: center !important; gap: 2px !important;
190
+ padding: 3px !important; margin: 0 !important;
191
+ border: 1px solid rgba(130,150,190,0.4) !important; border-radius: 8px !important;
192
+ background: rgb(24,26,32) !important; box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; }
193
+ .ec-rt-toolbar-btn { min-width: 26px !important; height: 24px !important; padding: 0 7px !important;
194
+ border: 0 !important; border-radius: 5px !important;
195
+ background: transparent !important; color: rgb(225,228,236) !important;
196
+ font: 600 12px/1 ui-sans-serif, system-ui, sans-serif !important;
197
+ cursor: pointer !important; }
198
+ .ec-rt-toolbar-btn:hover { background: rgba(130,150,190,0.25) !important; }
199
+ .ec-rt-toolbar-btn[data-active] { background: #5b8cff !important; color: white !important; }
200
+ .ec-rt-toolbar-input { width: 200px !important; height: 24px !important; padding: 0 7px !important;
201
+ border-radius: 5px !important;
202
+ border: 1px solid rgba(130,150,190,0.4) !important; background: rgb(16,18,22) !important;
203
+ color: rgb(225,228,236) !important;
204
+ font: 12px/1 ui-sans-serif, system-ui, sans-serif !important; outline: none !important; }
205
+
206
+ /* ── Locale-fallback chips (EC-176) ─────────────────────────────────────
207
+ A node whose bound content is currently served via locale fallback wears a
208
+ small corner chip naming the locale it actually came from — the canvas
209
+ never silently mixes locales. A pseudo-element cannot carry .ec-chrome, so
210
+ every declaration is !important (contract item 3). */
211
+ .ec-editable[data-ec-locale-fallback] { position: relative; }
212
+ .ec-editable[data-ec-locale-fallback]::after {
213
+ content: attr(data-ec-locale-fallback) !important;
214
+ position: absolute !important; top: 2px !important; right: 2px !important;
215
+ z-index: 5 !important; pointer-events: none !important;
216
+ padding: 0 4px !important; border-radius: 4px !important;
217
+ font: 600 9px/1.5 ui-sans-serif, system-ui, sans-serif !important;
218
+ letter-spacing: 0.05em !important; text-transform: uppercase !important;
219
+ color: #fbbf24 !important; background: rgba(180,83,9,0.25) !important;
220
+ border: 1px solid rgba(245,158,11,0.45) !important;
221
+ }
222
+
223
+ /* ── Drag & drop states (EC-160/EC-162) ─────────────────────────────────
224
+ Eligibility comes from the shared pure rules (lib/dnd/drop-rules); these
225
+ classes only *show* the resolution. Rejection is explicit and visible
226
+ during the drag — never a silent failed drop. Before/after bars follow the
227
+ parent's flow axis (data-ec-drop-axis); left/right are the
228
+ wrap-into-columns gesture (violet, paired highlight). Indicator
229
+ declarations !important (contract item 3). */
230
+ .ec-editable[data-ec-drop='into'] { outline: 2px solid #34d399 !important; outline-offset: -2px !important; background-color: rgba(52, 211, 153, 0.08) !important; }
231
+ .ec-editable[data-ec-drop='before'] { box-shadow: 0 -3px 0 0 #38bdf8 !important; }
232
+ .ec-editable[data-ec-drop='after'] { box-shadow: 0 3px 0 0 #38bdf8 !important; }
233
+ .ec-editable[data-ec-drop='before'][data-ec-drop-axis='x'] { box-shadow: -3px 0 0 0 #38bdf8 !important; }
234
+ .ec-editable[data-ec-drop='after'][data-ec-drop-axis='x'] { box-shadow: 3px 0 0 0 #38bdf8 !important; }
235
+ .ec-editable[data-ec-drop='left'] { box-shadow: -3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }
236
+ .ec-editable[data-ec-drop='right'] { box-shadow: 3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }
237
+ .ec-editable[data-ec-drop='blocked'] { outline: 2px solid #ef4444 !important; outline-offset: -2px !important; background-color: rgba(239, 68, 68, 0.08) !important; cursor: not-allowed !important; }
238
+ `;
239
+ /**
240
+ * The complete chrome stylesheet for a world — the `ec-outline-css` element's
241
+ * content (id kept from EC-389; the cascade position is pinned by test).
242
+ */
243
+ export function canvasChromeCss(world) {
244
+ return (world === 'legacy' ? LEGACY_BACKDROP_CSS : NEW_WORLD_BACKDROP_CSS) + CHROME_UI_CSS;
245
+ }
246
+ //# sourceMappingURL=canvas-chrome-css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-chrome-css.js","sourceRoot":"","sources":["../../../src/components/builder/canvas-chrome-css.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAyC9D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,4BAA4B,CAAA;AAE3E;;;;;;;;GAQG;AACH,MAAM,sBAAsB,GAAG;;;;;CAK9B,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,mBAAmB,GAAG;;CAE3B,CAAA;AAED;;;;;GAKG;AACH,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCAuIkB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDzD,CAAA;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,sBAAsB,CAAC,GAAG,aAAa,CAAA;AAC5F,CAAC","sourcesContent":["import { PRESERVE_NODE_ATTR } from '@elytracms/core/rich-text'\n\n/**\n * The canvas chrome STYLESHEET (EC-414 / AD-B) — the studio's complete in-frame\n * styling, hand-written. This is deliberately NOT a second Tailwind world: the\n * chrome inside the canvas iframe styles itself with `ec-*` classes, `data-ec-*`\n * attributes, and inline styles only, so the consumer's theme is the ONLY design\n * system in the canvas document and the canvas resolves exactly what delivery\n * resolves (AD-A).\n *\n * The chrome contract (binding on ALL in-frame studio UI — EC-404/405/408 build\n * on this):\n *\n * 1. Only `ec-*` class names / `data-ec-*` attributes / inline styles — never\n * studio utility classes, never studio React UI primitives.\n * 2. Chrome roots carry the containment recipe (`.ec-chrome`): `all: initial`\n * plus explicit font/color/line-height/color-scheme re-establishment, so\n * consumer element rules and inherited typography cannot restyle chrome.\n * NOTE (verified, MDN): `all: initial` does NOT stop custom-property\n * inheritance — the real protection is that chrome never CONSUMES a var()\n * name a consumer could define (this file uses no var() at all; the\n * contract test enforces it).\n * 3. Where a consumer ELEMENT selector could still match chrome descendants\n * (`div`, `span`, `button`, `input` rules in a site theme), chrome rules\n * use `!important` deliberately — a small, documented surface: the\n * selection/DnD indicators, the rich-text toolbar, and the locale chip.\n * The `.ec-chrome` recipe itself stays normal-strength on purpose: inline\n * styles are the top styling layer for chrome-owned looks (placeholders,\n * spacing handles) and stylesheet-`!important` would beat them.\n * 4. Escape hatch (documented only, NOT built): if chrome ever outgrows\n * hand-written CSS, the verified prefixed-bundle recipe is the upgrade\n * path — granular Tailwind imports `theme.css layer(theme) prefix(ec)` +\n * `utilities.css layer(utilities) prefix(ec) important source(none)`\n * compiled into an engine-provided entry (compiles clean on 4.3.2).\n */\n\n/** The two canvas stylesheet worlds (AD-C): `legacy` = the pre-EC-414 merged\n * canvas.css (studio design system + consumer theme in one build); `new` = the\n * consumer's standalone full-Tailwind theme entry, studio-free. */\nexport type CanvasWorld = 'legacy' | 'new'\n\n/**\n * Injected FIRST into the canvas head (before the `ec-app-css` link): a bare\n * layer STATEMENT that pins `ec-canvas-backdrop` to the very bottom of the\n * document's layer order (layer order is first-occurrence order). The new-world\n * backdrop rules live in this layer, so they lose to EVERYTHING the consumer\n * ships — `@layer base` body rules, unlayered rules, later layers — and only\n * paint when the consumer paints nothing. (An unlayered backdrop would ALWAYS\n * beat the consumer's layered `@layer base body { background }` — the exact\n * NK-004 mechanism this task retires.) Inert in the legacy world: the legacy\n * chrome CSS puts nothing in the layer.\n */\nexport const CANVAS_BACKDROP_LAYER_STATEMENT = '@layer ec-canvas-backdrop;'\n\n/**\n * NEW-world backdrop (AD-B item 4): the studio-utility body-class default is\n * retired; these neutral hand-written rules give the canvas a light, site-like\n * page when the consumer theme paints nothing, in the weakest possible cascade\n * position (see {@link CANVAS_BACKDROP_LAYER_STATEMENT}). Background sits on\n * BODY (not html) so a consumer `body { background }` keeps its normal\n * canvas-propagation behavior — short pages paint edge-to-edge exactly as the\n * delivery build does.\n */\nconst NEW_WORLD_BACKDROP_CSS = `\n @layer ec-canvas-backdrop {\n html { color-scheme: light; }\n body { background-color: #ffffff; }\n }\n`\n\n/**\n * LEGACY-world backdrop — today's rule, verbatim and unlayered (EC-389-era\n * behavior applies EXACTLY in the legacy world, per AD-C): the canvas backdrop\n * is deliberately always light/site-like, so the native form-control/scrollbar\n * theme is forced to match and a page with real inputs never shows dark native\n * chrome floating on a light page. The light page itself comes from the studio\n * body-class default (`DEFAULT_CANVAS_BODY_CLASS`) resolving against the merged\n * stylesheet's studio tokens.\n */\nconst LEGACY_BACKDROP_CSS = `\n html { color-scheme: light; }\n`\n\n/**\n * The chrome UI rules — identical in both worlds. Sections:\n * page frame, containment recipe, editable wrappers, selection/hover overlay\n * (EC-404), inline editing, rich-text toolbar, locale-fallback chips, drag &\n * drop indicators.\n */\nconst CHROME_UI_CSS = `\n /* ── Page frame ─────────────────────────────────────────────────────────\n The canvas gutter — unlayered on purpose (identical in both worlds, and\n it must beat a consumer body margin: the 16px frame is editor ergonomics,\n not page styling). */\n body { margin: 0; padding: 16px; }\n\n /* ── Containment recipe (chrome contract item 2) ────────────────────────\n Every chrome ROOT (toolbar, spacing handles, in-canvas placeholders)\n carries .ec-chrome: reset everything, then re-establish the chrome's own\n typography and color-scheme explicitly. Inherited custom properties pierce\n 'all: initial' (and even shadow DOM) — that is why chrome never consumes\n var() names (contract-tested), not something this recipe could fix.\n The :where(div, span, p) guard re-links descendant text to the chrome\n root's typography with class-level specificity, so a consumer's bare\n 'div, span { color: … }' element rules cannot restyle chrome text; chrome\n elements styled inline stay inline-styled (inline always wins here). */\n .ec-chrome {\n all: initial;\n display: block;\n box-sizing: border-box;\n font-family: ui-sans-serif, system-ui, sans-serif;\n font-size: 13px;\n line-height: 1.5;\n color: rgb(30, 35, 45);\n color-scheme: light;\n }\n .ec-chrome *,\n .ec-chrome *::before,\n .ec-chrome *::after { box-sizing: border-box; }\n .ec-chrome :where(div, span, p) { color: inherit; font: inherit; }\n\n /* ── Editable wrappers ──────────────────────────────────────────────────\n Scoped to the per-node editable wrappers. The wrappers WRAP consumer\n content, so they are deliberately NOT contained (no .ec-chrome — they must\n stay layout-transparent). EC-404 RETIRED the in-document selection/hover\n outlines (data-selected / data-hovered): an outline paints in the\n element's own stacking order and any full-bleed background or higher\n z-index sibling paints over it — a structural occlusion, not a z-index bug.\n Selection + hover are now drawn by the topmost .ec-overlay layer below.\n The outline-offset stays for the editing outline (and DnD 'into' overrides\n it); its !important (contract item 3) keeps a site theme's element rules\n from hiding those states. */\n .ec-editable { outline-offset: -1px !important; cursor: default; }\n\n /* ── Selection/hover overlay (EC-404) ───────────────────────────────────\n The topmost editing layer, portaled into the iframe body as a sibling of\n the page composition and pinned to the maximum stacking level, so NOTHING\n the page paints can occlude the selection frame. The root carries the\n .ec-chrome containment recipe; geometry (left/top/width/height) is inline,\n everything visual lives here. Element-matching declarations carry\n !important (contract item 3): the frame boxes are <div>s and the controls\n are <button>s a site theme's element rules would otherwise restyle. Frames\n are click-through (pointer-events:none); only the bar re-enables events. */\n .ec-overlay {\n position: absolute !important; top: 0 !important; left: 0 !important;\n z-index: 2147483647 !important; pointer-events: none !important;\n width: 0 !important; height: 0 !important;\n }\n .ec-overlay-frame {\n position: absolute !important; box-sizing: border-box !important;\n pointer-events: none !important; border-radius: 2px !important;\n }\n .ec-overlay-frame[data-ec-hover] { z-index: 1 !important; border: 1px dashed rgba(130,150,190,0.85) !important; }\n .ec-overlay-frame[data-ec-selected] { z-index: 2 !important; border: 2px solid #5b8cff !important; }\n /* ── Prop highlight (EC-408 reverse: field focus → sub-part) ─────────────\n A quiet FILLED tint (no hard border) on the focused prop's element,\n deliberately distinct from the selection frame (solid border) and the\n hover frame (dashed border). Sits below both frames so their borders stay\n crisp; click-through like the frames. */\n .ec-overlay-prop {\n position: absolute !important; z-index: 1 !important; box-sizing: border-box !important;\n pointer-events: none !important; border-radius: 3px !important;\n background: rgba(91,140,255,0.16) !important;\n box-shadow: inset 0 0 0 1px rgba(91,140,255,0.45) !important;\n }\n .ec-overlay-bar {\n position: absolute !important; z-index: 3 !important; pointer-events: auto !important;\n display: flex !important; align-items: center !important; gap: 1px !important;\n height: 22px !important; max-width: 320px !important; padding: 0 2px 0 6px !important;\n border-radius: 6px !important; background: #5b8cff !important;\n box-shadow: 0 2px 8px rgba(8,12,24,0.35) !important;\n font: 600 11px/1 ui-sans-serif, system-ui, sans-serif !important;\n color: #ffffff !important; white-space: nowrap !important;\n }\n .ec-overlay-label {\n overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important;\n max-width: 180px !important; padding-right: 2px !important;\n font: inherit !important; color: #ffffff !important;\n }\n .ec-overlay-sep {\n width: 1px !important; height: 12px !important; margin: 0 3px !important;\n background: rgba(255,255,255,0.4) !important;\n }\n .ec-overlay-btn {\n display: inline-flex !important; align-items: center !important; justify-content: center !important;\n width: 20px !important; height: 18px !important; padding: 0 !important; margin: 0 !important;\n border: 0 !important; border-radius: 4px !important; background: transparent !important;\n color: #ffffff !important; cursor: pointer !important; -webkit-appearance: none !important; appearance: none !important;\n }\n .ec-overlay-btn:hover { background: rgba(255,255,255,0.22) !important; }\n .ec-overlay-btn:disabled { opacity: 0.4 !important; cursor: default !important; }\n\n /* ── Insertion affordances (EC-405) ─────────────────────────────────────\n A \"+\" button centered ON a block boundary (before/after edge, or the center\n of an empty container) — the geometry is inline (the anchor point), the\n translate here centers the button on it. Appears only on hover/selection\n (never during a drag or inline edit); pointer-events re-enabled so it is\n clickable while the overlay root stays click-through. Element-matching\n declarations !important (contract item 3): it is a <button> a site theme's\n element rules would otherwise restyle. */\n .ec-overlay-add {\n position: absolute !important; z-index: 4 !important; pointer-events: auto !important;\n transform: translate(-50%, -50%) !important;\n display: inline-flex !important; align-items: center !important; justify-content: center !important;\n width: 20px !important; height: 20px !important; padding: 0 !important; margin: 0 !important;\n border: 1.5px solid #ffffff !important; border-radius: 999px !important;\n background: #5b8cff !important; color: #ffffff !important; cursor: pointer !important;\n box-shadow: 0 1px 5px rgba(8,12,24,0.4) !important;\n -webkit-appearance: none !important; appearance: none !important;\n }\n .ec-overlay-add:hover { background: #3f74f5 !important; }\n\n /* ── Inline editing (EC-158 text, EC-159 rich text) ─────────────────────\n The rendered element itself becomes the editor — no overlay, no\n replacement input — so typography, alignment, and layout are\n pixel-identical between edit and render states. The wrapper keeps the\n selection outline; the host only gets a caret. pre-wrap applies to PLAIN\n text hosts only — rich-text markup must keep its rendered whitespace\n handling (no layout shift). */\n .ec-editable[data-ec-editing] { outline: 2px solid #5b8cff !important; }\n .ec-editable [data-ec-edit] { outline: none; cursor: text; }\n .ec-editable [data-ec-edit='text'] { white-space: pre-wrap; }\n /* Preserved atoms during a rich-text session (custom blocks, unknown nodes,\n images, embeds): visible and rendered, but not text-editable. */\n .ec-editable [data-ec-edit='rich'] [${PRESERVE_NODE_ATTR}] { user-select: none; }\n\n /* ── Floating mark toolbar (EC-159), portaled into the iframe ───────────\n All visual declarations !important (contract item 3): the toolbar is\n stylesheet-styled chrome whose button/input elements a site theme's\n element rules would otherwise match. */\n .ec-rt-toolbar { color-scheme: dark !important; }\n .ec-rt-toolbar-row { display: flex !important; align-items: center !important; gap: 2px !important;\n padding: 3px !important; margin: 0 !important;\n border: 1px solid rgba(130,150,190,0.4) !important; border-radius: 8px !important;\n background: rgb(24,26,32) !important; box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; }\n .ec-rt-toolbar-btn { min-width: 26px !important; height: 24px !important; padding: 0 7px !important;\n border: 0 !important; border-radius: 5px !important;\n background: transparent !important; color: rgb(225,228,236) !important;\n font: 600 12px/1 ui-sans-serif, system-ui, sans-serif !important;\n cursor: pointer !important; }\n .ec-rt-toolbar-btn:hover { background: rgba(130,150,190,0.25) !important; }\n .ec-rt-toolbar-btn[data-active] { background: #5b8cff !important; color: white !important; }\n .ec-rt-toolbar-input { width: 200px !important; height: 24px !important; padding: 0 7px !important;\n border-radius: 5px !important;\n border: 1px solid rgba(130,150,190,0.4) !important; background: rgb(16,18,22) !important;\n color: rgb(225,228,236) !important;\n font: 12px/1 ui-sans-serif, system-ui, sans-serif !important; outline: none !important; }\n\n /* ── Locale-fallback chips (EC-176) ─────────────────────────────────────\n A node whose bound content is currently served via locale fallback wears a\n small corner chip naming the locale it actually came from — the canvas\n never silently mixes locales. A pseudo-element cannot carry .ec-chrome, so\n every declaration is !important (contract item 3). */\n .ec-editable[data-ec-locale-fallback] { position: relative; }\n .ec-editable[data-ec-locale-fallback]::after {\n content: attr(data-ec-locale-fallback) !important;\n position: absolute !important; top: 2px !important; right: 2px !important;\n z-index: 5 !important; pointer-events: none !important;\n padding: 0 4px !important; border-radius: 4px !important;\n font: 600 9px/1.5 ui-sans-serif, system-ui, sans-serif !important;\n letter-spacing: 0.05em !important; text-transform: uppercase !important;\n color: #fbbf24 !important; background: rgba(180,83,9,0.25) !important;\n border: 1px solid rgba(245,158,11,0.45) !important;\n }\n\n /* ── Drag & drop states (EC-160/EC-162) ─────────────────────────────────\n Eligibility comes from the shared pure rules (lib/dnd/drop-rules); these\n classes only *show* the resolution. Rejection is explicit and visible\n during the drag — never a silent failed drop. Before/after bars follow the\n parent's flow axis (data-ec-drop-axis); left/right are the\n wrap-into-columns gesture (violet, paired highlight). Indicator\n declarations !important (contract item 3). */\n .ec-editable[data-ec-drop='into'] { outline: 2px solid #34d399 !important; outline-offset: -2px !important; background-color: rgba(52, 211, 153, 0.08) !important; }\n .ec-editable[data-ec-drop='before'] { box-shadow: 0 -3px 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='after'] { box-shadow: 0 3px 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='before'][data-ec-drop-axis='x'] { box-shadow: -3px 0 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='after'][data-ec-drop-axis='x'] { box-shadow: 3px 0 0 0 #38bdf8 !important; }\n .ec-editable[data-ec-drop='left'] { box-shadow: -3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }\n .ec-editable[data-ec-drop='right'] { box-shadow: 3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }\n .ec-editable[data-ec-drop='blocked'] { outline: 2px solid #ef4444 !important; outline-offset: -2px !important; background-color: rgba(239, 68, 68, 0.08) !important; cursor: not-allowed !important; }\n`\n\n/**\n * The complete chrome stylesheet for a world — the `ec-outline-css` element's\n * content (id kept from EC-389; the cascade position is pinned by test).\n */\nexport function canvasChromeCss(world: CanvasWorld): string {\n return (world === 'legacy' ? LEGACY_BACKDROP_CSS : NEW_WORLD_BACKDROP_CSS) + CHROME_UI_CSS\n}\n"]}
@@ -1,24 +1,100 @@
1
1
  import type { StudioCanvasTheme } from '../app-views/studio-config.js';
2
+ import type { CanvasWorld } from './canvas-chrome-css.js';
3
+ export { canvasChromeCss, CANVAS_BACKDROP_LAYER_STATEMENT } from './canvas-chrome-css.js';
4
+ export type { CanvasWorld } from './canvas-chrome-css.js';
2
5
  /**
3
- * The canvas iframe's document chrome (EC-389) — the framework-free half of
4
- * `BuilderCanvas`'s `wireUp` (house pattern, EC-330): everything injected into
5
- * the preview document lives here, in ONE pinned cascade order, unit-testable
6
- * without jsdom.
6
+ * The canvas iframe's document chrome (EC-389, reshaped by EC-414) — the
7
+ * framework-free half of `BuilderCanvas`'s `wireUp` (house pattern, EC-330):
8
+ * everything injected into the preview document lives here, in ONE pinned
9
+ * cascade order, unit-testable without jsdom.
10
+ *
11
+ * EC-414 (AD-A/AD-C): the studio design system is leaving the canvas document.
12
+ * Which world a project lives in is decided by a RUNTIME PROBE of the loaded
13
+ * canvas stylesheet — no flag, no config, zero consumer breakage:
14
+ *
15
+ * - `legacy` (marker tokens present): the merged pre-EC-414 canvas.css that
16
+ * bundles `@elytracms/studio/styles.css` with the consumer theme. The
17
+ * EC-389 body-class default and today's behavior apply EXACTLY.
18
+ * - `new` (markers absent): the consumer's standalone full-Tailwind theme
19
+ * entry. No studio utilities exist in the document, so the body-class
20
+ * default retires and the neutral hand-written backdrop applies instead.
7
21
  */
8
- /** Selection/hover outlines, scoped to the per-node editable wrappers. */
9
- export declare const OUTLINE_CSS = "\n /* The canvas backdrop is deliberately always light/site-like (see the\n wireUp note in canvas.tsx) \u2014 force the native form-control/scrollbar theme\n to match, so a page with real inputs never shows dark native chrome\n floating on a light page. */\n html { color-scheme: light; }\n body { margin: 0; padding: 16px; }\n .ec-editable { outline-offset: -1px; cursor: default; }\n .ec-editable[data-hovered]:not([data-selected]) { outline: 1px dashed rgba(130,150,190,0.75); }\n .ec-editable[data-selected] { outline: 2px solid #5b8cff; }\n /* Inline editing (EC-158 text, EC-159 rich text): the rendered element\n itself becomes the editor \u2014 no overlay, no replacement input \u2014 so\n typography, alignment, and layout are pixel-identical between edit and\n render states. The wrapper keeps the selection outline; the host only\n gets a caret. pre-wrap applies to PLAIN text hosts only \u2014 rich-text\n markup must keep its rendered whitespace handling (no layout shift). */\n .ec-editable[data-ec-editing] { outline: 2px solid #5b8cff; }\n .ec-editable [data-ec-edit] { outline: none; cursor: text; }\n .ec-editable [data-ec-edit='text'] { white-space: pre-wrap; }\n /* Preserved atoms during a rich-text session (custom blocks, unknown nodes,\n images, embeds): visible and rendered, but not text-editable. */\n .ec-editable [data-ec-edit='rich'] [data-ec-rt-preserve] { user-select: none; }\n /* Floating mark toolbar (EC-159), portaled into the iframe. */\n .ec-rt-toolbar { color-scheme: dark; }\n .ec-rt-toolbar-row { display: flex; align-items: center; gap: 2px; padding: 3px;\n border: 1px solid rgba(130,150,190,0.4); border-radius: 8px;\n background: rgb(24,26,32); box-shadow: 0 4px 16px rgba(0,0,0,0.4); }\n .ec-rt-toolbar-btn { min-width: 26px; height: 24px; padding: 0 7px; border: 0; border-radius: 5px;\n background: transparent; color: rgb(225,228,236); font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;\n cursor: pointer; }\n .ec-rt-toolbar-btn:hover { background: rgba(130,150,190,0.25); }\n .ec-rt-toolbar-btn[data-active] { background: #5b8cff; color: white; }\n .ec-rt-toolbar-input { width: 200px; height: 24px; padding: 0 7px; border-radius: 5px;\n border: 1px solid rgba(130,150,190,0.4); background: rgb(16,18,22); color: rgb(225,228,236);\n font: 12px/1 ui-sans-serif, system-ui, sans-serif; outline: none; }\n /* Locale-fallback chips (EC-176): a node whose bound content is currently\n served via locale fallback wears a small corner chip naming the locale it\n actually came from \u2014 the canvas never silently mixes locales. */\n .ec-editable[data-ec-locale-fallback] { position: relative; }\n .ec-editable[data-ec-locale-fallback]::after {\n content: attr(data-ec-locale-fallback);\n position: absolute; top: 2px; right: 2px; z-index: 5; pointer-events: none;\n padding: 0 4px; border-radius: 4px;\n font: 600 9px/1.5 ui-sans-serif, system-ui, sans-serif;\n letter-spacing: 0.05em; text-transform: uppercase;\n color: #fbbf24; background: rgba(180,83,9,0.25); border: 1px solid rgba(245,158,11,0.45);\n }\n /* Drag & drop states (EC-160/EC-162): eligibility comes from the shared pure\n rules (lib/dnd/drop-rules); these classes only *show* the resolution.\n Rejection is explicit and visible during the drag \u2014 never a silent failed\n drop. Before/after bars follow the parent's flow axis (data-ec-drop-axis);\n left/right are the wrap-into-columns gesture (violet, paired highlight). */\n .ec-editable[data-ec-drop='into'] { outline: 2px solid #34d399; outline-offset: -2px; background-color: rgba(52, 211, 153, 0.08); }\n .ec-editable[data-ec-drop='before'] { box-shadow: 0 -3px 0 0 #38bdf8; }\n .ec-editable[data-ec-drop='after'] { box-shadow: 0 3px 0 0 #38bdf8; }\n .ec-editable[data-ec-drop='before'][data-ec-drop-axis='x'] { box-shadow: -3px 0 0 0 #38bdf8; }\n .ec-editable[data-ec-drop='after'][data-ec-drop-axis='x'] { box-shadow: 3px 0 0 0 #38bdf8; }\n .ec-editable[data-ec-drop='left'] { box-shadow: -3px 0 0 0 #a78bfa; background-color: rgba(167, 139, 250, 0.1); }\n .ec-editable[data-ec-drop='right'] { box-shadow: 3px 0 0 0 #a78bfa; background-color: rgba(167, 139, 250, 0.1); }\n .ec-editable[data-ec-drop='blocked'] { outline: 2px solid #ef4444; outline-offset: -2px; background-color: rgba(239, 68, 68, 0.08); cursor: not-allowed; }\n";
10
22
  /**
11
- * The studio's DEFAULT canvas body classes — a replaceable default (EC-389
12
- * decision): a consumer's `defineStudio({ canvas: { bodyClassName } })` REPLACES
13
- * it wholesale (including `''` to drop it), because these utility classes
23
+ * The studio design system's marker tokens (AD-C probe). These are the plain
24
+ * `:root` custom properties from the studio's generated `tokens.css`, which any
25
+ * merged legacy build carries VERBATIM — unlike `@theme inline` variables
26
+ * (e.g. `--color-panel`), which Tailwind v4 inlines into utilities and never
27
+ * emits as custom properties (verified against the repo's Tailwind 4.3.2; the
28
+ * task file's `--color-panel` example probe would classify every legacy build
29
+ * as new). Two tokens ANDed so a consumer theme that happens to define one
30
+ * generic name (`--panel`) cannot be mistaken for the studio design system.
31
+ */
32
+ export declare const CANVAS_LEGACY_MARKER_TOKENS: readonly ["--panel", "--grid-line"];
33
+ /**
34
+ * A Tailwind v4 build unconditionally emits `--default-font-family` on
35
+ * `:root, :host` (preflight consumes it) — its absence after a successful
36
+ * stylesheet load means the canvas stylesheet is not a Tailwind theme entry at
37
+ * all (AD-D item 2's "defines no @theme tokens" invalid state).
38
+ */
39
+ export declare const CANVAS_TAILWIND_MARKER_TOKEN = "--default-font-family";
40
+ /** The settled classification of the canvas stylesheet (AD-C + AD-D item 2). */
41
+ export type CanvasThemeStatus =
42
+ /** Pre-EC-414 merged canvas.css — studio design system + consumer theme. */
43
+ {
44
+ kind: 'legacy';
45
+ }
46
+ /** The EC-414 shape: a standalone consumer Tailwind theme entry. */
47
+ | {
48
+ kind: 'new';
49
+ }
50
+ /** The app never provided a canvas stylesheet (`setCanvasStylesheetUrl`). */
51
+ | {
52
+ kind: 'no-stylesheet';
53
+ }
54
+ /** The stylesheet `<link>` failed to load. */
55
+ | {
56
+ kind: 'stylesheet-error';
57
+ }
58
+ /** Loaded, but defines no Tailwind theme tokens at all. */
59
+ | {
60
+ kind: 'no-theme-tokens';
61
+ };
62
+ /** The raw computed-style sample the pure classifier decides from. */
63
+ export interface CanvasThemeProbeSample {
64
+ /** The `ec-app-css` link fired `error` (or never resolved). */
65
+ loadFailed: boolean;
66
+ /** Computed values of {@link CANVAS_LEGACY_MARKER_TOKENS}, in order. */
67
+ legacyMarkers: readonly string[];
68
+ /** Computed value of {@link CANVAS_TAILWIND_MARKER_TOKEN}. */
69
+ tailwindMarker: string;
70
+ }
71
+ /**
72
+ * AD-C pure core: classify the canvas stylesheet world from computed-token
73
+ * presence. Legacy needs EVERY legacy marker; a loaded stylesheet with no
74
+ * Tailwind marker is the explicit `no-theme-tokens` invalid state.
75
+ */
76
+ export declare function classifyCanvasTheme(sample: CanvasThemeProbeSample): CanvasThemeStatus;
77
+ /** The world a status renders as: only the legacy merged stylesheet keeps the
78
+ * EC-389 body-class default; every other state gets the neutral backdrop. */
79
+ export declare function canvasWorldOf(status: CanvasThemeStatus): CanvasWorld;
80
+ /** Sample the live document's computed root styles for {@link classifyCanvasTheme}. */
81
+ export declare function probeCanvasTheme(doc: Document, loadFailed?: boolean): CanvasThemeStatus;
82
+ /**
83
+ * The studio's DEFAULT canvas body classes — LEGACY world only (EC-389 → EC-414):
84
+ * a consumer's `defineStudio({ canvas: { bodyClassName } })` REPLACES it
85
+ * wholesale (including `''` to drop it), because these utility classes
14
86
  * out-cascade a site theme's `@layer base body` rules — keeping them additive
15
- * would make the site's own page background unreachable (the NK-004 bug).
87
+ * would make the site's own page background unreachable (the NK-004 bug). In
88
+ * the NEW world the default retires entirely (the utilities don't exist there);
89
+ * the chrome stylesheet's neutral backdrop takes over.
16
90
  */
17
91
  export declare const DEFAULT_CANVAS_BODY_CLASS = "bg-background text-foreground font-sans";
18
92
  /** Everything injected into the canvas preview document. */
19
93
  export interface CanvasChromeInputs {
20
94
  /** The consuming app's compiled canvas stylesheet URL; `null` → no link (tests/SSR). */
21
95
  stylesheetUrl: string | null;
96
+ /** Which stylesheet world the document is in (AD-C probe; see `wireCanvasChrome`). */
97
+ world: CanvasWorld;
22
98
  /** An instantiated starter kit's brand token palette (EC-117), when the project has one. */
23
99
  kitTokensCss?: string;
24
100
  /** The consumer's canvas theme from `defineStudio({ canvas })` (EC-389). */
@@ -48,18 +124,46 @@ export interface CanvasChromeDocument {
48
124
  };
49
125
  }
50
126
  /**
51
- * Apply the canvas document chrome in the PINNED cascade order (EC-389):
127
+ * Apply the canvas document chrome in the PINNED cascade order (EC-389/EC-414):
52
128
  *
53
- * 1. `ec-app-css` — the canvas stylesheet `<link>` (site delivery CSS + studio CSS);
54
- * 2. `ec-outline-css` — the studio's editing chrome (selection/DnD/toolbar styles);
55
- * 3. `ec-kit-tokens` — an instantiated starter kit's `tokensCss` (kit provides…);
56
- * 4. `ec-canvas-theme` — the consumer's `canvas.tokensCss` (…consumer overrides).
129
+ * 1. `ec-layer-order` — the backdrop layer STATEMENT, pinning the chrome's
130
+ * `ec-canvas-backdrop` layer to the bottom of the layer order (it must
131
+ * precede the consumer stylesheet in document order to lose to it);
132
+ * 2. `ec-app-css` — the canvas stylesheet `<link>` (the consumer theme; in
133
+ * the legacy world also the studio design system);
134
+ * 3. `ec-outline-css` — the studio's editing chrome (world-composed:
135
+ * backdrop + selection/DnD/toolbar/containment styles);
136
+ * 4. `ec-kit-tokens` — an instantiated starter kit's `tokensCss` (kit provides…);
137
+ * 5. `ec-canvas-theme` — the consumer's `canvas.tokensCss` (…consumer overrides).
57
138
  *
58
139
  * Later elements win the cascade at equal specificity, so studio defaults <
59
140
  * kit tokens < consumer config — the customization-seam ordering. The body
60
141
  * class is the consumer's `bodyClassName` when given (even `''`), else the
61
- * studio default. Safe to run repeatedly (iframe onLoad + the already-loaded
62
- * fast path): links/styles are deduped by id, and the two token styles are
63
- * re-appended IN ORDER so a re-wire never flips who wins.
142
+ * legacy default in the legacy world and empty in the new world (AD-C). Safe
143
+ * to run repeatedly (iframe onLoad + the already-loaded fast path + the probe
144
+ * settling): links/styles are deduped by id, the chrome CSS is re-composed in
145
+ * place when the world flips, and the two token styles are re-appended IN
146
+ * ORDER so a re-wire never flips who wins.
64
147
  */
65
148
  export declare function applyCanvasChrome(d: CanvasChromeDocument, inputs: CanvasChromeInputs): void;
149
+ /** `applyCanvasChrome` inputs minus the probe-owned `world`, plus the settle callback. */
150
+ export interface WireCanvasChromeOptions {
151
+ stylesheetUrl: string | null;
152
+ kitTokensCss?: string;
153
+ theme?: StudioCanvasTheme;
154
+ /**
155
+ * Fired when the classification SETTLES — the stylesheet finished loading
156
+ * (or failing), or there is no stylesheet to wait for. Feeds the AD-D dev
157
+ * notice; may fire again on re-wires (callers dedupe by kind).
158
+ */
159
+ onStatus?: (status: CanvasThemeStatus) => void;
160
+ }
161
+ /**
162
+ * The DOM-level wiring around {@link applyCanvasChrome}: applies the chrome
163
+ * immediately with the CURRENT classification, then re-applies once the
164
+ * `ec-app-css` link settles (the AD-C probe reads computed custom properties,
165
+ * which exist only after the stylesheet loads). Before the stylesheet arrives
166
+ * neither world's styling is resolvable anyway (no consumer CSS yet), so the
167
+ * pre-settle pass is visually neutral in both worlds.
168
+ */
169
+ export declare function wireCanvasChrome(doc: Document, options: WireCanvasChromeOptions): void;