@elytracms/studio 0.0.62 → 0.0.64

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 (110) hide show
  1. package/dist/components/account/credential-panel.js +6 -1
  2. package/dist/components/account/credential-panel.js.map +1 -1
  3. package/dist/components/auth-context.d.ts +14 -0
  4. package/dist/components/auth-context.js +32 -2
  5. package/dist/components/auth-context.js.map +1 -1
  6. package/dist/components/builder/blocks-model.d.ts +7 -0
  7. package/dist/components/builder/blocks-model.js +5 -0
  8. package/dist/components/builder/blocks-model.js.map +1 -1
  9. package/dist/components/builder/blocks-panel.js +2 -2
  10. package/dist/components/builder/blocks-panel.js.map +1 -1
  11. package/dist/components/builder/canvas-chrome-css.d.ts +54 -0
  12. package/dist/components/builder/canvas-chrome-css.js +164 -0
  13. package/dist/components/builder/canvas-chrome-css.js.map +1 -0
  14. package/dist/components/builder/canvas-chrome.d.ts +122 -18
  15. package/dist/components/builder/canvas-chrome.js +148 -82
  16. package/dist/components/builder/canvas-chrome.js.map +1 -1
  17. package/dist/components/builder/canvas-render-context.d.ts +13 -0
  18. package/dist/components/builder/canvas-render-context.js +7 -5
  19. package/dist/components/builder/canvas-render-context.js.map +1 -1
  20. package/dist/components/builder/canvas-stylesheet.d.ts +1 -1
  21. package/dist/components/builder/canvas-stylesheet.js +16 -9
  22. package/dist/components/builder/canvas-stylesheet.js.map +1 -1
  23. package/dist/components/builder/canvas-theme-notice.d.ts +6 -0
  24. package/dist/components/builder/canvas-theme-notice.js +46 -0
  25. package/dist/components/builder/canvas-theme-notice.js.map +1 -0
  26. package/dist/components/builder/canvas.js +17 -10
  27. package/dist/components/builder/canvas.js.map +1 -1
  28. package/dist/components/builder/fixture-thumbnail.d.ts +15 -0
  29. package/dist/components/builder/fixture-thumbnail.js +14 -0
  30. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  31. package/dist/components/builder/rich-text-toolbar.d.ts +23 -0
  32. package/dist/components/builder/rich-text-toolbar.js +24 -17
  33. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  34. package/dist/components/builder/spacing-handles.d.ts +31 -0
  35. package/dist/components/builder/spacing-handles.js +49 -37
  36. package/dist/components/builder/spacing-handles.js.map +1 -1
  37. package/dist/components/cms/cms-index.js +6 -3
  38. package/dist/components/cms/cms-index.js.map +1 -1
  39. package/dist/components/components/component-preview.d.ts +2 -1
  40. package/dist/components/components/component-preview.js +24 -25
  41. package/dist/components/components/component-preview.js.map +1 -1
  42. package/dist/components/deployment-setup-gate.js +40 -8
  43. package/dist/components/deployment-setup-gate.js.map +1 -1
  44. package/dist/components/operation-error.d.ts +7 -5
  45. package/dist/components/operation-error.js +19 -36
  46. package/dist/components/operation-error.js.map +1 -1
  47. package/dist/components/project-list.js +17 -1
  48. package/dist/components/project-list.js.map +1 -1
  49. package/dist/components/project-switcher.d.ts +10 -0
  50. package/dist/components/project-switcher.js +13 -3
  51. package/dist/components/project-switcher.js.map +1 -1
  52. package/dist/components/session-expired-overlay.d.ts +27 -0
  53. package/dist/components/session-expired-overlay.js +51 -0
  54. package/dist/components/session-expired-overlay.js.map +1 -0
  55. package/dist/components/sign-in-view.d.ts +13 -1
  56. package/dist/components/sign-in-view.js +27 -13
  57. package/dist/components/sign-in-view.js.map +1 -1
  58. package/dist/components/studio-project-screen.js +2 -2
  59. package/dist/components/studio-project-screen.js.map +1 -1
  60. package/dist/components/studio-shell.js +40 -3
  61. package/dist/components/studio-shell.js.map +1 -1
  62. package/dist/customization.d.ts +4 -1
  63. package/dist/customization.js +3 -0
  64. package/dist/customization.js.map +1 -1
  65. package/dist/lib/auth-errors.d.ts +58 -28
  66. package/dist/lib/auth-errors.js +93 -15
  67. package/dist/lib/auth-errors.js.map +1 -1
  68. package/dist/lib/i18n/index.d.ts +1 -0
  69. package/dist/lib/i18n/index.js.map +1 -1
  70. package/dist/lib/i18n/messages.de.d.ts +24 -0
  71. package/dist/lib/i18n/messages.de.js +1023 -0
  72. package/dist/lib/i18n/messages.de.js.map +1 -0
  73. package/dist/lib/i18n/messages.en.d.ts +884 -0
  74. package/dist/lib/i18n/messages.en.js +76 -3
  75. package/dist/lib/i18n/messages.en.js.map +1 -1
  76. package/dist/lib/operations/studio-operations.js +7 -0
  77. package/dist/lib/operations/studio-operations.js.map +1 -1
  78. package/dist/lib/persistence/backend-mode.d.ts +7 -0
  79. package/dist/lib/persistence/backend-mode.js +9 -0
  80. package/dist/lib/persistence/backend-mode.js.map +1 -1
  81. package/dist/lib/persistence/deployment-readiness.d.ts +8 -1
  82. package/dist/lib/persistence/deployment-readiness.js +16 -1
  83. package/dist/lib/persistence/deployment-readiness.js.map +1 -1
  84. package/dist/lib/persistence/index.d.ts +1 -1
  85. package/dist/lib/persistence/index.js +1 -1
  86. package/dist/lib/persistence/index.js.map +1 -1
  87. package/dist/lib/session-expiry.d.ts +67 -0
  88. package/dist/lib/session-expiry.js +72 -0
  89. package/dist/lib/session-expiry.js.map +1 -0
  90. package/dist/lib/tab-title.d.ts +53 -0
  91. package/dist/lib/tab-title.js +77 -0
  92. package/dist/lib/tab-title.js.map +1 -0
  93. package/dist/lib/workspace/index.d.ts +1 -1
  94. package/dist/lib/workspace/index.js +1 -1
  95. package/dist/lib/workspace/index.js.map +1 -1
  96. package/dist/lib/workspace/switcher-source.d.ts +27 -0
  97. package/dist/lib/workspace/switcher-source.js +30 -0
  98. package/dist/lib/workspace/switcher-source.js.map +1 -1
  99. package/dist/router/index.d.ts +6 -0
  100. package/dist/router/index.js +6 -0
  101. package/dist/router/index.js.map +1 -1
  102. package/dist/router/launcher-visit.d.ts +13 -0
  103. package/dist/router/launcher-visit.js +34 -0
  104. package/dist/router/launcher-visit.js.map +1 -0
  105. package/dist/router/root.js +12 -2
  106. package/dist/router/root.js.map +1 -1
  107. package/dist/ui/dialog.d.ts +15 -0
  108. package/dist/ui/dialog.js +17 -0
  109. package/dist/ui/dialog.js.map +1 -1
  110. package/package.json +6 -2
