@snowcone-app/canvas 0.16.0 → 0.17.1

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 (70) hide show
  1. package/dist/{CanvasStateV1-BQD8fCBU.js → CanvasStateV1-C6i6vU6p.js} +63 -63
  2. package/dist/{CanvasStateV1-BQD8fCBU.js.map → CanvasStateV1-C6i6vU6p.js.map} +1 -1
  3. package/dist/EditorCanvasImpl-C8SsT3C2.js +68 -0
  4. package/dist/EditorCanvasImpl-C8SsT3C2.js.map +1 -0
  5. package/dist/EditorContext-CyanYzo2.js +1131 -0
  6. package/dist/EditorContext-CyanYzo2.js.map +1 -0
  7. package/dist/{ImportManager-eotl7r5d.js → ElementFactory-C05JJkRp.js} +1510 -5121
  8. package/dist/ElementFactory-C05JJkRp.js.map +1 -0
  9. package/dist/{HybridHistoryManager-NL3zyEl4.js → HybridHistoryManager-Bht98XRv.js} +98 -98
  10. package/dist/{HybridHistoryManager-NL3zyEl4.js.map → HybridHistoryManager-Bht98XRv.js.map} +1 -1
  11. package/dist/ImportManager-kRYfIX-D.js +3617 -0
  12. package/dist/ImportManager-kRYfIX-D.js.map +1 -0
  13. package/dist/SnowconeCanvas-C3eZ9PP8.js +1047 -0
  14. package/dist/SnowconeCanvas-C3eZ9PP8.js.map +1 -0
  15. package/dist/ThemeContext-Rjz25x9X.js +75 -0
  16. package/dist/ThemeContext-Rjz25x9X.js.map +1 -0
  17. package/dist/UnifiedFontService-BAtqqkU4.js +1478 -0
  18. package/dist/UnifiedFontService-BAtqqkU4.js.map +1 -0
  19. package/dist/advanced.mjs +210 -207
  20. package/dist/advanced.mjs.map +1 -1
  21. package/dist/api/embed.d.ts +8 -4
  22. package/dist/api/testing.d.ts +1 -0
  23. package/dist/components/editor/EditorBuy.d.ts +49 -0
  24. package/dist/components/editor/EditorCanvas.d.ts +30 -0
  25. package/dist/components/editor/EditorCanvasImpl.d.ts +33 -0
  26. package/dist/components/editor/EditorPreview.d.ts +39 -0
  27. package/dist/components/editor/EditorRoot.d.ts +86 -0
  28. package/dist/components/editor/EditorTextField.d.ts +22 -0
  29. package/dist/components/editor/RootInternals.d.ts +77 -0
  30. package/dist/components/editor/designStateStore.d.ts +45 -0
  31. package/dist/components/editor/index.d.ts +23 -0
  32. package/dist/components/embed-realtime/RealtimeCanvas.d.ts +1 -1
  33. package/dist/components/embed-realtime/RealtimeEmbed.d.ts +5 -78
  34. package/dist/components/embed-realtime/RealtimeMockup.d.ts +5 -34
  35. package/dist/components/embed-realtime/context.d.ts +12 -2
  36. package/dist/components/embed-realtime/validateEmbed.d.ts +17 -3
  37. package/dist/design/design-builder.d.ts +81 -6
  38. package/dist/editorError-DMENTZ-3.js +19 -0
  39. package/dist/editorError-DMENTZ-3.js.map +1 -0
  40. package/dist/editorErrors-DbIEtCTt.js +29 -0
  41. package/dist/editorErrors-DbIEtCTt.js.map +1 -0
  42. package/dist/embed-B5kewSQY.js +738 -0
  43. package/dist/embed-B5kewSQY.js.map +1 -0
  44. package/dist/embed.mjs +38 -230
  45. package/dist/embed.mjs.map +1 -1
  46. package/dist/errors/editorError.d.ts +57 -0
  47. package/dist/iconify-D6eVIEy4.js +1089 -0
  48. package/dist/iconify-D6eVIEy4.js.map +1 -0
  49. package/dist/index.mjs +48 -46
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/internals.mjs +105 -104
  52. package/dist/internals.mjs.map +1 -1
  53. package/dist/template-fields-GOM4MNfq.js +643 -0
  54. package/dist/template-fields-GOM4MNfq.js.map +1 -0
  55. package/dist/testing/editorErrors.d.ts +30 -0
  56. package/dist/testing.mjs +28 -23
  57. package/dist/testing.mjs.map +1 -1
  58. package/dist/{usePieceGuideBundle-Cyl695jv.js → usePieceGuideBundle-CGERAQJl.js} +2 -2
  59. package/dist/{usePieceGuideBundle-Cyl695jv.js.map → usePieceGuideBundle-CGERAQJl.js.map} +1 -1
  60. package/dist/validation-DEd6EOCP.js +19947 -0
  61. package/dist/validation-DEd6EOCP.js.map +1 -0
  62. package/dist/workers/export-worker.bundle.string.d.ts +1 -1
  63. package/package.json +12 -4
  64. package/dist/ImportManager-eotl7r5d.js.map +0 -1
  65. package/dist/ThemeContext-D9AaUw9v.js +0 -1200
  66. package/dist/ThemeContext-D9AaUw9v.js.map +0 -1
  67. package/dist/template-fields-PcSPApl5.js +0 -1632
  68. package/dist/template-fields-PcSPApl5.js.map +0 -1
  69. package/dist/validation-BpEod0Rn.js +0 -22953
  70. package/dist/validation-BpEod0Rn.js.map +0 -1
