@snowcone-app/canvas 0.23.0 → 0.27.0
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.
- package/ERROR_CONTRACT.md +638 -0
- package/README.md +2 -1
- package/STYLING_CONTRACT.md +59 -8
- package/dist/{CanvasRenderer-6_HhR4-q.js → CanvasRenderer-yszzrDfo.js} +4 -4
- package/dist/{CanvasRenderer-6_HhR4-q.js.map → CanvasRenderer-yszzrDfo.js.map} +1 -1
- package/dist/{CanvasStateV1-DmZ1Qvvn.js → CanvasStateV1-BTbCD_xC.js} +3 -3
- package/dist/{CanvasStateV1-DmZ1Qvvn.js.map → CanvasStateV1-BTbCD_xC.js.map} +1 -1
- package/dist/{ContextualToolbars-BDEt9ND0.js → ContextualToolbars-BHjYsril.js} +6 -6
- package/dist/{ContextualToolbars-BDEt9ND0.js.map → ContextualToolbars-BHjYsril.js.map} +1 -1
- package/dist/{EditorCanvasImpl-Ckg7WO2j.js → EditorCanvasImpl-Ui8Ceb-h.js} +8 -8
- package/dist/{EditorCanvasImpl-Ckg7WO2j.js.map → EditorCanvasImpl-Ui8Ceb-h.js.map} +1 -1
- package/dist/{ExportManager-DoU4VK6h.js → ExportManager-BTcyj4cf.js} +2 -2
- package/dist/{ExportManager-DoU4VK6h.js.map → ExportManager-BTcyj4cf.js.map} +1 -1
- package/dist/{FontAnalyzer-CwIpgTGd.js → FontAnalyzer-EF3gLPr8.js} +30 -20
- package/dist/{FontAnalyzer-CwIpgTGd.js.map → FontAnalyzer-EF3gLPr8.js.map} +1 -1
- package/dist/{ImagePanel-BwqFDKvW.js → ImagePanel-CIk6z41z.js} +6 -6
- package/dist/{ImagePanel-BwqFDKvW.js.map → ImagePanel-CIk6z41z.js.map} +1 -1
- package/dist/{SnowconeCanvas-D1sLcIja.js → SnowconeCanvas-DuWALOAt.js} +10 -10
- package/dist/{SnowconeCanvas-D1sLcIja.js.map → SnowconeCanvas-DuWALOAt.js.map} +1 -1
- package/dist/{UnifiedFontService-CC3CSmH5.js → UnifiedFontService-CHVYDBDW.js} +869 -836
- package/dist/{UnifiedFontService-CC3CSmH5.js.map → UnifiedFontService-CHVYDBDW.js.map} +1 -1
- package/dist/advanced.mjs +14 -14
- package/dist/api/embed.d.ts +6 -2
- package/dist/components/editor/EditorLoadingOverlay.d.ts +12 -0
- package/dist/components/editor/EditorPlacements.d.ts +14 -0
- package/dist/components/editor/EditorPreview.d.ts +32 -8
- package/dist/components/editor/EditorRoot.d.ts +12 -0
- package/dist/components/editor/PreviewPrismOverlay.d.ts +23 -0
- package/dist/components/editor/RootInternals.d.ts +31 -0
- package/dist/components/editor/editorAudit.d.ts +66 -0
- package/dist/components/editor/index.d.ts +14 -0
- package/dist/components/editor/usePlacements.d.ts +14 -0
- package/dist/components/embed-realtime/context.d.ts +11 -2
- package/dist/components/embed-realtime/validateEmbed.d.ts +20 -1
- package/dist/contexts/EditorContext.d.ts +34 -0
- package/dist/core/HybridHistoryManager.d.ts +7 -0
- package/dist/core/elementGuards.d.ts +19 -0
- package/dist/{custom-icons-BW6hqmkL.js → custom-icons-CcLMyybv.js} +7 -7
- package/dist/{custom-icons-BW6hqmkL.js.map → custom-icons-CcLMyybv.js.map} +1 -1
- package/dist/design/design-builder.d.ts +41 -3
- package/dist/design/product-config.d.ts +10 -0
- package/dist/{design-builder-2VUjfmdt.js → design-builder-cZY958Rw.js} +109 -61
- package/dist/design-builder-cZY958Rw.js.map +1 -0
- package/dist/editorErrors-BhrZ_nwj.js +1219 -0
- package/dist/editorErrors-BhrZ_nwj.js.map +1 -0
- package/dist/embed-CJswRZte.js +1586 -0
- package/dist/embed-CJswRZte.js.map +1 -0
- package/dist/embed.mjs +42 -34
- package/dist/errors/editorError.d.ts +29 -1
- package/dist/{guards-8rkFAMBs.js → guards-DIX8aXWM.js} +2 -2
- package/dist/{guards-8rkFAMBs.js.map → guards-DIX8aXWM.js.map} +1 -1
- package/dist/hooks/elementPatch.d.ts +96 -0
- package/dist/hooks/useCommands.d.ts +20 -0
- package/dist/index.mjs +8 -8
- package/dist/internals.mjs +9 -9
- package/dist/style.css +1 -1
- package/dist/template-fields-BXl29sgh.js +716 -0
- package/dist/template-fields-BXl29sgh.js.map +1 -0
- package/dist/testing.mjs +4 -4
- package/dist/{usePieceGuideBundle-CbJJg66S.js → usePieceGuideBundle-DE21DQGZ.js} +2 -2
- package/dist/{usePieceGuideBundle-CbJJg66S.js.map → usePieceGuideBundle-DE21DQGZ.js.map} +1 -1
- package/dist/{validation-C9Fe9ahK.js → validation-DGXAQmUC.js} +11 -11
- package/dist/{validation-C9Fe9ahK.js.map → validation-DGXAQmUC.js.map} +1 -1
- package/example/package.json +1 -1
- package/llms.txt +3 -0
- package/package.json +7 -4
- package/dist/design-builder-2VUjfmdt.js.map +0 -1
- package/dist/editorErrors-B2mb1sVN.js +0 -1208
- package/dist/editorErrors-B2mb1sVN.js.map +0 -1
- package/dist/embed-DxsaqZJF.js +0 -1226
- package/dist/embed-DxsaqZJF.js.map +0 -1
- package/dist/template-fields-CQ6n2P4V.js +0 -548
- package/dist/template-fields-CQ6n2P4V.js.map +0 -1
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
# `Editor.*` error contract
|
|
2
|
+
|
|
3
|
+
<!-- GENERATED — do not edit. Derived from the EditorError/EmbedIssue source
|
|
4
|
+
of truth by packages/canvas/scripts/gen-error-contract.mjs.
|
|
5
|
+
Regenerate: pnpm -C packages/canvas gen:error-contract -->
|
|
6
|
+
|
|
7
|
+
Every composition/runtime violation in the `Editor.*` parts throws an
|
|
8
|
+
`EditorError`: a stable machine-readable `code` plus a structured
|
|
9
|
+
`error.data` payload (always including `code`, usually `docSlug` and the
|
|
10
|
+
valid alternatives). Throws fire dev AND prod and are contained by
|
|
11
|
+
`Editor.Root`'s error boundary. **Assert on `code` + `data` fields, never
|
|
12
|
+
on message prose** — messages are rendered from the data and may be
|
|
13
|
+
reworded. Every message names the violated contract and the corrective
|
|
14
|
+
code, so the fix is in the message itself. `${…}` in the templates below
|
|
15
|
+
marks runtime facts.
|
|
16
|
+
|
|
17
|
+
Advisory wiring problems (severity `warn`) never throw: they reach the
|
|
18
|
+
host through `Editor.Root`'s `onIssue` channel as `EmbedIssue` objects —
|
|
19
|
+
see [Embed wiring issues](#embed-wiring-issues-the-onissue-channel).
|
|
20
|
+
Error-severity wiring issues escalate to a thrown `E_EMBED_CONFIG` with
|
|
21
|
+
the issue key in `error.data.issue`.
|
|
22
|
+
|
|
23
|
+
## Code index
|
|
24
|
+
|
|
25
|
+
| code | severity | fires when |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| [`E_OUTSIDE_ROOT`](#e_outside_root) | throw | An `Editor.*` part mounted outside `<Editor.Root>`. |
|
|
28
|
+
| [`E_LAYER_NOT_FOUND`](#e_layer_not_found) | throw | A layer token (or its name) matches no layer in the mounted design. |
|
|
29
|
+
| [`E_LAYER_WRONG_KIND`](#e_layer_wrong_kind) | throw | The layer exists but is not the kind the part binds (e.g. an image layer passed to `Editor.TextField`). |
|
|
30
|
+
| [`E_LAYER_TOKEN_REQUIRED`](#e_layer_token_required) | throw | `Editor.TextField` received a bare string (or other non-token) — layers bind by `design.layer("name")` token only (ADR-0085 §1). |
|
|
31
|
+
| [`E_BOUND_LAYER_MUTATION`](#e_bound_layer_mutation) | throw | Programmatic rename/delete of a layer a mounted TextField is bound to. |
|
|
32
|
+
| [`E_DUPLICATE_CANVAS`](#e_duplicate_canvas) | throw | Two `Editor.Canvas` parts mounted under one Root. |
|
|
33
|
+
| [`E_REQUIRES_CANVAS`](#e_requires_canvas) | throw | A part that needs a mounted `Editor.Canvas` found none under this Root. |
|
|
34
|
+
| [`E_BUY_NO_PRODUCT`](#e_buy_no_product) | throw | `Editor.Buy` mounted without product context. |
|
|
35
|
+
| [`E_CHUNK_LOAD_FAILED`](#e_chunk_load_failed) | throw | The lazy renderer chunk failed to load (network/CDN). Retryable. |
|
|
36
|
+
| [`E_EMBED_CONFIG`](#e_embed_config) | throw | Embed wiring invalid at mount (missing grant/mockupIds, variant or placement mismatch) — the throw-mode upgrade of `validateEmbed` (F3). |
|
|
37
|
+
| [`E_PRODUCT_NOT_RENDERABLE`](#e_product_not_renderable) | throw | `productToRenderConfig`: the catalog product has no renderable image placement / no mockup scenes, or the requested placement doesn't exist. |
|
|
38
|
+
| [`E_DUPLICATE_ARTBOARD`](#e_duplicate_artboard) | throw | `design().artboard()`: an artboard with this label already exists in the design (artboard names are placement labels — unique by contract). |
|
|
39
|
+
| [`E_UNKNOWN_PLACEMENT`](#e_unknown_placement) | throw | `usePlacements().select()`: the label names no artboard in the design (ADR-0088). Lists the valid placement labels. |
|
|
40
|
+
| [`E_UNKNOWN_OPTION`](#e_unknown_option) | throw | `select()` named an option key/choice that doesn't exist on the product (Buy-owned selection, ADR-0085 §2 / P2). |
|
|
41
|
+
| [`E_COLOR_ROLE_COLLISION`](#e_color_role_collision) | throw | `design({ colors })`: two color roles share one (normalized) hex value. `useColorBinding` matches by VALUE, so colliding roles would capture each other's elements — rejected at mint time instead of failing subtly. |
|
|
42
|
+
| [`E_COLOR_ROLE_INVALID`](#e_color_role_invalid) | throw | `design({ colors })`: a role's value is not a `#RGB`/`#RRGGBB` hex (value-matched bindings normalize hex; other syntaxes can't bind). |
|
|
43
|
+
| [`E_COLOR_ROLE_NOT_FOUND`](#e_color_role_not_found) | throw | `design.color(name)`: no color role with this name — lists the design's declared roles. |
|
|
44
|
+
| [`E_PREVIEW_NO_LOADER`](#e_preview_no_loader) | throw | `Editor.Preview loadingOverlay` got `false`/an unknown value. A mockup preview ALWAYS has a render-in-flight treatment (a server render takes real time, and the loader paints OVER the still-visible previous mockup) — keep the default, pass a variant/custom element, or declare `"host-owned"` with your own indicator. Silence-then-pop-in is a composition mistake, not a preference. |
|
|
45
|
+
| [`E_INVALID_PATCH`](#e_invalid_patch) | throw | `updateElement(target, patch)`: a patch key doesn't apply to the target element's kind (e.g. `fontSize` on a shape), or a value is out of range (opacity outside 0..1, fontSize/width/height <= 0, non-finite number, wrong primitive type). The message names the element kind and lists the valid keys for it. Dev AND prod. |
|
|
46
|
+
| [`E_NO_ACTIVE_ARTBOARD`](#e_no_active_artboard) | throw | `addElement(spec)`: there is no active artboard to add the element to. Create or select an artboard first (`<Editor.Root>` seeds one automatically; with a raw `EditorProvider` use `executeCreateArtboard`). Replaces the old `handleAddElement` window.alert — agents read throws, not dialogs. |
|
|
47
|
+
| [`E_ASSET_FORBIDDEN`](#e_asset_forbidden) | throw | `assertRenders`: the shop's asset-origin allowlist denied a design asset (translated from the server's 403 — F4). |
|
|
48
|
+
| [`E_RENDER_FAILED`](#e_render_failed) | throw | `assertRenders`: the render failed or produced no usable image (F4). |
|
|
49
|
+
| [`E_BINDING_INERT`](#e_binding_inert) | throw | `assertRenders`: the mutated render is byte-identical to the seed render — the binding under test is dead (the two-render contract, F4). |
|
|
50
|
+
| [`E_NO_MUTABLE_BINDING`](#e_no_mutable_binding) | throw | `assertRenders`: the design has no named text layer to mutate and no `mutate` override was given (F4). |
|
|
51
|
+
| [`E_AUDIT_NO_ROOT`](#e_audit_no_root) | throw | `auditEditor()` called with no `<Editor.Root>` mounted — the imperative audit reads the most-recent mounted Root's registry (#10). |
|
|
52
|
+
| [`missing-shop`](#missing-shop) | error → throws `E_EMBED_CONFIG` | No `shop` (the publishable shop id) was passed. |
|
|
53
|
+
| [`missing-grant`](#missing-grant) | error → throws `E_EMBED_CONFIG` | No grant source (`grantUrl`/`grant`) — the session can never authorize, so no mockup ever renders. |
|
|
54
|
+
| [`empty-mockup-ids`](#empty-mockup-ids) | error → throws `E_EMBED_CONFIG` | `product.mockupIds` is empty — nothing to render. |
|
|
55
|
+
| [`variant-required`](#variant-required) | error → throws `E_EMBED_CONFIG` | The product has a variant axis but no `variantId` — the render would hang ~30s waiting on a color blob that never comes. |
|
|
56
|
+
| [`multi-placement`](#multi-placement) | error → throws `E_EMBED_CONFIG` | The product requires several placements but this embed renders ONE — the server times out waiting on the rest (needs the eject path). |
|
|
57
|
+
| [`placement-mismatch`](#placement-mismatch) | error → throws `E_EMBED_CONFIG` | The design's placement isn't one of the product's placements — the server renders a blank mockup. |
|
|
58
|
+
| [`duplicate-layer-names`](#duplicate-layer-names) | warn (advisory) | Two layers share a name — name-bound hooks bind the FIRST match, so a control may edit the wrong layer. |
|
|
59
|
+
| [`slow-render`](#slow-render) | warn (advisory) | Runtime (not mount-time): a render has been in flight past `SLOW_RENDER_HINT_MS` with no mockup and no error — the silent-hang class. The render may still arrive; this is a heads-up. |
|
|
60
|
+
|
|
61
|
+
## `EditorError` codes
|
|
62
|
+
|
|
63
|
+
### `E_OUTSIDE_ROOT`
|
|
64
|
+
|
|
65
|
+
**Fires when:** An `Editor.*` part mounted outside `<Editor.Root>`.
|
|
66
|
+
|
|
67
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
68
|
+
|
|
69
|
+
- `src/components/editor/RootInternals.ts`
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
<${partName}> must be rendered inside <Editor.Root>. Wrap it: <Editor.Root shop={…} product={…} design={…} grantUrl={…}><${partName}/></Editor.Root>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`error.data`: `code`, `part`, `docSlug`
|
|
76
|
+
|
|
77
|
+
### `E_LAYER_NOT_FOUND`
|
|
78
|
+
|
|
79
|
+
**Fires when:** A layer token (or its name) matches no layer in the mounted design.
|
|
80
|
+
|
|
81
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
82
|
+
|
|
83
|
+
- `src/components/editor/EditorTextField.tsx`
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
no layer "${layer.name}" in this Root's design — the token was minted from a different design than the one passed to <Editor.Root design={…}>. Bindable layers here: ${JSON.stringify(stateStore.bindableLayerNames())}.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`error.data`: `code`, `layerName`, `validNames`, `docSlug`
|
|
90
|
+
|
|
91
|
+
- `src/components/editor/EditorTextField.tsx`
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
layer "${layer.name}" no longer exists in the mounted canvas — it was removed or renamed after this <Editor.TextField> bound to it. Bound layers must not be renamed or deleted while a field is mounted (ADR-0085 §3).
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`error.data`: `code`, `layerName`, `docSlug`
|
|
98
|
+
|
|
99
|
+
- `src/components/editor/designStateStore.ts`
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
no layer "${name}" in the current design state. Bindable layers: ${JSON.stringify(this.bindableLayerNames())}.
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`error.data`: `code`, `layerName`, `validNames`, `docSlug`
|
|
106
|
+
|
|
107
|
+
- `src/design/design-builder.ts`
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
no layer "${name}" in this design. Text layers: ${JSON.stringify(textNames)}; image layers: ${JSON.stringify(imageNames)}. Fix the name, or add the layer first — e.g. design(…).text('…', { name: '${name}' }).
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`error.data`: `code`, `layerName`, `validNames`, `textNames`, `imageNames`, `docSlug`
|
|
114
|
+
|
|
115
|
+
- `src/hooks/bindingGuards.ts`
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
no layer "${name}" in this Root's document — the token was minted from a different design than the one passed to <Editor.Root design={…}>, or the layer was deleted. Bindable layers here: ${JSON.stringify(named.map((e) => e.name))}.
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`error.data`: `code`, `layerName`, `validNames`, `docSlug`
|
|
122
|
+
|
|
123
|
+
- `src/hooks/elementPatch.ts`
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
no element with ${isToken ? 'layer name' : 'id or layer name'} "${key}" in this document. Valid layer names: ${JSON.stringify(names)}; valid ids: ${JSON.stringify(all.map((el) => el.id))}.
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`error.data`: `code`, `layerName`, `validNames`, `validIds`, `docSlug`
|
|
130
|
+
|
|
131
|
+
### `E_LAYER_WRONG_KIND`
|
|
132
|
+
|
|
133
|
+
**Fires when:** The layer exists but is not the kind the part binds (e.g. an image layer passed to `Editor.TextField`).
|
|
134
|
+
|
|
135
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
136
|
+
|
|
137
|
+
- `src/components/editor/EditorTextField.tsx`
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
layer "${layer.name}" is ${kindNow === null ? 'not a bindable kind' : `an ${kindNow} layer`} — <Editor.TextField> binds text layers only.
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`error.data`: `code`, `layerName`, `actualKind`, `docSlug`
|
|
144
|
+
|
|
145
|
+
- `src/design/design-builder.ts`
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
layer "${name}" is a ${(el as { transformType?: string }).transformType} element — only text and image layers are bindable. Bindable layers: ${JSON.stringify([...this.layerNamesOfKind('text'), ...this.layerNamesOfKind('image')])}.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`error.data`: `code`, `layerName`, `actualType`, `docSlug`
|
|
152
|
+
|
|
153
|
+
- `src/hooks/bindingGuards.ts`
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
layer "${name}" is a ${layerOrName.kind} token — this hook binds ${expectedKind} layers only (use ${HOOK_BY_KIND[layerOrName.kind]}).
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`error.data`: `code`, `layerName`, `actualKind`, `expectedKind`, `docSlug`
|
|
160
|
+
|
|
161
|
+
- `src/hooks/bindingGuards.ts`
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
layer "${name}" is ${actual === 'unbindable' ? 'not a bindable kind' : `an ${actual} layer`} — this hook binds ${expectedKind} layers only.
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`error.data`: `code`, `layerName`, `actualKind`, `expectedKind`, `docSlug`
|
|
168
|
+
|
|
169
|
+
### `E_LAYER_TOKEN_REQUIRED`
|
|
170
|
+
|
|
171
|
+
**Fires when:** `Editor.TextField` received a bare string (or other non-token) — layers bind by `design.layer("name")` token only (ADR-0085 §1).
|
|
172
|
+
|
|
173
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
174
|
+
|
|
175
|
+
- `src/components/editor/EditorTextField.tsx`
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
the `layer` prop must be a token minted by the design — pass layer={design.layer("name")}, not ${typeof layer === 'string' ? `the bare string "${layer}"` : 'a plain value'}. Tokens validate the name against the design's layers at mint time.
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`error.data`: `code`, `received`, `docSlug`
|
|
182
|
+
|
|
183
|
+
### `E_BOUND_LAYER_MUTATION`
|
|
184
|
+
|
|
185
|
+
**Fires when:** Programmatic rename/delete of a layer a mounted TextField is bound to.
|
|
186
|
+
|
|
187
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
188
|
+
|
|
189
|
+
- `src/hooks/useCommandHistory.ts`
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
cannot ${operation} layer "${name}" — a mounted <${bound.part} layer={design.layer("${name}")}> is bound to it. Unmount that field first, or keep the layer and set its text through the field / its binding instead.
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`error.data`: `code`, `layer`, `part`, `operation`, `docSlug`
|
|
196
|
+
|
|
197
|
+
### `E_DUPLICATE_CANVAS`
|
|
198
|
+
|
|
199
|
+
**Fires when:** Two `Editor.Canvas` parts mounted under one Root.
|
|
200
|
+
|
|
201
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
202
|
+
|
|
203
|
+
- `src/components/editor/RootInternals.ts`
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
two <Editor.Canvas> parts are mounted under one <Editor.Root>. A Root owns exactly one editable artboard — remove one Canvas, or mount a second <Editor.Root> for a second editor. (Multiple <Editor.Preview> parts ARE valid.)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`error.data`: `code`, `docSlug`
|
|
210
|
+
|
|
211
|
+
### `E_REQUIRES_CANVAS`
|
|
212
|
+
|
|
213
|
+
**Fires when:** A part that needs a mounted `Editor.Canvas` found none under this Root.
|
|
214
|
+
|
|
215
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
216
|
+
|
|
217
|
+
- `src/components/editor/RootInternals.ts`
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
${parts.join(', ')} require${parts.length === 1 ? 's' : ''} an <Editor.Canvas> under the same <Editor.Root> — they operate on the editable artboard (selection, editor commands). Mount <Editor.Canvas/>, or remove the part. Canvas-free parts: Editor.Preview, Editor.Buy, Editor.TextField, Editor.Layers.
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`error.data`: `code`, `parts`, `docSlug`
|
|
224
|
+
|
|
225
|
+
### `E_BUY_NO_PRODUCT`
|
|
226
|
+
|
|
227
|
+
**Fires when:** `Editor.Buy` mounted without product context.
|
|
228
|
+
|
|
229
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
230
|
+
|
|
231
|
+
- `src/components/editor/EditorBuy.tsx`
|
|
232
|
+
|
|
233
|
+
```text
|
|
234
|
+
<Editor.Buy> has no product context — pass `product` (with `productId`) on <Editor.Root product={…}>. Buy assembles its cart payload from the Root product and the live design state.
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`error.data`: `code`, `docSlug`
|
|
238
|
+
|
|
239
|
+
### `E_CHUNK_LOAD_FAILED`
|
|
240
|
+
|
|
241
|
+
**Fires when:** The lazy renderer chunk failed to load (network/CDN). Retryable.
|
|
242
|
+
|
|
243
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
244
|
+
|
|
245
|
+
- `src/components/editor/EditorCanvas.tsx`
|
|
246
|
+
|
|
247
|
+
```text
|
|
248
|
+
the editor's rendering chunk failed to load (${err instanceof Error ? err.message : String(err)}). This is a network/CDN failure, not a composition mistake — retry, or check the deployment serves the @snowcone-app/canvas lazy chunks.
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`error.data`: `code`, `cause`, `docSlug`
|
|
252
|
+
|
|
253
|
+
- `src/components/editor/EditorControls.tsx`
|
|
254
|
+
|
|
255
|
+
```text
|
|
256
|
+
the editor's controls chunk failed to load (${err instanceof Error ? err.message : String(err)}). This is a network/CDN failure, not a composition mistake — retry, or check the deployment serves the @snowcone-app/canvas lazy chunks.
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`error.data`: `code`, `cause`, `docSlug`
|
|
260
|
+
|
|
261
|
+
- `src/components/editor/LazyImageBrowser.tsx`
|
|
262
|
+
|
|
263
|
+
```text
|
|
264
|
+
the editor's image-browser chunk failed to load (${err instanceof Error ? err.message : String(err)}). This is a network/CDN failure, not a composition mistake — retry, or check the deployment serves the @snowcone-app/canvas lazy chunks.
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`error.data`: `code`, `cause`, `docSlug`
|
|
268
|
+
|
|
269
|
+
### `E_EMBED_CONFIG`
|
|
270
|
+
|
|
271
|
+
**Fires when:** Embed wiring invalid at mount (missing grant/mockupIds, variant or placement mismatch) — the throw-mode upgrade of `validateEmbed` (F3).
|
|
272
|
+
|
|
273
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
274
|
+
|
|
275
|
+
- `src/components/editor/EditorRoot.tsx`
|
|
276
|
+
|
|
277
|
+
```text
|
|
278
|
+
${fatal.message}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`error.data`: `code`, `issue`, `docSlug`
|
|
282
|
+
|
|
283
|
+
### `E_PRODUCT_NOT_RENDERABLE`
|
|
284
|
+
|
|
285
|
+
**Fires when:** `productToRenderConfig`: the catalog product has no renderable image placement / no mockup scenes, or the requested placement doesn't exist.
|
|
286
|
+
|
|
287
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
288
|
+
|
|
289
|
+
- `src/design/product-config.ts`
|
|
290
|
+
|
|
291
|
+
```text
|
|
292
|
+
product ${catalogProduct.id} has no renderable placement "${options.placement}". Renderable image placements: [${renderableLabels.join(', ')}].
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`error.data`: `code`, `productId`, `requested`, `validNames`, `docSlug`
|
|
296
|
+
|
|
297
|
+
- `src/design/product-config.ts`
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
product ${catalogProduct.id} has no renderable image placement (a non-color placement with width/height). The catalog payload may be incomplete — fetch it via getProduct(), not a search hit.
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
`error.data`: `code`, `productId`, `docSlug`
|
|
304
|
+
|
|
305
|
+
- `src/design/product-config.ts`
|
|
306
|
+
|
|
307
|
+
```text
|
|
308
|
+
product ${catalogProduct.id} has no mockup scenes — nothing to render a preview into.
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`error.data`: `code`, `productId`, `docSlug`
|
|
312
|
+
|
|
313
|
+
### `E_DUPLICATE_ARTBOARD`
|
|
314
|
+
|
|
315
|
+
**Fires when:** `design().artboard()`: an artboard with this label already exists in the design (artboard names are placement labels — unique by contract).
|
|
316
|
+
|
|
317
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
318
|
+
|
|
319
|
+
- `src/design/design-builder.ts`
|
|
320
|
+
|
|
321
|
+
```text
|
|
322
|
+
the design already has an artboard "${label}" — artboard names are placement labels and must be unique. Existing: ${JSON.stringify(this.allArtboards.map((a) => a.name))}.
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`error.data`: `code`, `artboardName`, `validNames`, `docSlug`
|
|
326
|
+
|
|
327
|
+
### `E_UNKNOWN_PLACEMENT`
|
|
328
|
+
|
|
329
|
+
**Fires when:** `usePlacements().select()`: the label names no artboard in the design (ADR-0088). Lists the valid placement labels.
|
|
330
|
+
|
|
331
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
332
|
+
|
|
333
|
+
- `src/components/editor/EditorRoot.tsx`
|
|
334
|
+
|
|
335
|
+
```text
|
|
336
|
+
no placement "${label}" in this design — its artboards are ${JSON.stringify(placements)}. Add it to the seed with design(…).artboard('${label}', { width, height }) (ADR-0088).
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`error.data`: `code`, `placement`, `validNames`, `docSlug`
|
|
340
|
+
|
|
341
|
+
### `E_UNKNOWN_OPTION`
|
|
342
|
+
|
|
343
|
+
**Fires when:** `select()` named an option key/choice that doesn't exist on the product (Buy-owned selection, ADR-0085 §2 / P2).
|
|
344
|
+
|
|
345
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
346
|
+
|
|
347
|
+
- `src/components/editor/commerceStore.ts`
|
|
348
|
+
|
|
349
|
+
```text
|
|
350
|
+
product ${this.product.productId} has no option "${key}". Valid option keys: ${JSON.stringify(Object.keys(this.attrs))}.
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
`error.data`: `code`, `key`, `validKeys`, `docSlug`
|
|
354
|
+
|
|
355
|
+
- `src/components/editor/commerceStore.ts`
|
|
356
|
+
|
|
357
|
+
```text
|
|
358
|
+
"${choice}" is not a choice of option "${key}" on product ${this.product.productId}. Valid choices: ${JSON.stringify(labels)}.
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
`error.data`: `code`, `key`, `choice`, `validChoices`, `docSlug`
|
|
362
|
+
|
|
363
|
+
### `E_COLOR_ROLE_COLLISION`
|
|
364
|
+
|
|
365
|
+
**Fires when:** `design({ colors })`: two color roles share one (normalized) hex value. `useColorBinding` matches by VALUE, so colliding roles would capture each other's elements — rejected at mint time instead of failing subtly.
|
|
366
|
+
|
|
367
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
368
|
+
|
|
369
|
+
- `src/design/design-builder.ts`
|
|
370
|
+
|
|
371
|
+
```text
|
|
372
|
+
color roles "${collision[0]}" and "${roleName}" both normalize to ${value}. useColorBinding matches elements by VALUE, so colliding roles would repaint each other's elements — give every role a distinct hex.
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`error.data`: `code`, `roles`, `value`, `docSlug`
|
|
376
|
+
|
|
377
|
+
### `E_COLOR_ROLE_INVALID`
|
|
378
|
+
|
|
379
|
+
**Fires when:** `design({ colors })`: a role's value is not a `#RGB`/`#RRGGBB` hex (value-matched bindings normalize hex; other syntaxes can't bind).
|
|
380
|
+
|
|
381
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
382
|
+
|
|
383
|
+
- `src/design/design-builder.ts`
|
|
384
|
+
|
|
385
|
+
```text
|
|
386
|
+
color role "${roleName}" is ${JSON.stringify(raw)} — roles must be #RGB/#RRGGBB hex (useColorBinding matches by normalized hex value; other syntaxes can't bind).
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
`error.data`: `code`, `roleName`, `value`, `docSlug`
|
|
390
|
+
|
|
391
|
+
### `E_COLOR_ROLE_NOT_FOUND`
|
|
392
|
+
|
|
393
|
+
**Fires when:** `design.color(name)`: no color role with this name — lists the design's declared roles.
|
|
394
|
+
|
|
395
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
396
|
+
|
|
397
|
+
- `src/design/design-builder.ts`
|
|
398
|
+
|
|
399
|
+
```text
|
|
400
|
+
no color role "${name}" in this design. Declared roles: ${JSON.stringify([...this.colorRoles.keys()])}. Declare roles up front — design({ …, colors: { ${name}: '#FF2D8A' } }).
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
`error.data`: `code`, `roleName`, `validNames`, `docSlug`
|
|
404
|
+
|
|
405
|
+
### `E_PREVIEW_NO_LOADER`
|
|
406
|
+
|
|
407
|
+
**Fires when:** `Editor.Preview loadingOverlay` got `false`/an unknown value. A mockup preview ALWAYS has a render-in-flight treatment (a server render takes real time, and the loader paints OVER the still-visible previous mockup) — keep the default, pass a variant/custom element, or declare `"host-owned"` with your own indicator. Silence-then-pop-in is a composition mistake, not a preference.
|
|
408
|
+
|
|
409
|
+
**Docs:** <https://developers.snowcone.app/preview-loading-overlay> (`error.data.docSlug`)
|
|
410
|
+
|
|
411
|
+
- `src/components/editor/EditorPreview.tsx`
|
|
412
|
+
|
|
413
|
+
```text
|
|
414
|
+
Editor.Preview: invalid loadingOverlay value ${JSON.stringify(value)}. A mockup preview always has a render-in-flight treatment (the loader paints over the still-visible previous mockup). Omit the prop for the default rainbow overlay, pass "dark" | "light", pass a custom ReactElement, or declare "host-owned" if the host renders its own indicator off data-sc-preview-state / <Editor.LoadingOverlay>.
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
`error.data`: `code`, `docSlug`
|
|
418
|
+
|
|
419
|
+
### `E_INVALID_PATCH`
|
|
420
|
+
|
|
421
|
+
**Fires when:** `updateElement(target, patch)`: a patch key doesn't apply to the target element's kind (e.g. `fontSize` on a shape), or a value is out of range (opacity outside 0..1, fontSize/width/height <= 0, non-finite number, wrong primitive type). The message names the element kind and lists the valid keys for it. Dev AND prod.
|
|
422
|
+
|
|
423
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
424
|
+
|
|
425
|
+
- `src/hooks/elementPatch.ts`
|
|
426
|
+
|
|
427
|
+
```text
|
|
428
|
+
${message}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
`error.data`: `code`, `...data`, `docSlug`
|
|
432
|
+
|
|
433
|
+
### `E_NO_ACTIVE_ARTBOARD`
|
|
434
|
+
|
|
435
|
+
**Fires when:** `addElement(spec)`: there is no active artboard to add the element to. Create or select an artboard first (`<Editor.Root>` seeds one automatically; with a raw `EditorProvider` use `executeCreateArtboard`). Replaces the old `handleAddElement` window.alert — agents read throws, not dialogs.
|
|
436
|
+
|
|
437
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
438
|
+
|
|
439
|
+
- `src/contexts/EditorContext.tsx`
|
|
440
|
+
|
|
441
|
+
```text
|
|
442
|
+
addElement({ type: "${elementType}" }) needs an active artboard and none exists. <Editor.Root> seeds one automatically; with a raw EditorProvider create one first (executeCreateArtboard) or select one (artboardManager.setActiveArtboard).
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
`error.data`: `code`, `type`, `docSlug`
|
|
446
|
+
|
|
447
|
+
### `E_ASSET_FORBIDDEN`
|
|
448
|
+
|
|
449
|
+
**Fires when:** `assertRenders`: the shop's asset-origin allowlist denied a design asset (translated from the server's 403 — F4).
|
|
450
|
+
|
|
451
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
452
|
+
|
|
453
|
+
- `src/testing/assertRenders.ts`
|
|
454
|
+
|
|
455
|
+
```text
|
|
456
|
+
the shop's asset-origin allowlist denied an asset in this design (${label} render). Allow the asset's origin on the shop (or host it on the shop's storage origin).
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
`error.data`: `code`, `phase`, `url`, `failedAssets`, `docSlug`
|
|
460
|
+
|
|
461
|
+
### `E_RENDER_FAILED`
|
|
462
|
+
|
|
463
|
+
**Fires when:** `assertRenders`: the render failed or produced no usable image (F4).
|
|
464
|
+
|
|
465
|
+
- `src/testing/assertRenders.ts`
|
|
466
|
+
|
|
467
|
+
```text
|
|
468
|
+
the ${label} render produced no mockup within ${timeoutMs}ms.
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
`error.data`: `code`, `phase`, `timeoutMs`
|
|
472
|
+
|
|
473
|
+
- `src/testing/assertRenders.ts`
|
|
474
|
+
|
|
475
|
+
```text
|
|
476
|
+
the ${label} render failed: ${message}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
`error.data`: `code`, `phase`, `error`
|
|
480
|
+
|
|
481
|
+
- `src/testing/assertRenders.ts`
|
|
482
|
+
|
|
483
|
+
```text
|
|
484
|
+
the ${label} render failed to dispatch: ${message}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
`error.data`: `code`, `phase`, `error`
|
|
488
|
+
|
|
489
|
+
- `src/testing/assertRenders.ts`
|
|
490
|
+
|
|
491
|
+
```text
|
|
492
|
+
the ${label} render's image fetch failed: ${err.message}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
`error.data`: `code`, `phase`, `url`, `...err.data`
|
|
496
|
+
|
|
497
|
+
### `E_BINDING_INERT`
|
|
498
|
+
|
|
499
|
+
**Fires when:** `assertRenders`: the mutated render is byte-identical to the seed render — the binding under test is dead (the two-render contract, F4).
|
|
500
|
+
|
|
501
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
502
|
+
|
|
503
|
+
- `src/testing/assertRenders.ts`
|
|
504
|
+
|
|
505
|
+
```text
|
|
506
|
+
the mutated render is byte-identical to the seed render — the mutation never reached the server, so the binding under test is DEAD. Usual causes: the mutated layer name doesn't exist in the design (check design.layer() names), or opts.mutate returned the seed unchanged.
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
`error.data`: `code`, `seedUrl`, `mutatedUrl`, `bytes`, `docSlug`
|
|
510
|
+
|
|
511
|
+
### `E_NO_MUTABLE_BINDING`
|
|
512
|
+
|
|
513
|
+
**Fires when:** `assertRenders`: the design has no named text layer to mutate and no `mutate` override was given (F4).
|
|
514
|
+
|
|
515
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
516
|
+
|
|
517
|
+
- `src/testing/assertRenders.ts`
|
|
518
|
+
|
|
519
|
+
```text
|
|
520
|
+
this design has no NAMED text layer to mutate for the two-render check. Name one (design().text("…", { name: "headline" })) or pass opts.mutate to define the mutation.
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
`error.data`: `code`, `docSlug`
|
|
524
|
+
|
|
525
|
+
### `E_AUDIT_NO_ROOT`
|
|
526
|
+
|
|
527
|
+
**Fires when:** `auditEditor()` called with no `<Editor.Root>` mounted — the imperative audit reads the most-recent mounted Root's registry (#10).
|
|
528
|
+
|
|
529
|
+
**Docs:** <https://developers.snowcone.app/embed-mode> (`error.data.docSlug`)
|
|
530
|
+
|
|
531
|
+
- `src/components/editor/editorAudit.ts`
|
|
532
|
+
|
|
533
|
+
```text
|
|
534
|
+
auditEditor() found no mounted <Editor.Root>. Mount a Root first — or, to audit at mount time, pass onAudit on the Root: <Editor.Root onAudit={(report) => …}>.
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
`error.data`: `code`, `docSlug`
|
|
538
|
+
|
|
539
|
+
## Embed wiring issues (the `onIssue` channel)
|
|
540
|
+
|
|
541
|
+
Mount-time wiring problems and runtime stalls are `EmbedIssue` objects
|
|
542
|
+
(`{ severity, issue, message }`) delivered to `Editor.Root`'s `onIssue`
|
|
543
|
+
prop in every env. `warn`-severity issues are advisory — the embed still
|
|
544
|
+
mounts and renders what it can. `error`-severity issues break the embed,
|
|
545
|
+
so `Editor.Root` throws them as `E_EMBED_CONFIG` (the issue key is in
|
|
546
|
+
`error.data.issue`).
|
|
547
|
+
|
|
548
|
+
### `missing-shop`
|
|
549
|
+
|
|
550
|
+
**Severity:** error (throws as `E_EMBED_CONFIG`)
|
|
551
|
+
|
|
552
|
+
**Fires when:** No `shop` (the publishable shop id) was passed.
|
|
553
|
+
|
|
554
|
+
- `src/components/embed-realtime/validateEmbed.ts`
|
|
555
|
+
|
|
556
|
+
```text
|
|
557
|
+
`shop` is required (your publishable shop id).
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### `missing-grant`
|
|
561
|
+
|
|
562
|
+
**Severity:** error (throws as `E_EMBED_CONFIG`)
|
|
563
|
+
|
|
564
|
+
**Fires when:** No grant source (`grantUrl`/`grant`) — the session can never authorize, so no mockup ever renders.
|
|
565
|
+
|
|
566
|
+
- `src/components/embed-realtime/validateEmbed.ts`
|
|
567
|
+
|
|
568
|
+
```text
|
|
569
|
+
no grant source — pass `grantUrl` (a proxy to mintRealtimeGrant) or `grant`. Without it the session can never authorize and no mockup renders.
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### `empty-mockup-ids`
|
|
573
|
+
|
|
574
|
+
**Severity:** error (throws as `E_EMBED_CONFIG`)
|
|
575
|
+
|
|
576
|
+
**Fires when:** `product.mockupIds` is empty — nothing to render.
|
|
577
|
+
|
|
578
|
+
- `src/components/embed-realtime/validateEmbed.ts`
|
|
579
|
+
|
|
580
|
+
```text
|
|
581
|
+
product.mockupIds is empty — pass the catalog scene codes (product.mockups[].id) you want rendered, e.g. ["FV1qjO"].
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### `variant-required`
|
|
585
|
+
|
|
586
|
+
**Severity:** error (throws as `E_EMBED_CONFIG`)
|
|
587
|
+
|
|
588
|
+
**Fires when:** The product has a variant axis but no `variantId` — the render would hang ~30s waiting on a color blob that never comes.
|
|
589
|
+
|
|
590
|
+
- `src/components/embed-realtime/validateEmbed.ts`
|
|
591
|
+
|
|
592
|
+
```text
|
|
593
|
+
${err instanceof Error ? err.message : String(err)}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### `multi-placement`
|
|
597
|
+
|
|
598
|
+
**Severity:** error (throws as `E_EMBED_CONFIG`)
|
|
599
|
+
|
|
600
|
+
**Fires when:** The product requires several placements but this embed renders ONE — the server times out waiting on the rest (needs the eject path).
|
|
601
|
+
|
|
602
|
+
- `src/components/embed-realtime/validateEmbed.ts`
|
|
603
|
+
|
|
604
|
+
```text
|
|
605
|
+
product ${product.productId} requires placements [${required.join(', ')}] but the design only has artboards [${artboardNames.join(', ')}] — missing [${missing.join(', ')}]. The render would time out waiting for the missing canvas_states. Add each missing placement to the seed: design(…).artboard('${missing[0]}', { width, height })… (ADR-0088), or use the eject path — see https://developers.snowcone.app/embed-mode#eject.
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### `placement-mismatch`
|
|
609
|
+
|
|
610
|
+
**Severity:** error (throws as `E_EMBED_CONFIG`)
|
|
611
|
+
|
|
612
|
+
**Fires when:** The design's placement isn't one of the product's placements — the server renders a blank mockup.
|
|
613
|
+
|
|
614
|
+
- `src/components/embed-realtime/validateEmbed.ts`
|
|
615
|
+
|
|
616
|
+
```text
|
|
617
|
+
the design's artboard(s) [${foreign.join(', ')}] are not in product.placements [${product.placements!.join(', ')}]. Every artboard name must equal a real product placement label (renderState targets placements by that name), or the server renders a blank mockup. Fix the `name` passed to design({ name }) / design().artboard(name, …).
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### `duplicate-layer-names`
|
|
621
|
+
|
|
622
|
+
**Severity:** warn (advisory)
|
|
623
|
+
|
|
624
|
+
**Fires when:** Two layers share a name — name-bound hooks bind the FIRST match, so a control may edit the wrong layer.
|
|
625
|
+
|
|
626
|
+
- `src/components/embed-realtime/validateEmbed.ts`
|
|
627
|
+
|
|
628
|
+
```text
|
|
629
|
+
duplicate layer name(s) [${[...new Set(dupes)].join(', ')}] — useTextBinding(name) / useImageBinding(name) bind to the FIRST match, so a duplicate name makes a control edit the wrong layer. Keep names unique.
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### `slow-render`
|
|
633
|
+
|
|
634
|
+
**Severity:** warn (advisory)
|
|
635
|
+
|
|
636
|
+
**Fires when:** Runtime (not mount-time): a render has been in flight past `SLOW_RENDER_HINT_MS` with no mockup and no error — the silent-hang class. The render may still arrive; this is a heads-up.
|
|
637
|
+
|
|
638
|
+
- `src/components/editor/EditorRoot.tsx`
|
package/README.md
CHANGED
|
@@ -135,7 +135,8 @@ Every part renders minimal DOM tagged `data-sc-part` and themes through
|
|
|
135
135
|
`.sc-root` CSS custom properties — the full generated reference is
|
|
136
136
|
[`STYLING_CONTRACT.md`](./STYLING_CONTRACT.md). Composition mistakes throw
|
|
137
137
|
structured `EditorError`s (stable `code` + `error.data`) in dev AND prod,
|
|
138
|
-
contained by the Root's error boundary
|
|
138
|
+
contained by the Root's error boundary — the full generated code catalog is
|
|
139
|
+
[`ERROR_CONTRACT.md`](./ERROR_CONTRACT.md). Test with
|
|
139
140
|
`@snowcone-app/canvas/testing`: `assertRenders` proves a design's bindings
|
|
140
141
|
are alive; `expectNoEditorErrors()` makes contained errors fail your tests.
|
|
141
142
|
|