@@ -0,0 +1,164 @@
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, selection/hover outlines, inline editing,
44
+ * rich-text toolbar, locale-fallback chips, drag & drop indicators.
45
+ */
46
+ const CHROME_UI_CSS = `
47
+ /* ── Page frame ─────────────────────────────────────────────────────────
48
+ The canvas gutter — unlayered on purpose (identical in both worlds, and
49
+ it must beat a consumer body margin: the 16px frame is editor ergonomics,
50
+ not page styling). */
51
+ body { margin: 0; padding: 16px; }
52
+
53
+ /* ── Containment recipe (chrome contract item 2) ────────────────────────
54
+ Every chrome ROOT (toolbar, spacing handles, in-canvas placeholders)
55
+ carries .ec-chrome: reset everything, then re-establish the chrome's own
56
+ typography and color-scheme explicitly. Inherited custom properties pierce
57
+ 'all: initial' (and even shadow DOM) — that is why chrome never consumes
58
+ var() names (contract-tested), not something this recipe could fix.
59
+ The :where(div, span, p) guard re-links descendant text to the chrome
60
+ root's typography with class-level specificity, so a consumer's bare
61
+ 'div, span { color: … }' element rules cannot restyle chrome text; chrome
62
+ elements styled inline stay inline-styled (inline always wins here). */
63
+ .ec-chrome {
64
+ all: initial;
65
+ display: block;
66
+ box-sizing: border-box;
67
+ font-family: ui-sans-serif, system-ui, sans-serif;
68
+ font-size: 13px;
69
+ line-height: 1.5;
70
+ color: rgb(30, 35, 45);
71
+ color-scheme: light;
72
+ }
73
+ .ec-chrome *,
74
+ .ec-chrome *::before,
75
+ .ec-chrome *::after { box-sizing: border-box; }
76
+ .ec-chrome :where(div, span, p) { color: inherit; font: inherit; }
77
+
78
+ /* ── Selection/hover outlines ───────────────────────────────────────────
79
+ Scoped to the per-node editable wrappers. The wrappers WRAP consumer
80
+ content, so they are deliberately NOT contained (no .ec-chrome — they must
81
+ stay layout-transparent); their indicator declarations carry !important
82
+ (contract item 3) so a site theme's element rules can never hide the
83
+ selection state. */
84
+ .ec-editable { outline-offset: -1px !important; cursor: default; }
85
+ .ec-editable[data-hovered]:not([data-selected]) { outline: 1px dashed rgba(130,150,190,0.75) !important; }
86
+ .ec-editable[data-selected] { outline: 2px solid #5b8cff !important; }
87
+
88
+ /* ── Inline editing (EC-158 text, EC-159 rich text) ─────────────────────
89
+ The rendered element itself becomes the editor — no overlay, no
90
+ replacement input — so typography, alignment, and layout are
91
+ pixel-identical between edit and render states. The wrapper keeps the
92
+ selection outline; the host only gets a caret. pre-wrap applies to PLAIN
93
+ text hosts only — rich-text markup must keep its rendered whitespace
94
+ handling (no layout shift). */
95
+ .ec-editable[data-ec-editing] { outline: 2px solid #5b8cff !important; }
96
+ .ec-editable [data-ec-edit] { outline: none; cursor: text; }
97
+ .ec-editable [data-ec-edit='text'] { white-space: pre-wrap; }
98
+ /* Preserved atoms during a rich-text session (custom blocks, unknown nodes,
99
+ images, embeds): visible and rendered, but not text-editable. */
100
+ .ec-editable [data-ec-edit='rich'] [${PRESERVE_NODE_ATTR}] { user-select: none; }
101
+
102
+ /* ── Floating mark toolbar (EC-159), portaled into the iframe ───────────
103
+ All visual declarations !important (contract item 3): the toolbar is
104
+ stylesheet-styled chrome whose button/input elements a site theme's
105
+ element rules would otherwise match. */
106
+ .ec-rt-toolbar { color-scheme: dark !important; }
107
+ .ec-rt-toolbar-row { display: flex !important; align-items: center !important; gap: 2px !important;
108
+ padding: 3px !important; margin: 0 !important;
109
+ border: 1px solid rgba(130,150,190,0.4) !important; border-radius: 8px !important;
110
+ background: rgb(24,26,32) !important; box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; }
111
+ .ec-rt-toolbar-btn { min-width: 26px !important; height: 24px !important; padding: 0 7px !important;
112
+ border: 0 !important; border-radius: 5px !important;
113
+ background: transparent !important; color: rgb(225,228,236) !important;
114
+ font: 600 12px/1 ui-sans-serif, system-ui, sans-serif !important;
115
+ cursor: pointer !important; }
116
+ .ec-rt-toolbar-btn:hover { background: rgba(130,150,190,0.25) !important; }
117
+ .ec-rt-toolbar-btn[data-active] { background: #5b8cff !important; color: white !important; }
118
+ .ec-rt-toolbar-input { width: 200px !important; height: 24px !important; padding: 0 7px !important;
119
+ border-radius: 5px !important;
120
+ border: 1px solid rgba(130,150,190,0.4) !important; background: rgb(16,18,22) !important;
121
+ color: rgb(225,228,236) !important;
122
+ font: 12px/1 ui-sans-serif, system-ui, sans-serif !important; outline: none !important; }
123
+
124
+ /* ── Locale-fallback chips (EC-176) ─────────────────────────────────────
125
+ A node whose bound content is currently served via locale fallback wears a
126
+ small corner chip naming the locale it actually came from — the canvas
127
+ never silently mixes locales. A pseudo-element cannot carry .ec-chrome, so
128
+ every declaration is !important (contract item 3). */
129
+ .ec-editable[data-ec-locale-fallback] { position: relative; }
130
+ .ec-editable[data-ec-locale-fallback]::after {
131
+ content: attr(data-ec-locale-fallback) !important;
132
+ position: absolute !important; top: 2px !important; right: 2px !important;
133
+ z-index: 5 !important; pointer-events: none !important;
134
+ padding: 0 4px !important; border-radius: 4px !important;
135
+ font: 600 9px/1.5 ui-sans-serif, system-ui, sans-serif !important;
136
+ letter-spacing: 0.05em !important; text-transform: uppercase !important;
137
+ color: #fbbf24 !important; background: rgba(180,83,9,0.25) !important;
138
+ border: 1px solid rgba(245,158,11,0.45) !important;
139
+ }
140
+
141
+ /* ── Drag & drop states (EC-160/EC-162) ─────────────────────────────────
142
+ Eligibility comes from the shared pure rules (lib/dnd/drop-rules); these
143
+ classes only *show* the resolution. Rejection is explicit and visible
144
+ during the drag — never a silent failed drop. Before/after bars follow the
145
+ parent's flow axis (data-ec-drop-axis); left/right are the
146
+ wrap-into-columns gesture (violet, paired highlight). Indicator
147
+ declarations !important (contract item 3). */
148
+ .ec-editable[data-ec-drop='into'] { outline: 2px solid #34d399 !important; outline-offset: -2px !important; background-color: rgba(52, 211, 153, 0.08) !important; }
149
+ .ec-editable[data-ec-drop='before'] { box-shadow: 0 -3px 0 0 #38bdf8 !important; }
150
+ .ec-editable[data-ec-drop='after'] { box-shadow: 0 3px 0 0 #38bdf8 !important; }
151
+ .ec-editable[data-ec-drop='before'][data-ec-drop-axis='x'] { box-shadow: -3px 0 0 0 #38bdf8 !important; }
152
+ .ec-editable[data-ec-drop='after'][data-ec-drop-axis='x'] { box-shadow: 3px 0 0 0 #38bdf8 !important; }
153
+ .ec-editable[data-ec-drop='left'] { box-shadow: -3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }
154
+ .ec-editable[data-ec-drop='right'] { box-shadow: 3px 0 0 0 #a78bfa !important; background-color: rgba(167, 139, 250, 0.1) !important; }
155
+ .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; }
156
+ `;
157
+ /**
158
+ * The complete chrome stylesheet for a world — the `ec-outline-css` element's
159
+ * content (id kept from EC-389; the cascade position is pinned by test).
160
+ */
161
+ export function canvasChromeCss(world) {
162
+ return (world === 'legacy' ? LEGACY_BACKDROP_CSS : NEW_WORLD_BACKDROP_CSS) + CHROME_UI_CSS;
163
+ }
164
+ //# 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;;;;GAIG;AACH,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCAsDkB,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, selection/hover outlines, inline editing,\n * rich-text toolbar, locale-fallback chips, drag & 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 /* ── Selection/hover outlines ───────────────────────────────────────────\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); their indicator declarations carry !important\n (contract item 3) so a site theme's element rules can never hide the\n selection state. */\n .ec-editable { outline-offset: -1px !important; cursor: default; }\n .ec-editable[data-hovered]:not([data-selected]) { outline: 1px dashed rgba(130,150,190,0.75) !important; }\n .ec-editable[data-selected] { outline: 2px solid #5b8cff !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;