@@ -34,6 +34,10 @@
34
34
  *
35
35
  * @packageDocumentation
36
36
  */
37
+ export { Editor, DesignStateStore } from '../components/editor/index.js';
38
+ export type { EditorRootProps, EditorRootBaseProps, EditorCanvasProps, EditorPreviewProps, EditorTextFieldProps, EditorBuyProps, EditorBuyPayload, BuyMissingReason, DesignState, } from '../components/editor/index.js';
39
+ export { EditorError, isEditorError } from '../errors/editorError.js';
40
+ export type { EditorErrorCode, EditorErrorData } from '../errors/editorError.js';
37
41
  export { RealtimeEmbed } from '../components/embed-realtime/RealtimeEmbed.js';
38
42
  export type { RealtimeEmbedProps, GrantSource } from '../components/embed-realtime/RealtimeEmbed.js';
39
43
  export { RealtimeCanvas } from '../components/embed-realtime/RealtimeCanvas.js';
@@ -43,10 +47,10 @@ export { RealtimeMockup } from '../components/embed-realtime/RealtimeMockup.js';
43
47
  export type { RealtimeMockupProps } from '../components/embed-realtime/RealtimeMockup.js';
44
48
  export { useRealtimeMockups } from '../components/embed-realtime/useRealtimeMockups.js';
45
49
  export type { UseRealtimeMockupsResult } from '../components/embed-realtime/useRealtimeMockups.js';
46
- export { validateEmbed } from '../components/embed-realtime/validateEmbed.js';
47
- export type { ValidateEmbedInput } from '../components/embed-realtime/validateEmbed.js';
48
- export { design, DesignBuilder } from '../design/design-builder.js';
49
- export type { Anchor, ShapeKind, DesignArtboard } from '../design/design-builder.js';
50
+ export { validateEmbed, collectEmbedIssues } from '../components/embed-realtime/validateEmbed.js';
51
+ export type { ValidateEmbedInput, EmbedIssue } from '../components/embed-realtime/validateEmbed.js';
52
+ export { design, DesignBuilder, isLayerToken } from '../design/design-builder.js';
53
+ export type { Anchor, ShapeKind, DesignArtboard, AnyDesign, LayerKind, LayerToken, } from '../design/design-builder.js';
50
54
  export { useEditor } from '../contexts/EditorContext.js';
51
55
  export type { EditorContextValue, EditorProviderProps } from '../contexts/EditorContext.js';
52
56
  export { useLayers } from '../hooks/useLayers.js';
@@ -23,3 +23,4 @@
23
23
  */
24
24
  export { MockEditorProvider, MockEditorProviderWithState, createMockElement, createMockImageElement, createMockArtboard, waitFor, waitForExport, simulateKeyPress, createMockCanvas, assertElementProperties, getElementBounds, } from '../testing/index.js';
25
25
  export type { MockEditorProviderProps, MockEditorElement, } from '../testing/index.js';
26
+ export { expectNoEditorErrors, getContainedEditorErrors, resetContainedEditorErrors, } from '../testing/editorErrors.js';
@@ -0,0 +1,49 @@
1
+ import { default as React, CSSProperties, ReactNode } from 'react';
2
+ import { CartDetail, RenderProduct } from '@snowcone-app/sdk';
3
+ import { ServerRenderRequest } from '../../rendering/serialize-for-server.js';
4
+ import { DesignState } from './designStateStore.js';
5
+ /** A structured incompleteness reason — also serialized (comma-joined) into
6
+ * the button's `data-sc-buy-missing` attribute for agents and styling. */
7
+ export type BuyMissingReason = 'variant' | `layer:${string}` | `layer-seed:${string}` | 'empty-design' | 'quantity';
8
+ export interface EditorBuyPayload {
9
+ productId: string;
10
+ variantId?: string;
11
+ quantity: number;
12
+ /** SDK cart detail (variant resolution + price) when the product carries
13
+ * catalog `options`; `null` for option-less products. */
14
+ cartDetail: CartDetail | null;
15
+ /** The DESIGN STATE at click time (editor shape) — persist this; it is the
16
+ * re-renderable source of truth for the purchased design. */
17
+ designState: DesignState;
18
+ /** The same state serialized for the server render path. */
19
+ serverRequest: ServerRenderRequest;
20
+ }
21
+ export interface EditorBuyProps {
22
+ /** The final submit — the host's one line. Everything before it (variant,
23
+ * quantity, completeness, payload assembly) is this part's job. */
24
+ onAddToCart: (payload: EditorBuyPayload) => void | Promise<void>;
25
+ /** Initial quantity (integer ≥ 1; default 1). */
26
+ quantity?: number;
27
+ /** Button label. Defaults to "Add to cart". */
28
+ children?: ReactNode;
29
+ className?: string;
30
+ style?: CSSProperties;
31
+ }
32
+ /** Pure completeness evaluation — exported for tests. @internal */
33
+ export declare function evaluateBuyCompleteness(input: {
34
+ product: RenderProduct;
35
+ quantity: number;
36
+ textFields: Array<{
37
+ name: string;
38
+ optional: boolean;
39
+ }>;
40
+ textOf: (name: string) => string | undefined;
41
+ seedTextOf: (name: string) => string | undefined;
42
+ hasRenderableElement: boolean;
43
+ }): BuyMissingReason[];
44
+ /**
45
+ * The add-to-cart button, completeness-gated and payload-assembling.
46
+ *
47
+ * @experimental ADR-0085; subject to change until the P4 gate.
48
+ */
49
+ export declare function EditorBuy(props: EditorBuyProps): React.ReactElement;
@@ -0,0 +1,30 @@
1
+ import { default as React, CSSProperties, ReactNode } from 'react';
2
+ import { EditorCanvasImplProps } from './EditorCanvasImpl.js';
3
+ import { SnowconeCanvasHandle } from '../embed/SnowconeCanvas.js';
4
+ type ImplComponent = (typeof import('./EditorCanvasImpl.js'))['EditorCanvasImpl'];
5
+ /** Load the heavy impl chunk. Exposed for the legacy `<RealtimeCanvas>`
6
+ * wrapper so both share ONE lazy boundary (one chunk). @internal */
7
+ export declare function loadCanvasImpl(): Promise<ImplComponent>;
8
+ /** Swap the chunk loader — TEST-ONLY seam for the chunk-load-failure → error
9
+ * card → retry contract (a real `import()` can't be made to fail once and
10
+ * then succeed under the module cache). Returns a restore function.
11
+ * @internal */
12
+ export declare function __setCanvasImplLoaderForTests(loader: () => Promise<ImplComponent>): () => void;
13
+ export interface EditorCanvasProps {
14
+ /** Your overlay controls — rendered inside the editor context, so the
15
+ * binding hooks (`useTextBinding` …) resolve against live elements. */
16
+ children?: ReactNode;
17
+ /** Cap the canvas height in px. */
18
+ maxHeight?: number;
19
+ className?: string;
20
+ style?: CSSProperties;
21
+ }
22
+ /**
23
+ * The editable artboard. Mount at most one under a Root
24
+ * (`E_DUPLICATE_CANVAS`); mount none for the no-canvas personalization
25
+ * composition.
26
+ *
27
+ * @experimental ADR-0085; subject to change until the P4 gate.
28
+ */
29
+ export declare const EditorCanvas: React.ForwardRefExoticComponent<EditorCanvasProps & React.RefAttributes<SnowconeCanvasHandle>>;
30
+ export type { EditorCanvasImplProps };
@@ -0,0 +1,33 @@
1
+ import { default as React, CSSProperties, ReactNode } from 'react';
2
+ import { CanvasState, SnowconeCanvasHandle } from '../embed/SnowconeCanvas.js';
3
+ import { KitPresetId, KitDefinition } from '../../kits/types.js';
4
+ import { CanvasServices } from '../../contexts/CanvasServicesContext.js';
5
+ export interface EditorCanvasImplProps {
6
+ /** Your controls (toolbar, inputs). Rendered as an overlay inside the editor
7
+ * context so binding hooks work. */
8
+ children?: ReactNode;
9
+ /** Editor chrome preset. `Editor.Canvas` pins the chrome-less `'embed-only'`;
10
+ * the legacy `<RealtimeCanvas kit>` prop passes richer presets through. */
11
+ kit?: KitPresetId | KitDefinition;
12
+ /** Inherit the host app's theme instead of managing its own (default true). */
13
+ inheritTheme?: boolean;
14
+ /** Cap the canvas height in px. */
15
+ maxHeight?: number;
16
+ /** Privileged-service wiring override (usually inherited from Root). */
17
+ services?: CanvasServices;
18
+ /** Observe the live design state (the same {@link CanvasState} the Root's
19
+ * state store tracks). Safe to pass inline — read through a ref. */
20
+ onChange?: (state: CanvasState) => void;
21
+ /** Whether the wrapping div self-applies `.sc-root`. The legacy
22
+ * `<RealtimeCanvas>` always did (it could be the only Snowcone DOM on the
23
+ * page); under `Editor.Root` the Root applies it once and parts never
24
+ * self-apply (ADR-0085). Re-applying is harmless (same variables), so the
25
+ * legacy wrapper keeps `true` for bit-identical back-compat. */
26
+ selfScope?: boolean;
27
+ /** `data-sc-part` value for the wrapping div (the parts contract). */
28
+ partAttr?: string;
29
+ className?: string;
30
+ style?: CSSProperties;
31
+ }
32
+ export declare const EditorCanvasImpl: React.ForwardRefExoticComponent<EditorCanvasImplProps & React.RefAttributes<SnowconeCanvasHandle>>;
33
+ export type { CanvasState, SnowconeCanvasHandle };
@@ -0,0 +1,39 @@
1
+ import { default as React, CSSProperties, ReactNode } from 'react';
2
+ export interface EditorPreviewProps {
3
+ /** Which mockup scene to show (a `product.mockupIds` entry). Defaults to the
4
+ * first mockup that has rendered. */
5
+ mockupId?: string;
6
+ /** Shown before the first mockup arrives / while a render is in flight with no
7
+ * prior image. Defaults to nothing. */
8
+ fallback?: ReactNode;
9
+ /** Alt text for the rendered image. */
10
+ alt?: string;
11
+ className?: string;
12
+ style?: CSSProperties;
13
+ }
14
+ /**
15
+ * How far the displayed device-pixel width may exceed the delivered mockup's
16
+ * natural width before {@link resolutionMismatchHint} fires. Renders default to
17
+ * 1000px wide, so a 1000px image inside a ~1100 device-px slot is fine (browser
18
+ * downscale slack), but stretched across 2000 device px it looks soft and the
19
+ * developer deserves to hear why.
20
+ */
21
+ export declare const RESOLUTION_MISMATCH_RATIO = 1.2;
22
+ /** Rounds a device-pixel width up to the next {@link SUGGESTED_WIDTHS} step
23
+ * (capped at the top step). */
24
+ export declare function suggestRenderWidth(devicePx: number): number;
25
+ /**
26
+ * Builds the dev-console warning shown when a delivered mockup's natural width
27
+ * is meaningfully below the width it's displayed at in DEVICE pixels (CSS width
28
+ * × `devicePixelRatio`) — the "my preview looks soft and nothing says why"
29
+ * trap: renders default to 1000px wide, which blurs on a large or high-density
30
+ * display. Returns `null` when the resolution is adequate (within
31
+ * {@link RESOLUTION_MISMATCH_RATIO}) or either measurement is unusable.
32
+ */
33
+ export declare function resolutionMismatchHint(naturalWidth: number, devicePx: number): string | null;
34
+ /**
35
+ * The live product mockup. Mount as many as you need under one Root.
36
+ *
37
+ * @experimental ADR-0085; subject to change until the P4 gate.
38
+ */
39
+ export declare function EditorPreview({ mockupId, fallback, alt, className, style, }: EditorPreviewProps): React.ReactElement;
@@ -0,0 +1,86 @@
1
+ import { default as React, ReactNode } from 'react';
2
+ import { RenderProduct, MockupResult, RealtimeGrant } from '@snowcone-app/sdk';
3
+ import { AnyDesign } from '../../design/design-builder.js';
4
+ import { CanvasServices } from '../../contexts/CanvasServicesContext.js';
5
+ /**
6
+ * How the session authorizes. Provide exactly one — the discriminated union makes
7
+ * "I forgot the grant" a compile error rather than a silent runtime hang.
8
+ *
9
+ * Member order is load-bearing: when NEITHER prop is supplied (the common
10
+ * mistake) TS reports the missing property of the LAST union member, so the
11
+ * `grantUrl` arm is kept last on purpose — the error then reads "Property
12
+ * 'grantUrl' is missing", pointing at the recommended prop instead of `grant`.
13
+ * Don't reorder these.
14
+ */
15
+ export type GrantSource = {
16
+ /** Full control over fetching the grant: a token or a token factory. */
17
+ grant: RealtimeGrant | (() => Promise<RealtimeGrant>);
18
+ grantUrl?: never;
19
+ } | {
20
+ /** Your endpoint returning `{ token, expiresAt }` for `POST { shop }` —
21
+ * a same-origin proxy to `mintRealtimeGrant`. Recommended. */
22
+ grantUrl: string;
23
+ grant?: never;
24
+ };
25
+ export type EditorRootBaseProps = {
26
+ /** Publishable shop id (= shop.id, like a Stripe pk_). Billing target. */
27
+ shop: string;
28
+ /** Product to render against (catalog ids). `mockupIds` are scene codes
29
+ * (product.mockups[].id), NOT placement names. Pass `placements`, `options`
30
+ * + `requiredPlacements` so wiring mistakes (including a multi-placement
31
+ * product, which the embed can't render) fail loud at mount instead of
32
+ * hanging. */
33
+ product: RenderProduct;
34
+ /** Single source of truth — seeds the canvas AND the first render. Build it
35
+ * with `design({ name, width, height })…`. Its artboard `name` is the
36
+ * placement every render targets. */
37
+ design: AnyDesign;
38
+ /** Edit→render throttle in ms (default 200). */
39
+ throttleMs?: number;
40
+ /** Called whenever new mockups arrive. */
41
+ onMockups?: (results: MockupResult[]) => void;
42
+ /** Called on a render/timeout error. */
43
+ onRenderError?: (message: string) => void;
44
+ /** Override the realtime WS endpoint (defaults to the SDK's prod URL). */
45
+ wsUrl?: string;
46
+ /**
47
+ * Privileged-service wiring for the editor chrome (the Image Browser's
48
+ * stock-image search and AI generation tabs). Same pattern as `grantUrl`:
49
+ * host these URLs on your server; your vendor keys live there.
50
+ */
51
+ services?: CanvasServices;
52
+ children?: ReactNode;
53
+ /**
54
+ * Wiring-validation mode. `Editor.Root` defaults to `'throw'` (F3:
55
+ * console.error is invisible to headless agents — error-severity wiring
56
+ * issues throw `EditorError E_EMBED_CONFIG` into Root's boundary). The
57
+ * legacy `<RealtimeEmbed>` wrapper passes `'warn'` for back-compat
58
+ * (dev-only console diagnostics, never throws).
59
+ * @internal
60
+ */
61
+ __validation?: 'throw' | 'warn';
62
+ };
63
+ export type EditorRootProps = GrantSource & EditorRootBaseProps;
64
+ /**
65
+ * How long a render may be in flight before the embed assumes something is wrong
66
+ * and surfaces a diagnostic. A healthy render returns in ~2s; the session's own
67
+ * watchdog is ~30s. 8s sits in between — long enough not to false-fire on a
68
+ * genuinely slow render, short enough to spare a first-timer the full 30s of
69
+ * silence.
70
+ */
71
+ export declare const SLOW_RENDER_HINT_MS = 8000;
72
+ /**
73
+ * The dev-console message shown when a render hasn't produced a mockup in
74
+ * {@link SLOW_RENDER_HINT_MS}. A general backstop for the whole silent-hang
75
+ * class — not just the missing-`variantId` trap — so a stuck embed always says
76
+ * *something* actionable instead of hanging quietly.
77
+ */
78
+ export declare function slowRenderHint(placement: string): string;
79
+ /**
80
+ * The root of an `Editor.*` composition. See the module doc for the state
81
+ * model; see `RealtimeEmbed` for the legacy turnkey wrapper this was
82
+ * extracted from.
83
+ *
84
+ * @experimental ADR-0085; subject to change until the P4 gate.
85
+ */
86
+ export declare function EditorRoot(props: EditorRootProps): React.ReactElement;
@@ -0,0 +1,22 @@
1
+ import { default as React, CSSProperties } from 'react';
2
+ import { LayerToken } from '../../design/design-builder.js';
3
+ export interface EditorTextFieldProps {
4
+ /** The text layer this input edits — mint with `design.layer("name")`.
5
+ * Type-checked against the design's TEXT-layer names when the design is
6
+ * statically built; runtime-validated always. */
7
+ layer: LayerToken<'text'>;
8
+ /** Marked optional ⇒ Buy completeness does not require it (C2/C3). */
9
+ optional?: boolean;
10
+ placeholder?: string;
11
+ /** Accessible label. Defaults to the layer name. */
12
+ 'aria-label'?: string;
13
+ className?: string;
14
+ style?: CSSProperties;
15
+ }
16
+ /**
17
+ * A text input bound to a design layer. Works with or without a mounted
18
+ * `Editor.Canvas`.
19
+ *
20
+ * @experimental ADR-0085; subject to change until the P4 gate.
21
+ */
22
+ export declare function EditorTextField(props: EditorTextFieldProps): React.ReactElement;
@@ -0,0 +1,77 @@
1
+ import { EditorError } from '../../errors/editorError.js';
2
+ import { DesignStateStore } from './designStateStore.js';
3
+ /** Imperative ops a mounted `Editor.Canvas` offers sibling parts. Routes
4
+ * TextField writes through the editor command path (undo/redo, repaint) so
5
+ * the editor document stays the single write authority while a canvas is
6
+ * mounted (ADR-0085 §3). */
7
+ export interface CanvasBridge {
8
+ /** Set a named text layer's content via editor commands. Returns false when
9
+ * no live element carries the name. */
10
+ setTextByName(name: string, text: string): boolean;
11
+ }
12
+ export interface TextFieldRegistration {
13
+ name: string;
14
+ optional: boolean;
15
+ }
16
+ export declare class EditorPartsRegistry {
17
+ private canvasIds;
18
+ private requirements;
19
+ private textFields;
20
+ private bridge;
21
+ private listeners;
22
+ private version;
23
+ /** Monotonic snapshot for `useSyncExternalStore`. */
24
+ getVersion: () => number;
25
+ subscribe: (listener: () => void) => (() => void);
26
+ registerCanvas(instanceId: string): () => void;
27
+ hasCanvas(): boolean;
28
+ /** Declare that a part is meaningless without a mounted Canvas (ADR-0085 §4
29
+ * table). Validated by Root once per commit. */
30
+ requireCanvas(instanceId: string, partName: string): () => void;
31
+ /** Run by Root during RENDER, re-triggered by every registry change — by
32
+ * then the whole commit's registrations are in, so there is no race
33
+ * against the lazy chunk (ADR-0085 §4). Throws land in Root's boundary. */
34
+ validate(): void;
35
+ registerTextField(instanceId: string, reg: TextFieldRegistration): () => void;
36
+ /** Mounted TextField registrations — Buy's required-layer list (C2/C3).
37
+ * Unmounted (incl. conditionally hidden) fields are absent by construction. */
38
+ getTextFields(): TextFieldRegistration[];
39
+ registerCanvasBridge(bridge: CanvasBridge): () => void;
40
+ getCanvasBridge(): CanvasBridge | null;
41
+ private bump;
42
+ }
43
+ export interface EditorRootInternals {
44
+ /** The one live design state under this Root (ADR-0085 §3 / T2). */
45
+ store: DesignStateStore;
46
+ registry: EditorPartsRegistry;
47
+ /**
48
+ * Handler/async-time invariant channel (ADR-0085, D11 containment): React
49
+ * error boundaries only catch render/lifecycle throws, so handlers report
50
+ * here — throws in dev AND test (an agent's test must fail loud), routes to
51
+ * Root's error state (same visible card) in prod.
52
+ */
53
+ reportInvariant(error: EditorError): void;
54
+ /**
55
+ * Runtime-failure channel (NOT a composition mistake — e.g. the lazy
56
+ * renderer chunk failing to load): routes to Root's error card in every
57
+ * env, with a retry affordance. Never throws.
58
+ */
59
+ reportRuntimeError(error: EditorError): void;
60
+ }
61
+ export declare const EditorRootInternalsContext: import('react').Context<EditorRootInternals | null>;
62
+ /**
63
+ * Read the Root internals. Throws `E_OUTSIDE_ROOT` (dev AND prod) when the
64
+ * part is mounted outside `<Editor.Root>` — the most common wiring mistake.
65
+ */
66
+ export declare function useEditorRootInternals(partName: string): EditorRootInternals;
67
+ /**
68
+ * Declare that the calling part requires a mounted `Editor.Canvas` under the
69
+ * same Root (ADR-0085 §4 — Controls/Add/History/Menu in P3; the throw is
70
+ * `E_REQUIRES_CANVAS`, validated race-free by Root once per commit).
71
+ */
72
+ export declare function useRequiresCanvas(partName: string): void;
73
+ /** Shared impl of the invariant channel. @internal */
74
+ export declare function makeInvariantReporter(routeToErrorState: (error: EditorError) => void): {
75
+ reportInvariant: (error: EditorError) => void;
76
+ reportRuntimeError: (error: EditorError) => void;
77
+ };
@@ -0,0 +1,45 @@
1
+ import { AnyElementConfig } from '../../types/index.js';
2
+ /** Editor-shape design state — the same shape `design.toJSON()` produces and
3
+ * `<SnowconeCanvas onChange>` emits (and `serializeStateForServer` accepts). */
4
+ export interface DesignState {
5
+ elements: AnyElementConfig[];
6
+ artboards: Array<{
7
+ name: string;
8
+ width: number;
9
+ height: number;
10
+ backgroundColor?: string;
11
+ exportBackground?: boolean;
12
+ }>;
13
+ activeArtboard: string;
14
+ }
15
+ export declare class DesignStateStore {
16
+ private state;
17
+ /** Frozen snapshot of the values the design was seeded with — Buy's
18
+ * "differs from seed" completeness rule (C3) compares against this. */
19
+ private readonly seedTextByName;
20
+ private readonly listeners;
21
+ constructor(seed: DesignState);
22
+ getState: () => DesignState;
23
+ subscribe: (listener: () => void) => (() => void);
24
+ /** Replace the whole state (the canvas `onChange` path). */
25
+ setState(next: DesignState): void;
26
+ /** The seed value of a named text layer (for completeness rule C3). */
27
+ seedTextOf(name: string): string | undefined;
28
+ /** Current text of a named text layer, or undefined when absent. */
29
+ textOf(name: string): string | undefined;
30
+ /** Kind of the named layer in the CURRENT state ('text' | 'image' | null
31
+ * for unbindable), or undefined when no layer has the name. */
32
+ layerKindOf(name: string): 'text' | 'image' | null | undefined;
33
+ /** Names of bindable layers in the current state, for error messages. */
34
+ bindableLayerNames(): string[];
35
+ /**
36
+ * Direct write path for `Editor.TextField` when NO canvas is mounted (with
37
+ * one, writes route through the editor command path and arrive back here
38
+ * via `setState`). Immutable update; last-write-wins.
39
+ */
40
+ updateTextLayer(name: string, text: string): void;
41
+ /** ≥1 element with visible content (completeness rule C4): non-empty text,
42
+ * an image with a URL, or a shape. Hidden elements don't count. */
43
+ hasRenderableElement(): boolean;
44
+ private emit;
45
+ }
@@ -0,0 +1,23 @@
1
+ import { EditorRoot } from './EditorRoot.js';
2
+ import { EditorPreview } from './EditorPreview.js';
3
+ import { EditorTextField } from './EditorTextField.js';
4
+ import { EditorBuy } from './EditorBuy.js';
5
+ export declare const Editor: {
6
+ /** The one provider boundary — session, design, live state, error boundary. */
7
+ readonly Root: typeof EditorRoot;
8
+ /** The editable artboard (the ONE lazy renderer boundary — D9). */
9
+ readonly Canvas: import('react').ForwardRefExoticComponent<import('./EditorCanvas.js').EditorCanvasProps & import('react').RefAttributes<import('./EditorCanvasImpl.js').SnowconeCanvasHandle>>;
10
+ /** Live product mockup. ×N under one Root is valid. */
11
+ readonly Preview: typeof EditorPreview;
12
+ /** Form input bound to a design text layer by typed token (T1). */
13
+ readonly TextField: typeof EditorTextField;
14
+ /** Smart commerce part: completeness + payload assembly; submit = host callback. */
15
+ readonly Buy: typeof EditorBuy;
16
+ };
17
+ export type { EditorRootProps, EditorRootBaseProps, GrantSource } from './EditorRoot.js';
18
+ export type { EditorCanvasProps } from './EditorCanvas.js';
19
+ export type { EditorPreviewProps } from './EditorPreview.js';
20
+ export type { EditorTextFieldProps } from './EditorTextField.js';
21
+ export type { EditorBuyProps, EditorBuyPayload, BuyMissingReason } from './EditorBuy.js';
22
+ export type { DesignState } from './designStateStore.js';
23
+ export { DesignStateStore } from './designStateStore.js';
@@ -15,7 +15,7 @@ export interface RealtimeCanvasProps {
15
15
  *
16
16
  * To ship a real editor through the turnkey realtime path, pass a richer
17
17
  * preset — `'compact-customizer'` (a trimmed toolbar) or `'pro-studio'` (full
18
- * panels). Every edit still flows to the shared {@link RenderSession} as a
18
+ * panels). Every edit still flows to the shared RenderSession as a
19
19
  * server render automatically; you get add-element / font-size / layers for
20
20
  * free without ejecting to a raw `SnowconeCanvas` + `RenderSession`.
21
21
  *
@@ -1,79 +1,6 @@
1
- import { default as React, ReactNode } from 'react';
2
- import { RenderProduct, MockupResult, RealtimeGrant } from '@snowcone-app/sdk';
3
- import { DesignBuilder } from '../../design/design-builder.js';
4
- import { CanvasServices } from '../../contexts/CanvasServicesContext.js';
5
- /**
6
- * How the session authorizes. Provide exactly one — the discriminated union makes
7
- * "I forgot the grant" a compile error rather than a silent runtime hang.
8
- *
9
- * Member order is load-bearing: when NEITHER prop is supplied (the common
10
- * mistake) TS reports the missing property of the LAST union member, so the
11
- * `grantUrl` arm is kept last on purpose — the error then reads "Property
12
- * 'grantUrl' is missing", pointing at the recommended prop instead of `grant`.
13
- * Don't reorder these.
14
- */
15
- export type GrantSource = {
16
- /** Full control over fetching the grant: a token or a token factory. */
17
- grant: RealtimeGrant | (() => Promise<RealtimeGrant>);
18
- grantUrl?: never;
19
- } | {
20
- /** Your endpoint returning `{ token, expiresAt }` for `POST { shop }` —
21
- * a same-origin proxy to `mintRealtimeGrant`. Recommended. */
22
- grantUrl: string;
23
- grant?: never;
24
- };
25
- export type RealtimeEmbedProps = GrantSource & {
26
- /** Publishable shop id (= shop.id, like a Stripe pk_). Billing target. */
27
- shop: string;
28
- /** Product to render against (catalog ids). `mockupIds` are scene codes
29
- * (product.mockups[].id), NOT placement names. Pass `placements`, `options`
30
- * + `requiredPlacements` so wiring mistakes (including a multi-placement
31
- * product, which the embed can't render) fail loud at mount instead of
32
- * hanging. */
33
- product: RenderProduct;
34
- /** Single source of truth — seeds the canvas AND the first render. Build it
35
- * with `design({ name, width, height })…`. Its artboard `name` is the
36
- * placement every render targets. */
37
- design: DesignBuilder;
38
- /** Edit→render throttle in ms (default 200). */
39
- throttleMs?: number;
40
- /** Called whenever new mockups arrive. */
41
- onMockups?: (results: MockupResult[]) => void;
42
- /** Called on a render/timeout error. */
43
- onRenderError?: (message: string) => void;
44
- /** Override the realtime WS endpoint (defaults to the SDK's prod URL). */
45
- wsUrl?: string;
46
- /**
47
- * Privileged-service wiring for the editor chrome (the Image Browser's
48
- * stock-image search and AI generation tabs). Same pattern as `grantUrl`:
49
- * host these URLs on your server; your vendor keys live there. Flows to
50
- * every `<RealtimeCanvas>` inside this embed. Tabs without a configured
51
- * service do not render.
52
- *
53
- * @example
54
- * ```tsx
55
- * <RealtimeEmbed
56
- * services={{ imageSearchUrl: '/api/image-search', generateUrl: '/api/generate-image' }}
57
- * …
58
- * />
59
- * ```
60
- */
61
- services?: CanvasServices;
62
- children?: ReactNode;
63
- };
64
- /**
65
- * How long a render may be in flight before the embed assumes something is wrong
66
- * and surfaces a diagnostic. A healthy render returns in ~2s; the session's own
67
- * watchdog is ~30s. 8s sits in between — long enough not to false-fire on a
68
- * genuinely slow render, short enough to spare a first-timer the full 30s of
69
- * silence.
70
- */
71
- export declare const SLOW_RENDER_HINT_MS = 8000;
72
- /**
73
- * The dev-console message shown when a render hasn't produced a mockup in
74
- * {@link SLOW_RENDER_HINT_MS}. A general backstop for the whole silent-hang
75
- * class — not just the missing-`variantId` trap — so a stuck embed always says
76
- * *something* actionable instead of hanging quietly.
77
- */
78
- export declare function slowRenderHint(placement: string): string;
1
+ import { default as React } from 'react';
2
+ import { EditorRootBaseProps, GrantSource } from '../editor/EditorRoot.js';
3
+ export { SLOW_RENDER_HINT_MS, slowRenderHint } from '../editor/EditorRoot.js';
4
+ export type { GrantSource };
5
+ export type RealtimeEmbedProps = GrantSource & Omit<EditorRootBaseProps, '__validation'>;
79
6
  export declare function RealtimeEmbed(props: RealtimeEmbedProps): React.ReactElement;
@@ -1,34 +1,5 @@
1
- import { default as React, CSSProperties, ReactNode } from 'react';
2
- export interface RealtimeMockupProps {
3
- /** Which mockup scene to show (a `product.mockupIds` entry). Defaults to the
4
- * first mockup that has rendered. */
5
- mockupId?: string;
6
- /** Shown before the first mockup arrives / while a render is in flight with no
7
- * prior image. Defaults to nothing. */
8
- fallback?: ReactNode;
9
- /** Alt text for the rendered image. */
10
- alt?: string;
11
- className?: string;
12
- style?: CSSProperties;
13
- }
14
- /**
15
- * How far the displayed device-pixel width may exceed the delivered mockup's
16
- * natural width before {@link resolutionMismatchHint} fires. Renders default to
17
- * 1000px wide, so a 1000px image inside a ~1100 device-px slot is fine (browser
18
- * downscale slack), but stretched across 2000 device px it looks soft and the
19
- * developer deserves to hear why.
20
- */
21
- export declare const RESOLUTION_MISMATCH_RATIO = 1.2;
22
- /** Rounds a device-pixel width up to the next {@link SUGGESTED_WIDTHS} step
23
- * (capped at the top step). */
24
- export declare function suggestRenderWidth(devicePx: number): number;
25
- /**
26
- * Builds the dev-console warning shown when a delivered mockup's natural width
27
- * is meaningfully below the width it's displayed at in DEVICE pixels (CSS width
28
- * × `devicePixelRatio`) — the "my preview looks soft and nothing says why"
29
- * trap: renders default to 1000px wide, which blurs on a large or high-density
30
- * display. Returns `null` when the resolution is adequate (within
31
- * {@link RESOLUTION_MISMATCH_RATIO}) or either measurement is unusable.
32
- */
33
- export declare function resolutionMismatchHint(naturalWidth: number, devicePx: number): string | null;
34
- export declare function RealtimeMockup({ mockupId, fallback, alt, className, style, }: RealtimeMockupProps): React.ReactElement;
1
+ import { default as React } from 'react';
2
+ import { EditorPreviewProps } from '../editor/EditorPreview.js';
3
+ export { RESOLUTION_MISMATCH_RATIO, resolutionMismatchHint, suggestRenderWidth, } from '../editor/EditorPreview.js';
4
+ export type RealtimeMockupProps = EditorPreviewProps;
5
+ export declare function RealtimeMockup(props: RealtimeMockupProps): React.ReactElement;
@@ -1,6 +1,7 @@
1
1
  import { RenderSession, RenderProduct, RenderState, MockupResult } from '@snowcone-app/sdk';
2
- import { DesignBuilder } from '../../design/design-builder.js';
2
+ import { AnyDesign } from '../../design/design-builder.js';
3
3
  import { PieceGuidesConfig } from '../embed/SnowconeCanvas.js';
4
+ import { DesignStateStore } from '../editor/designStateStore.js';
4
5
  export interface EmbedContextValue {
5
6
  /** The single session the editor pushes renders to. `null` if the session
6
7
  * failed to construct (a config fail-fast, surfaced via `error`). Escape
@@ -8,7 +9,7 @@ export interface EmbedContextValue {
8
9
  session: RenderSession | null;
9
10
  /** The design the embed was seeded with — single source of truth for both the
10
11
  * canvas seed and the seed render. */
11
- design: DesignBuilder;
12
+ design: AnyDesign;
12
13
  /** The placement name (= the design's artboard name). Both the canvas artboard
13
14
  * and every `renderState` call use this; derived once so it can't drift. */
14
15
  placement: string;
@@ -32,6 +33,15 @@ export interface EmbedContextValue {
32
33
  pieceGuides: PieceGuidesConfig | null;
33
34
  /** Dispatch a server render for the embed's placement. Sets `isRendering`. */
34
35
  pushRender: (state: RenderState, throttleMs?: number) => void;
36
+ /**
37
+ * The ONE live design state under this Root (ADR-0085 §3 / T2): seeded from
38
+ * `design`, updated by every canvas change and every `Editor.TextField`
39
+ * write, last-write-wins. Commerce parts read THIS, never the seed. Stable
40
+ * identity; subscribe for updates (`useSyncExternalStore`-shaped).
41
+ *
42
+ * @experimental ADR-0085; subject to change until the P4 gate.
43
+ */
44
+ stateStore: DesignStateStore;
35
45
  }
36
46
  export declare const EmbedContext: import('react').Context<EmbedContextValue | undefined>;
37
47
  /**
@@ -1,9 +1,23 @@
1
1
  import { RenderProduct } from '@snowcone-app/sdk';
2
- import { DesignBuilder } from '../../design/design-builder.js';
2
+ import { AnyDesign } from '../../design/design-builder.js';
3
3
  export interface ValidateEmbedInput {
4
4
  shop: string;
5
5
  product: RenderProduct;
6
- design: DesignBuilder;
6
+ design: AnyDesign;
7
7
  hasGrant: boolean;
8
8
  }
9
- export declare function validateEmbed({ shop, product, design, hasGrant }: ValidateEmbedInput): void;
9
+ /** One wiring problem found at mount. `error`-severity issues break the embed
10
+ * (no render will ever arrive / it targets the wrong thing); `warn`-severity
11
+ * issues are advisory. */
12
+ export interface EmbedIssue {
13
+ severity: 'error' | 'warn';
14
+ /** Stable issue key, carried into `EditorError.data.issue` in throw mode. */
15
+ issue: 'missing-shop' | 'missing-grant' | 'empty-mockup-ids' | 'variant-required' | 'multi-placement' | 'placement-mismatch' | 'duplicate-layer-names';
16
+ message: string;
17
+ }
18
+ /** Find every wiring problem. Pure — no logging, no throwing. */
19
+ export declare function collectEmbedIssues({ shop, product, design, hasGrant }: ValidateEmbedInput): EmbedIssue[];
20
+ /** Legacy advisory API: log everything, never throw. (`placement-mismatch`
21
+ * keeps its historical `console.warn` level here; in `Editor.Root`'s throw
22
+ * mode it is error-severity per F3.) */
23
+ export declare function validateEmbed(input: ValidateEmbedInput): void;