@snowcone-app/sdk 0.1.15 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#220](https://github.com/snowcone-app/snowcone-monorepo/pull/220) [`aa318b5`](https://github.com/snowcone-app/snowcone-monorepo/commit/aa318b51a15e977ecb993c81ae30b6afcf6864b8) Thanks [@kevinsproles](https://github.com/kevinsproles)! - Add realtime saved-design-state API: `createDesignState` (top-level export for building a design state payload) and `RenderSession.renderSavedState(placement, stateId)` (render a previously-saved design state in a realtime session). These were already in `main` and documented on developers.snowcone.app but were missing from the published `@snowcone-app/sdk@0.1.15`, so docs examples failed to compile against the npm package.
8
+
3
9
  All notable changes to the Merch SDK will be documented in this file.
4
10
 
5
11
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
@@ -8,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
14
  ## [0.1.9] - 2025-10-16
9
15
 
10
16
  ### Added
17
+
11
18
  - **DOM-Based URL Resolution** - Intelligent URL resolution for transformed assets
12
19
  - Added `resolveUrlFromDOM()` to find actual rendered URLs in the DOM
13
20
  - Automatically resolves Vercel Blob Storage URLs from v0.dev
@@ -15,18 +22,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
22
  - Perfect for environments where image URLs are transformed at runtime
16
23
 
17
24
  ### Changed
25
+
18
26
  - **Enhanced URL Normalization** - Multi-tier resolution strategy
19
27
  1. First attempts to find actual URL in DOM (for v0/Blob Storage)
20
28
  2. Falls back to `window.location.origin` conversion
21
29
  3. Warns about localhost URLs for better developer experience
22
30
 
23
31
  ### Fixed
32
+
24
33
  - Relative URLs now work correctly in v0.dev and Vercel Blob Storage environments
25
34
  - Mockup generation can access transformed image URLs with hash suffixes
26
35
 
27
36
  ## [0.1.8] - 2025-10-16
28
37
 
29
38
  ### Added
39
+
30
40
  - **Automatic URL Normalization** - Relative artwork URLs are now automatically converted to absolute URLs
31
41
  - Added `normalizeImageUrl()` utility function in `utils/url.ts`
32
42
  - Supports `/images/art.jpg`, `./art.jpg`, `../art.jpg` patterns
@@ -34,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
34
44
  - SSR-safe with helpful warnings
35
45
 
36
46
  ### Changed
47
+
37
48
  - **Design Generation** - All artwork URLs are normalized in `createDesignForPlacements()`
38
49
  - Handles placement designs, provided images, and default URLs
39
50
  - Single source of truth for URL normalization
@@ -42,12 +53,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
42
53
  - Clear error messages for invalid formats
43
54
 
44
55
  ### Fixed
56
+
45
57
  - Artwork URLs no longer require absolute paths - relative paths work seamlessly
46
58
  - Better developer experience matching Next.js image patterns
47
59
 
48
60
  ## [0.1.7] - 2025-10-16
49
61
 
50
62
  ### Changed
63
+
51
64
  - Version bump to maintain parity with @snowcone-app/ui 0.1.11
52
65
  - No functional changes in this release
53
66
 
package/README.md CHANGED
@@ -167,25 +167,60 @@ const url = getMockupUrl('hoodie-black', {
167
167
 
168
168
  See [Public or signed](https://developers.snowcone.app/get-started) for the full picture.
169
169
 
170
- ### React Hooks (Optional)
170
+ ### Realtime: render a saved canvas, no pixel upload
171
171
 
172
- For React applications, use the React-specific exports:
172
+ `getMockupUrl` composites **one** artwork onto a product. For a full multi-layer
173
+ design — the kind the Snowcone canvas editor produces — use a **`RenderSession`**:
174
+ the browser sends a ~1 KB description of the canvas (or just a saved design's id)
175
+ over a WebSocket, and the server rasterizes it and **fetches the referenced assets
176
+ itself**. No per-placement PNG is ever uploaded, so it's fast on thin/mobile
177
+ clients. This is exactly how the Snowcone PDP renders mockups (see ADR-0079).
178
+
179
+ Authorize the session with a short-lived **grant**. Mint it server-side with a
180
+ secret API key (`sk_…`, scope `mockups:realtime`) so the key never reaches the
181
+ browser. Create the key — and find your publishable `shop.id` — on the
182
+ [API keys page](https://snowcone.app/api-keys):
173
183
 
174
184
  ```typescript
175
- import { useRealtimeMockup } from '@snowcone-app/sdk/react';
176
-
177
- function MyComponent() {
178
- const { mockupUrl, isGenerating } = useRealtimeMockup({
179
- productId: 'BEEB77',
180
- artworkUrl: artwork.src,
181
- placement: 'Front',
182
- alignment: 'center'
185
+ // YOUR backend route, e.g. POST /api/realtime/grant
186
+ import { mintRealtimeGrant } from '@snowcone-app/sdk';
187
+
188
+ export async function POST(req: Request) {
189
+ const { shop } = await req.json();
190
+ const grant = await mintRealtimeGrant({
191
+ apiKey: process.env.SNOWCONE_API_KEY!, // secret — server only
192
+ shop, // = shop.id (publishable)
183
193
  });
184
-
185
- return <img src={mockupUrl} alt="Product mockup" />;
194
+ return Response.json(grant); // { token, expiresAt }
186
195
  }
187
196
  ```
188
197
 
198
+ In the browser, point a `RenderSession` at that proxy and render. Render a **saved**
199
+ design by id (no canvas JSON needed) or a live `serializeStateForServer(...)` state:
200
+
201
+ ```typescript
202
+ import { RenderSession } from '@snowcone-app/sdk';
203
+
204
+ const session = new RenderSession({
205
+ shop: 'YOUR_SHOP_ID', // publishable, like Stripe pk_
206
+ grantUrl: '/api/realtime/grant', // your proxy above
207
+ product: { productId: 'BEEB77', mockupIds: ['Front'] },
208
+ });
209
+
210
+ session.onMockups((results) => { img.src = results[0].imageUrl; });
211
+
212
+ // (a) render a SAVED canvas by id — ideal for fulfillment / agents
213
+ await session.renderSavedState('Front', 'design-state-id');
214
+
215
+ // (b) or render a live canvas state from @snowcone-app/canvas
216
+ // import { serializeStateForServer } from '@snowcone-app/canvas';
217
+ // await session.renderState('Front', serializeStateForServer(canvasState));
218
+ ```
219
+
220
+ React apps can use the lower-level `useRealtimeMockup({ getToken })` hook from
221
+ `@snowcone-app/sdk/react` instead. Full walkthrough:
222
+ [Realtime server-side render](https://developers.snowcone.app/realtime).
223
+
189
224
  ## TypeScript Support
190
225
 
191
226
  Full TypeScript definitions are included. Import types:
@@ -472,6 +472,32 @@ var RealtimeMockupService = class {
472
472
  }
473
473
  return true;
474
474
  }
475
+ /**
476
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). Sends a `stateId`
477
+ * instead of inline state; the server resolves it, fetches the referenced
478
+ * assets, and renders — nothing is uploaded and the client need not hold the
479
+ * canvas JSON. One-shot (no throttle). Results arrive like any other render.
480
+ */
481
+ sendCanvasStateRef(placement, stateId) {
482
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.isConfigured) {
483
+ const reason = !this.ws ? "no WebSocket" : this.ws.readyState !== WebSocket.OPEN ? `ws.readyState=${this.ws.readyState}` : "not configured";
484
+ console.log(`[WS] sendCanvasStateRef BLOCKED for "${placement}": ${reason}`);
485
+ return false;
486
+ }
487
+ this.lastSentVersion = ++this.requestVersion;
488
+ this.latestSentVersionByPlacement[placement] = this.lastSentVersion;
489
+ const message = JSON.stringify({
490
+ type: "canvas_state",
491
+ placement,
492
+ version: this.lastSentVersion,
493
+ stateId
494
+ });
495
+ this.ws.send(message);
496
+ this.lastBlobSentAt = Date.now();
497
+ this.addLog(`Sent canvas stateId "${stateId}" for "${placement}" (v${this.lastSentVersion})`, "sent");
498
+ this.callbacks.onBlobSent?.(placement);
499
+ return true;
500
+ }
475
501
  sendCanvasStateImmediately(placement, state) {
476
502
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
477
503
  console.log(`[WS] sendCanvasStateImmediately ABORTED: ws not open`);
package/dist/index.cjs CHANGED
@@ -76,6 +76,7 @@ __export(index_exports, {
76
76
  createComponent: () => createComponent,
77
77
  createContextProvider: () => createContextProvider,
78
78
  createDesignForPlacements: () => createDesignForPlacements,
79
+ createDesignState: () => createDesignState,
79
80
  createDevFetcher: () => createDevFetcher,
80
81
  createErrorHandler: () => createErrorHandler,
81
82
  createEventManager: () => createEventManager,
@@ -5276,6 +5277,32 @@ var RealtimeMockupService = class {
5276
5277
  }
5277
5278
  return true;
5278
5279
  }
5280
+ /**
5281
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). Sends a `stateId`
5282
+ * instead of inline state; the server resolves it, fetches the referenced
5283
+ * assets, and renders — nothing is uploaded and the client need not hold the
5284
+ * canvas JSON. One-shot (no throttle). Results arrive like any other render.
5285
+ */
5286
+ sendCanvasStateRef(placement, stateId) {
5287
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.isConfigured) {
5288
+ const reason = !this.ws ? "no WebSocket" : this.ws.readyState !== WebSocket.OPEN ? `ws.readyState=${this.ws.readyState}` : "not configured";
5289
+ console.log(`[WS] sendCanvasStateRef BLOCKED for "${placement}": ${reason}`);
5290
+ return false;
5291
+ }
5292
+ this.lastSentVersion = ++this.requestVersion;
5293
+ this.latestSentVersionByPlacement[placement] = this.lastSentVersion;
5294
+ const message = JSON.stringify({
5295
+ type: "canvas_state",
5296
+ placement,
5297
+ version: this.lastSentVersion,
5298
+ stateId
5299
+ });
5300
+ this.ws.send(message);
5301
+ this.lastBlobSentAt = Date.now();
5302
+ this.addLog(`Sent canvas stateId "${stateId}" for "${placement}" (v${this.lastSentVersion})`, "sent");
5303
+ this.callbacks.onBlobSent?.(placement);
5304
+ return true;
5305
+ }
5279
5306
  sendCanvasStateImmediately(placement, state) {
5280
5307
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
5281
5308
  console.log(`[WS] sendCanvasStateImmediately ABORTED: ws not open`);
@@ -5566,11 +5593,32 @@ var RenderSession = class {
5566
5593
  * the assets referenced inside — no pixels are uploaded. Results arrive via
5567
5594
  * {@link RenderSession.onMockups}. Safe to call repeatedly (e.g. on every
5568
5595
  * canvas edit); pass `throttleMs` to debounce during live dragging.
5596
+ *
5597
+ * `placement` is the print-area NAME (e.g. `'Front'`, the product's
5598
+ * `placements[].label`) — a different id from the product's `mockupIds`, and it
5599
+ * must match an artboard in `state`. The server renders only once it has a state
5600
+ * for EVERY required placement, so on a multi-placement product call this once
5601
+ * per placement; a missing one surfaces as an `incomplete_canvas_placements`
5602
+ * error on {@link RenderSession.onError}.
5603
+ *
5604
+ * `state` is the flattened `serializeStateForServer()` shape — NOT the
5605
+ * un-flattened shape `createDesignState` takes.
5569
5606
  */
5570
5607
  async renderState(placement, state, throttleMs = 0) {
5571
5608
  await this.connect();
5572
5609
  this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
5573
5610
  }
5611
+ /**
5612
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
5613
+ * the `stateId`, fetches the referenced assets, and renders — no pixels are
5614
+ * uploaded and you don't need to hold the canvas JSON. Ideal for re-rendering
5615
+ * a stored design (fulfillment, agents, server-to-server). Results arrive via
5616
+ * {@link RenderSession.onMockups}.
5617
+ */
5618
+ async renderSavedState(placement, stateId) {
5619
+ await this.connect();
5620
+ this.svc.sendCanvasStateRef(placement, stateId);
5621
+ }
5574
5622
  /** Update only the mockup ids to render (reuses the current state). */
5575
5623
  updateMockupIds(mockupIds) {
5576
5624
  if (this.product) this.product = { ...this.product, mockupIds };
@@ -5603,6 +5651,29 @@ var RenderSession = class {
5603
5651
  }
5604
5652
  };
5605
5653
 
5654
+ // src/realtime/design-state.ts
5655
+ var TRANSPARENT_1X1_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
5656
+ async function createDesignState(input) {
5657
+ const f = input.fetch ?? globalThis.fetch;
5658
+ const res = await f(`${input.base ?? DEFAULT_GRANT_BASE}/snowcone/design-states`, {
5659
+ method: "POST",
5660
+ headers: { "Content-Type": "application/json" },
5661
+ body: JSON.stringify({
5662
+ productId: input.productId,
5663
+ stateJson: JSON.stringify(input.state),
5664
+ previewBase64: input.previewBase64 ?? TRANSPARENT_1X1_PNG
5665
+ })
5666
+ });
5667
+ if (!res.ok) {
5668
+ const detail = await res.text().catch(() => "");
5669
+ throw new Error(`createDesignState failed: ${res.status}${detail ? ` ${detail}` : ""}`);
5670
+ }
5671
+ const body = await res.json();
5672
+ const ds = body?.designState;
5673
+ if (!ds?.id) throw new Error("createDesignState: response missing designState.id");
5674
+ return { stateId: ds.id, stateUrl: ds.stateUrl ?? "", previewUrl: ds.previewUrl ?? "" };
5675
+ }
5676
+
5606
5677
  // src/index.ts
5607
5678
  function getFetcher(config) {
5608
5679
  return config?.fetcher || globalThis.fetch.bind(globalThis);
@@ -5728,6 +5799,7 @@ function createClient(config) {
5728
5799
  createComponent,
5729
5800
  createContextProvider,
5730
5801
  createDesignForPlacements,
5802
+ createDesignState,
5731
5803
  createDevFetcher,
5732
5804
  createErrorHandler,
5733
5805
  createEventManager,
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { createDevFetcher } from './dev-fetcher.cjs';
2
2
  import { PublicDesign, PublicFill, PublicOptions } from '@snowcone-app/mockup-url';
3
- import { O as OptionAttribute, a as OptionSelection, C as Combination, F as FrameworkAdapter, b as ComponentProps, c as ComponentDescriptor, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, g as FrameworkUtilities, P as PlacementSettings, M as MockupResult, R as RealtimeMockupService } from './websocket-BQH1HYJp.cjs';
4
- export { A as AdapterRegistry, h as ProductComponentContext, i as RealtimeMockupCallbacks, j as RealtimeMockupState, k as RenderResult, W as WebSocketConfig, l as WebSocketMessage, m as adapterRegistry, n as computeDisabledChoices, o as createComponent, p as defineComponent, q as deriveDefaultSelection, r as findBestCombination, s as getPricePreview, t as isOptionAvailable, u as resolveBestCombination } from './websocket-BQH1HYJp.cjs';
3
+ import { O as OptionAttribute, a as OptionSelection, C as Combination, F as FrameworkAdapter, b as ComponentProps, c as ComponentDescriptor, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, g as FrameworkUtilities, P as PlacementSettings, M as MockupResult, R as RealtimeMockupService } from './websocket-Poy8LZNA.cjs';
4
+ export { A as AdapterRegistry, h as ProductComponentContext, i as RealtimeMockupCallbacks, j as RealtimeMockupState, k as RenderResult, W as WebSocketConfig, l as WebSocketMessage, m as adapterRegistry, n as computeDisabledChoices, o as createComponent, p as defineComponent, q as deriveDefaultSelection, r as findBestCombination, s as getPricePreview, t as isOptionAvailable, u as resolveBestCombination } from './websocket-Poy8LZNA.cjs';
5
5
 
6
6
  interface CatalogProduct$2 {
7
7
  id: string;
@@ -16,7 +16,6 @@ interface CatalogProduct$2 {
16
16
  highestPrice?: number;
17
17
  mockups?: {
18
18
  id: string;
19
- ar: number;
20
19
  gvids: string[];
21
20
  }[];
22
21
  options?: {
@@ -2581,8 +2580,18 @@ interface RenderState {
2581
2580
  /** Product the session renders against (Snowcone catalog ids). */
2582
2581
  interface RenderProduct {
2583
2582
  productId: string;
2583
+ /**
2584
+ * The mockup scenes to render — opaque catalog codes (the product's
2585
+ * `mockups[].id`, e.g. `'FV1qjO'`). These are a DIFFERENT namespace from the
2586
+ * `placement` you pass to {@link RenderSession.renderState} (a print-area name
2587
+ * like `'Front'`). Don't pass a placement name here.
2588
+ */
2584
2589
  mockupIds: string[];
2585
- /** Optional variant (gvid). Omit if the product has no variant axis. */
2590
+ /**
2591
+ * Optional variant (gvid). Omit if the product has no variant axis. Required
2592
+ * for products with a color option — it auto-fills the color placement; without
2593
+ * it the render waits for a color blob that never comes.
2594
+ */
2586
2595
  variantId?: string;
2587
2596
  /** Render width in px (default 1000). */
2588
2597
  width?: number;
@@ -2631,8 +2640,26 @@ declare class RenderSession {
2631
2640
  * the assets referenced inside — no pixels are uploaded. Results arrive via
2632
2641
  * {@link RenderSession.onMockups}. Safe to call repeatedly (e.g. on every
2633
2642
  * canvas edit); pass `throttleMs` to debounce during live dragging.
2643
+ *
2644
+ * `placement` is the print-area NAME (e.g. `'Front'`, the product's
2645
+ * `placements[].label`) — a different id from the product's `mockupIds`, and it
2646
+ * must match an artboard in `state`. The server renders only once it has a state
2647
+ * for EVERY required placement, so on a multi-placement product call this once
2648
+ * per placement; a missing one surfaces as an `incomplete_canvas_placements`
2649
+ * error on {@link RenderSession.onError}.
2650
+ *
2651
+ * `state` is the flattened `serializeStateForServer()` shape — NOT the
2652
+ * un-flattened shape `createDesignState` takes.
2634
2653
  */
2635
2654
  renderState(placement: string, state: RenderState, throttleMs?: number): Promise<void>;
2655
+ /**
2656
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
2657
+ * the `stateId`, fetches the referenced assets, and renders — no pixels are
2658
+ * uploaded and you don't need to hold the canvas JSON. Ideal for re-rendering
2659
+ * a stored design (fulfillment, agents, server-to-server). Results arrive via
2660
+ * {@link RenderSession.onMockups}.
2661
+ */
2662
+ renderSavedState(placement: string, stateId: string): Promise<void>;
2636
2663
  /** Update only the mockup ids to render (reuses the current state). */
2637
2664
  updateMockupIds(mockupIds: string[]): void;
2638
2665
  /** Current rendered results. */
@@ -2644,6 +2671,45 @@ declare class RenderSession {
2644
2671
  private toConfig;
2645
2672
  }
2646
2673
 
2674
+ interface CreateDesignStateInput {
2675
+ /** Catalog product code the design targets. */
2676
+ productId: string;
2677
+ /**
2678
+ * The canvas state to persist — the **raw, un-flattened** stored shape:
2679
+ * top-level `elements` with `transformType` + nested `transformData`, plus
2680
+ * bare `artboards` metadata. The server flattens this at render time
2681
+ * (`normalizeCanvasState`).
2682
+ *
2683
+ * ⚠️ This is NOT the `serializeStateForServer()` output you pass to
2684
+ * `RenderSession.renderState` — that shape is already flattened and will
2685
+ * mis-render if stored here. Persist the raw canvas state (e.g. `toJSON()`
2686
+ * from the editor), not the server-render payload.
2687
+ */
2688
+ state: RenderState | Record<string, unknown>;
2689
+ /**
2690
+ * Preview thumbnail as a base64 PNG/JPEG (no data: prefix). Optional —
2691
+ * defaults to a 1×1 transparent PNG, which is fine for render-only use.
2692
+ */
2693
+ previewBase64?: string;
2694
+ /** Backend base URL. Defaults to {@link DEFAULT_GRANT_BASE}. */
2695
+ base?: string;
2696
+ /** Override fetch (tests / non-global-fetch runtimes). */
2697
+ fetch?: typeof fetch;
2698
+ }
2699
+ interface CreatedDesignState {
2700
+ /** The id to pass to `renderSavedState(placement, stateId)`. */
2701
+ stateId: string;
2702
+ /** Content-addressed URL of the persisted state JSON. */
2703
+ stateUrl: string;
2704
+ /** Content-addressed URL of the preview image. */
2705
+ previewUrl: string;
2706
+ }
2707
+ /**
2708
+ * Persist a canvas state and return its `stateId` (+ URLs). Pair with
2709
+ * `RenderSession.renderSavedState(placement, stateId)`.
2710
+ */
2711
+ declare function createDesignState(input: CreateDesignStateInput): Promise<CreatedDesignState>;
2712
+
2647
2713
  interface ProductPlacement {
2648
2714
  label: string;
2649
2715
  x: number;
@@ -2698,4 +2764,4 @@ declare function getProduct(idOrSlug: string, config?: Partial<SdkConfig>): Prom
2698
2764
  */
2699
2765
  declare function createClient(config: SdkConfig): SnowconeClient;
2700
2766
 
2701
- export { AbstractTemplateRenderer, type AddToCartOptions, Animations, type ArtSelectorContext, type ArtSelectorDescriptor, type ArtSelectorOptions, type ArtworkData, type AspectRatio, Attributes, type BaseDescriptor, type BlendConfig, type BuildMockupUrlConfig, type CartDetail, type CatalogListResponse, type CatalogProduct$2 as CatalogProduct, ClassNames, type ColorDesignElement, ColorPickerUtils, Combination, CommonProps, ComponentContext, type ComponentDefinition, ComponentDescriptor, ComponentEventManager, ComponentFactory, ComponentLifecycle, ComponentLifecycleHooks, type ComponentMetadata, ComponentProps, ComponentRegistry, ComponentState, type ComponentTemplate, ComponentTemplates, type ContainerDescriptor, ContextBridge, type ContextComparator, type ContextConsumer, ContextInjector, type ContextProviderConfig, type ContextSubscriber, ContextSynchronizer, DEFAULT_ARTWORK_URL, DEFAULT_ASPECT_RATIO, DEFAULT_COLOR, DEFAULT_GRANT_BASE, DEFAULT_MOCKUP_BASE, DEFAULT_PLACEMENT_DIMENSIONS, type Descriptor, type Design, type DesignElement, type DesignGenerationContext, type Effects, Elements, type ErrorContext, type ErrorHandler, ErrorManager, type EventDefinition, EventDelegator, EventEmitter, EventHandler, type EventListener, type EventPayload, type Fill, Focus, FrameworkAdapter, FrameworkUtilities, type GetMockupUrlOptions, type ImageAlignment, type ImageDesignElement, type LifecycleHook, LifecycleManager, type LifecyclePhase, type LifecycleTransition, LitAdapter, type MaskOverride, type MockupGenerationOptions, MockupResult, type MockupService, type MockupServiceConfig, OptionAttribute, type OptionChoice, type OptionChoiceRenderData, type OptionRenderData, OptionSelection, type Placement, type PlacementDesign, PlacementSettings, type ProductArtAlignmentContext, type ProductArtAlignmentDescriptor, type ProductArtAlignmentOptions, type ProductContext, type ProductContextEvents, ProductContextManager, type ProductData, type ProductFetcher, type ProductImageDescriptor, type ProductImageOptions, ProductLoader, type ProductMockupData, type ProductOptionChoice, type ProductOptionData, type ProductOptionsDescriptor, type ProductOptionsOptions, type ProductPlacement, type ProductPriceDescriptor, type ProductPriceOptions, ProductProps, type ProductSpec, type ProductTitleDescriptor, type ProductTitleOptions, type ProductVariant, type PropDefinition, type PropSchema, type PropType, PropertyManager, REALTIME_WS_URL, type RateLimitState, type RealtimeGrant, RealtimeMockupService, type RegularArtwork, type RenderProduct, RenderSession, type RenderSessionOptions, type RenderState, type SdkConfig, type SeamlessPattern, type SignedUrlResponse, type SnowconeClient, StandardComponents, StandardEvents, StateManager, type StateManagerOptions, type StateMiddleware, type StateSelector, type StateSubscriber, type StateUpdater, Styles, SvelteAdapter, SwatchUtils, type TagName, TemplateBuilder, type TemplateNode, type TemplateNodeType, type TemplateRenderer, type TextDescriptor, type TileCount, UniversalContextProvider, type UserSelection, type ValidationError, type ValidationResult, VueAdapter, autoRegister, buildMockupUrl, componentRegistry, createAddToCartEvent, createAddToCartHandler, createCartDetail, createClient, createContextProvider, createDesignForPlacements, createErrorHandler, createEventManager, createLifecycle, createLitComponent, createProductContext, createProductLoader, createPropertyManager, createStateHook, createStateStore, createSvelteComponent, createUniversalProvider, createVueComponent, describeArtSelector, describeProductArtAlignment, describeProductImage, describeProductOptions, describeProductPrice, describeProductTitle, extractProductId, fetchRealtimeGrant, filterImagePlacements, findClosestSnapPoint, findVariantForSelection, formatPrice, formatValidationErrors, getDefaultVariantId, getEffectiveAlignment, getIncompleteSelectionMessage, getMissingSelections, getMockupUrl, getOptionRenderType, getProduct, getSelectionDisplayText, getSnapPoints, getVariant, handleOptionChange, isSelectionComplete, isValidAlignment, isValidTileCount, listProducts, mintRealtimeGrant, mockupUrl, normalizeChoice, prepareOptionRenderData, registerStandardComponents, resolveMockupId, resolveVariantId, retryOperation, simulateCartOperation, toCombinations, toOptionAttributes, useFrameworkAdapter as useVueAdapter, validateAlignment, validateDesignElement, validateEffects, validateImageUrl, validateMockupOptions, validateProductSelection, validateProductSpec, validateRequiredOptions, validateTileCount, withContext, withErrorHandling, withSyncErrorHandling };
2767
+ export { AbstractTemplateRenderer, type AddToCartOptions, Animations, type ArtSelectorContext, type ArtSelectorDescriptor, type ArtSelectorOptions, type ArtworkData, type AspectRatio, Attributes, type BaseDescriptor, type BlendConfig, type BuildMockupUrlConfig, type CartDetail, type CatalogListResponse, type CatalogProduct$2 as CatalogProduct, ClassNames, type ColorDesignElement, ColorPickerUtils, Combination, CommonProps, ComponentContext, type ComponentDefinition, ComponentDescriptor, ComponentEventManager, ComponentFactory, ComponentLifecycle, ComponentLifecycleHooks, type ComponentMetadata, ComponentProps, ComponentRegistry, ComponentState, type ComponentTemplate, ComponentTemplates, type ContainerDescriptor, ContextBridge, type ContextComparator, type ContextConsumer, ContextInjector, type ContextProviderConfig, type ContextSubscriber, ContextSynchronizer, type CreateDesignStateInput, type CreatedDesignState, DEFAULT_ARTWORK_URL, DEFAULT_ASPECT_RATIO, DEFAULT_COLOR, DEFAULT_GRANT_BASE, DEFAULT_MOCKUP_BASE, DEFAULT_PLACEMENT_DIMENSIONS, type Descriptor, type Design, type DesignElement, type DesignGenerationContext, type Effects, Elements, type ErrorContext, type ErrorHandler, ErrorManager, type EventDefinition, EventDelegator, EventEmitter, EventHandler, type EventListener, type EventPayload, type Fill, Focus, FrameworkAdapter, FrameworkUtilities, type GetMockupUrlOptions, type ImageAlignment, type ImageDesignElement, type LifecycleHook, LifecycleManager, type LifecyclePhase, type LifecycleTransition, LitAdapter, type MaskOverride, type MockupGenerationOptions, MockupResult, type MockupService, type MockupServiceConfig, OptionAttribute, type OptionChoice, type OptionChoiceRenderData, type OptionRenderData, OptionSelection, type Placement, type PlacementDesign, PlacementSettings, type ProductArtAlignmentContext, type ProductArtAlignmentDescriptor, type ProductArtAlignmentOptions, type ProductContext, type ProductContextEvents, ProductContextManager, type ProductData, type ProductFetcher, type ProductImageDescriptor, type ProductImageOptions, ProductLoader, type ProductMockupData, type ProductOptionChoice, type ProductOptionData, type ProductOptionsDescriptor, type ProductOptionsOptions, type ProductPlacement, type ProductPriceDescriptor, type ProductPriceOptions, ProductProps, type ProductSpec, type ProductTitleDescriptor, type ProductTitleOptions, type ProductVariant, type PropDefinition, type PropSchema, type PropType, PropertyManager, REALTIME_WS_URL, type RateLimitState, type RealtimeGrant, RealtimeMockupService, type RegularArtwork, type RenderProduct, RenderSession, type RenderSessionOptions, type RenderState, type SdkConfig, type SeamlessPattern, type SignedUrlResponse, type SnowconeClient, StandardComponents, StandardEvents, StateManager, type StateManagerOptions, type StateMiddleware, type StateSelector, type StateSubscriber, type StateUpdater, Styles, SvelteAdapter, SwatchUtils, type TagName, TemplateBuilder, type TemplateNode, type TemplateNodeType, type TemplateRenderer, type TextDescriptor, type TileCount, UniversalContextProvider, type UserSelection, type ValidationError, type ValidationResult, VueAdapter, autoRegister, buildMockupUrl, componentRegistry, createAddToCartEvent, createAddToCartHandler, createCartDetail, createClient, createContextProvider, createDesignForPlacements, createDesignState, createErrorHandler, createEventManager, createLifecycle, createLitComponent, createProductContext, createProductLoader, createPropertyManager, createStateHook, createStateStore, createSvelteComponent, createUniversalProvider, createVueComponent, describeArtSelector, describeProductArtAlignment, describeProductImage, describeProductOptions, describeProductPrice, describeProductTitle, extractProductId, fetchRealtimeGrant, filterImagePlacements, findClosestSnapPoint, findVariantForSelection, formatPrice, formatValidationErrors, getDefaultVariantId, getEffectiveAlignment, getIncompleteSelectionMessage, getMissingSelections, getMockupUrl, getOptionRenderType, getProduct, getSelectionDisplayText, getSnapPoints, getVariant, handleOptionChange, isSelectionComplete, isValidAlignment, isValidTileCount, listProducts, mintRealtimeGrant, mockupUrl, normalizeChoice, prepareOptionRenderData, registerStandardComponents, resolveMockupId, resolveVariantId, retryOperation, simulateCartOperation, toCombinations, toOptionAttributes, useFrameworkAdapter as useVueAdapter, validateAlignment, validateDesignElement, validateEffects, validateImageUrl, validateMockupOptions, validateProductSelection, validateProductSpec, validateRequiredOptions, validateTileCount, withContext, withErrorHandling, withSyncErrorHandling };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { createDevFetcher } from './dev-fetcher.js';
2
2
  import { PublicDesign, PublicFill, PublicOptions } from '@snowcone-app/mockup-url';
3
- import { O as OptionAttribute, a as OptionSelection, C as Combination, F as FrameworkAdapter, b as ComponentProps, c as ComponentDescriptor, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, g as FrameworkUtilities, P as PlacementSettings, M as MockupResult, R as RealtimeMockupService } from './websocket-BQH1HYJp.js';
4
- export { A as AdapterRegistry, h as ProductComponentContext, i as RealtimeMockupCallbacks, j as RealtimeMockupState, k as RenderResult, W as WebSocketConfig, l as WebSocketMessage, m as adapterRegistry, n as computeDisabledChoices, o as createComponent, p as defineComponent, q as deriveDefaultSelection, r as findBestCombination, s as getPricePreview, t as isOptionAvailable, u as resolveBestCombination } from './websocket-BQH1HYJp.js';
3
+ import { O as OptionAttribute, a as OptionSelection, C as Combination, F as FrameworkAdapter, b as ComponentProps, c as ComponentDescriptor, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, g as FrameworkUtilities, P as PlacementSettings, M as MockupResult, R as RealtimeMockupService } from './websocket-Poy8LZNA.js';
4
+ export { A as AdapterRegistry, h as ProductComponentContext, i as RealtimeMockupCallbacks, j as RealtimeMockupState, k as RenderResult, W as WebSocketConfig, l as WebSocketMessage, m as adapterRegistry, n as computeDisabledChoices, o as createComponent, p as defineComponent, q as deriveDefaultSelection, r as findBestCombination, s as getPricePreview, t as isOptionAvailable, u as resolveBestCombination } from './websocket-Poy8LZNA.js';
5
5
 
6
6
  interface CatalogProduct$2 {
7
7
  id: string;
@@ -16,7 +16,6 @@ interface CatalogProduct$2 {
16
16
  highestPrice?: number;
17
17
  mockups?: {
18
18
  id: string;
19
- ar: number;
20
19
  gvids: string[];
21
20
  }[];
22
21
  options?: {
@@ -2581,8 +2580,18 @@ interface RenderState {
2581
2580
  /** Product the session renders against (Snowcone catalog ids). */
2582
2581
  interface RenderProduct {
2583
2582
  productId: string;
2583
+ /**
2584
+ * The mockup scenes to render — opaque catalog codes (the product's
2585
+ * `mockups[].id`, e.g. `'FV1qjO'`). These are a DIFFERENT namespace from the
2586
+ * `placement` you pass to {@link RenderSession.renderState} (a print-area name
2587
+ * like `'Front'`). Don't pass a placement name here.
2588
+ */
2584
2589
  mockupIds: string[];
2585
- /** Optional variant (gvid). Omit if the product has no variant axis. */
2590
+ /**
2591
+ * Optional variant (gvid). Omit if the product has no variant axis. Required
2592
+ * for products with a color option — it auto-fills the color placement; without
2593
+ * it the render waits for a color blob that never comes.
2594
+ */
2586
2595
  variantId?: string;
2587
2596
  /** Render width in px (default 1000). */
2588
2597
  width?: number;
@@ -2631,8 +2640,26 @@ declare class RenderSession {
2631
2640
  * the assets referenced inside — no pixels are uploaded. Results arrive via
2632
2641
  * {@link RenderSession.onMockups}. Safe to call repeatedly (e.g. on every
2633
2642
  * canvas edit); pass `throttleMs` to debounce during live dragging.
2643
+ *
2644
+ * `placement` is the print-area NAME (e.g. `'Front'`, the product's
2645
+ * `placements[].label`) — a different id from the product's `mockupIds`, and it
2646
+ * must match an artboard in `state`. The server renders only once it has a state
2647
+ * for EVERY required placement, so on a multi-placement product call this once
2648
+ * per placement; a missing one surfaces as an `incomplete_canvas_placements`
2649
+ * error on {@link RenderSession.onError}.
2650
+ *
2651
+ * `state` is the flattened `serializeStateForServer()` shape — NOT the
2652
+ * un-flattened shape `createDesignState` takes.
2634
2653
  */
2635
2654
  renderState(placement: string, state: RenderState, throttleMs?: number): Promise<void>;
2655
+ /**
2656
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
2657
+ * the `stateId`, fetches the referenced assets, and renders — no pixels are
2658
+ * uploaded and you don't need to hold the canvas JSON. Ideal for re-rendering
2659
+ * a stored design (fulfillment, agents, server-to-server). Results arrive via
2660
+ * {@link RenderSession.onMockups}.
2661
+ */
2662
+ renderSavedState(placement: string, stateId: string): Promise<void>;
2636
2663
  /** Update only the mockup ids to render (reuses the current state). */
2637
2664
  updateMockupIds(mockupIds: string[]): void;
2638
2665
  /** Current rendered results. */
@@ -2644,6 +2671,45 @@ declare class RenderSession {
2644
2671
  private toConfig;
2645
2672
  }
2646
2673
 
2674
+ interface CreateDesignStateInput {
2675
+ /** Catalog product code the design targets. */
2676
+ productId: string;
2677
+ /**
2678
+ * The canvas state to persist — the **raw, un-flattened** stored shape:
2679
+ * top-level `elements` with `transformType` + nested `transformData`, plus
2680
+ * bare `artboards` metadata. The server flattens this at render time
2681
+ * (`normalizeCanvasState`).
2682
+ *
2683
+ * ⚠️ This is NOT the `serializeStateForServer()` output you pass to
2684
+ * `RenderSession.renderState` — that shape is already flattened and will
2685
+ * mis-render if stored here. Persist the raw canvas state (e.g. `toJSON()`
2686
+ * from the editor), not the server-render payload.
2687
+ */
2688
+ state: RenderState | Record<string, unknown>;
2689
+ /**
2690
+ * Preview thumbnail as a base64 PNG/JPEG (no data: prefix). Optional —
2691
+ * defaults to a 1×1 transparent PNG, which is fine for render-only use.
2692
+ */
2693
+ previewBase64?: string;
2694
+ /** Backend base URL. Defaults to {@link DEFAULT_GRANT_BASE}. */
2695
+ base?: string;
2696
+ /** Override fetch (tests / non-global-fetch runtimes). */
2697
+ fetch?: typeof fetch;
2698
+ }
2699
+ interface CreatedDesignState {
2700
+ /** The id to pass to `renderSavedState(placement, stateId)`. */
2701
+ stateId: string;
2702
+ /** Content-addressed URL of the persisted state JSON. */
2703
+ stateUrl: string;
2704
+ /** Content-addressed URL of the preview image. */
2705
+ previewUrl: string;
2706
+ }
2707
+ /**
2708
+ * Persist a canvas state and return its `stateId` (+ URLs). Pair with
2709
+ * `RenderSession.renderSavedState(placement, stateId)`.
2710
+ */
2711
+ declare function createDesignState(input: CreateDesignStateInput): Promise<CreatedDesignState>;
2712
+
2647
2713
  interface ProductPlacement {
2648
2714
  label: string;
2649
2715
  x: number;
@@ -2698,4 +2764,4 @@ declare function getProduct(idOrSlug: string, config?: Partial<SdkConfig>): Prom
2698
2764
  */
2699
2765
  declare function createClient(config: SdkConfig): SnowconeClient;
2700
2766
 
2701
- export { AbstractTemplateRenderer, type AddToCartOptions, Animations, type ArtSelectorContext, type ArtSelectorDescriptor, type ArtSelectorOptions, type ArtworkData, type AspectRatio, Attributes, type BaseDescriptor, type BlendConfig, type BuildMockupUrlConfig, type CartDetail, type CatalogListResponse, type CatalogProduct$2 as CatalogProduct, ClassNames, type ColorDesignElement, ColorPickerUtils, Combination, CommonProps, ComponentContext, type ComponentDefinition, ComponentDescriptor, ComponentEventManager, ComponentFactory, ComponentLifecycle, ComponentLifecycleHooks, type ComponentMetadata, ComponentProps, ComponentRegistry, ComponentState, type ComponentTemplate, ComponentTemplates, type ContainerDescriptor, ContextBridge, type ContextComparator, type ContextConsumer, ContextInjector, type ContextProviderConfig, type ContextSubscriber, ContextSynchronizer, DEFAULT_ARTWORK_URL, DEFAULT_ASPECT_RATIO, DEFAULT_COLOR, DEFAULT_GRANT_BASE, DEFAULT_MOCKUP_BASE, DEFAULT_PLACEMENT_DIMENSIONS, type Descriptor, type Design, type DesignElement, type DesignGenerationContext, type Effects, Elements, type ErrorContext, type ErrorHandler, ErrorManager, type EventDefinition, EventDelegator, EventEmitter, EventHandler, type EventListener, type EventPayload, type Fill, Focus, FrameworkAdapter, FrameworkUtilities, type GetMockupUrlOptions, type ImageAlignment, type ImageDesignElement, type LifecycleHook, LifecycleManager, type LifecyclePhase, type LifecycleTransition, LitAdapter, type MaskOverride, type MockupGenerationOptions, MockupResult, type MockupService, type MockupServiceConfig, OptionAttribute, type OptionChoice, type OptionChoiceRenderData, type OptionRenderData, OptionSelection, type Placement, type PlacementDesign, PlacementSettings, type ProductArtAlignmentContext, type ProductArtAlignmentDescriptor, type ProductArtAlignmentOptions, type ProductContext, type ProductContextEvents, ProductContextManager, type ProductData, type ProductFetcher, type ProductImageDescriptor, type ProductImageOptions, ProductLoader, type ProductMockupData, type ProductOptionChoice, type ProductOptionData, type ProductOptionsDescriptor, type ProductOptionsOptions, type ProductPlacement, type ProductPriceDescriptor, type ProductPriceOptions, ProductProps, type ProductSpec, type ProductTitleDescriptor, type ProductTitleOptions, type ProductVariant, type PropDefinition, type PropSchema, type PropType, PropertyManager, REALTIME_WS_URL, type RateLimitState, type RealtimeGrant, RealtimeMockupService, type RegularArtwork, type RenderProduct, RenderSession, type RenderSessionOptions, type RenderState, type SdkConfig, type SeamlessPattern, type SignedUrlResponse, type SnowconeClient, StandardComponents, StandardEvents, StateManager, type StateManagerOptions, type StateMiddleware, type StateSelector, type StateSubscriber, type StateUpdater, Styles, SvelteAdapter, SwatchUtils, type TagName, TemplateBuilder, type TemplateNode, type TemplateNodeType, type TemplateRenderer, type TextDescriptor, type TileCount, UniversalContextProvider, type UserSelection, type ValidationError, type ValidationResult, VueAdapter, autoRegister, buildMockupUrl, componentRegistry, createAddToCartEvent, createAddToCartHandler, createCartDetail, createClient, createContextProvider, createDesignForPlacements, createErrorHandler, createEventManager, createLifecycle, createLitComponent, createProductContext, createProductLoader, createPropertyManager, createStateHook, createStateStore, createSvelteComponent, createUniversalProvider, createVueComponent, describeArtSelector, describeProductArtAlignment, describeProductImage, describeProductOptions, describeProductPrice, describeProductTitle, extractProductId, fetchRealtimeGrant, filterImagePlacements, findClosestSnapPoint, findVariantForSelection, formatPrice, formatValidationErrors, getDefaultVariantId, getEffectiveAlignment, getIncompleteSelectionMessage, getMissingSelections, getMockupUrl, getOptionRenderType, getProduct, getSelectionDisplayText, getSnapPoints, getVariant, handleOptionChange, isSelectionComplete, isValidAlignment, isValidTileCount, listProducts, mintRealtimeGrant, mockupUrl, normalizeChoice, prepareOptionRenderData, registerStandardComponents, resolveMockupId, resolveVariantId, retryOperation, simulateCartOperation, toCombinations, toOptionAttributes, useFrameworkAdapter as useVueAdapter, validateAlignment, validateDesignElement, validateEffects, validateImageUrl, validateMockupOptions, validateProductSelection, validateProductSpec, validateRequiredOptions, validateTileCount, withContext, withErrorHandling, withSyncErrorHandling };
2767
+ export { AbstractTemplateRenderer, type AddToCartOptions, Animations, type ArtSelectorContext, type ArtSelectorDescriptor, type ArtSelectorOptions, type ArtworkData, type AspectRatio, Attributes, type BaseDescriptor, type BlendConfig, type BuildMockupUrlConfig, type CartDetail, type CatalogListResponse, type CatalogProduct$2 as CatalogProduct, ClassNames, type ColorDesignElement, ColorPickerUtils, Combination, CommonProps, ComponentContext, type ComponentDefinition, ComponentDescriptor, ComponentEventManager, ComponentFactory, ComponentLifecycle, ComponentLifecycleHooks, type ComponentMetadata, ComponentProps, ComponentRegistry, ComponentState, type ComponentTemplate, ComponentTemplates, type ContainerDescriptor, ContextBridge, type ContextComparator, type ContextConsumer, ContextInjector, type ContextProviderConfig, type ContextSubscriber, ContextSynchronizer, type CreateDesignStateInput, type CreatedDesignState, DEFAULT_ARTWORK_URL, DEFAULT_ASPECT_RATIO, DEFAULT_COLOR, DEFAULT_GRANT_BASE, DEFAULT_MOCKUP_BASE, DEFAULT_PLACEMENT_DIMENSIONS, type Descriptor, type Design, type DesignElement, type DesignGenerationContext, type Effects, Elements, type ErrorContext, type ErrorHandler, ErrorManager, type EventDefinition, EventDelegator, EventEmitter, EventHandler, type EventListener, type EventPayload, type Fill, Focus, FrameworkAdapter, FrameworkUtilities, type GetMockupUrlOptions, type ImageAlignment, type ImageDesignElement, type LifecycleHook, LifecycleManager, type LifecyclePhase, type LifecycleTransition, LitAdapter, type MaskOverride, type MockupGenerationOptions, MockupResult, type MockupService, type MockupServiceConfig, OptionAttribute, type OptionChoice, type OptionChoiceRenderData, type OptionRenderData, OptionSelection, type Placement, type PlacementDesign, PlacementSettings, type ProductArtAlignmentContext, type ProductArtAlignmentDescriptor, type ProductArtAlignmentOptions, type ProductContext, type ProductContextEvents, ProductContextManager, type ProductData, type ProductFetcher, type ProductImageDescriptor, type ProductImageOptions, ProductLoader, type ProductMockupData, type ProductOptionChoice, type ProductOptionData, type ProductOptionsDescriptor, type ProductOptionsOptions, type ProductPlacement, type ProductPriceDescriptor, type ProductPriceOptions, ProductProps, type ProductSpec, type ProductTitleDescriptor, type ProductTitleOptions, type ProductVariant, type PropDefinition, type PropSchema, type PropType, PropertyManager, REALTIME_WS_URL, type RateLimitState, type RealtimeGrant, RealtimeMockupService, type RegularArtwork, type RenderProduct, RenderSession, type RenderSessionOptions, type RenderState, type SdkConfig, type SeamlessPattern, type SignedUrlResponse, type SnowconeClient, StandardComponents, StandardEvents, StateManager, type StateManagerOptions, type StateMiddleware, type StateSelector, type StateSubscriber, type StateUpdater, Styles, SvelteAdapter, SwatchUtils, type TagName, TemplateBuilder, type TemplateNode, type TemplateNodeType, type TemplateRenderer, type TextDescriptor, type TileCount, UniversalContextProvider, type UserSelection, type ValidationError, type ValidationResult, VueAdapter, autoRegister, buildMockupUrl, componentRegistry, createAddToCartEvent, createAddToCartHandler, createCartDetail, createClient, createContextProvider, createDesignForPlacements, createDesignState, createErrorHandler, createEventManager, createLifecycle, createLitComponent, createProductContext, createProductLoader, createPropertyManager, createStateHook, createStateStore, createSvelteComponent, createUniversalProvider, createVueComponent, describeArtSelector, describeProductArtAlignment, describeProductImage, describeProductOptions, describeProductPrice, describeProductTitle, extractProductId, fetchRealtimeGrant, filterImagePlacements, findClosestSnapPoint, findVariantForSelection, formatPrice, formatValidationErrors, getDefaultVariantId, getEffectiveAlignment, getIncompleteSelectionMessage, getMissingSelections, getMockupUrl, getOptionRenderType, getProduct, getSelectionDisplayText, getSnapPoints, getVariant, handleOptionChange, isSelectionComplete, isValidAlignment, isValidTileCount, listProducts, mintRealtimeGrant, mockupUrl, normalizeChoice, prepareOptionRenderData, registerStandardComponents, resolveMockupId, resolveVariantId, retryOperation, simulateCartOperation, toCombinations, toOptionAttributes, useFrameworkAdapter as useVueAdapter, validateAlignment, validateDesignElement, validateEffects, validateImageUrl, validateMockupOptions, validateProductSelection, validateProductSpec, validateRequiredOptions, validateTileCount, withContext, withErrorHandling, withSyncErrorHandling };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  REALTIME_WS_URL,
6
6
  RealtimeMockupService
7
- } from "./chunk-RPA3B6I3.js";
7
+ } from "./chunk-D5ZRGKA5.js";
8
8
 
9
9
  // src/validation.ts
10
10
  import { z } from "zod";
@@ -4760,11 +4760,32 @@ var RenderSession = class {
4760
4760
  * the assets referenced inside — no pixels are uploaded. Results arrive via
4761
4761
  * {@link RenderSession.onMockups}. Safe to call repeatedly (e.g. on every
4762
4762
  * canvas edit); pass `throttleMs` to debounce during live dragging.
4763
+ *
4764
+ * `placement` is the print-area NAME (e.g. `'Front'`, the product's
4765
+ * `placements[].label`) — a different id from the product's `mockupIds`, and it
4766
+ * must match an artboard in `state`. The server renders only once it has a state
4767
+ * for EVERY required placement, so on a multi-placement product call this once
4768
+ * per placement; a missing one surfaces as an `incomplete_canvas_placements`
4769
+ * error on {@link RenderSession.onError}.
4770
+ *
4771
+ * `state` is the flattened `serializeStateForServer()` shape — NOT the
4772
+ * un-flattened shape `createDesignState` takes.
4763
4773
  */
4764
4774
  async renderState(placement, state, throttleMs = 0) {
4765
4775
  await this.connect();
4766
4776
  this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
4767
4777
  }
4778
+ /**
4779
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
4780
+ * the `stateId`, fetches the referenced assets, and renders — no pixels are
4781
+ * uploaded and you don't need to hold the canvas JSON. Ideal for re-rendering
4782
+ * a stored design (fulfillment, agents, server-to-server). Results arrive via
4783
+ * {@link RenderSession.onMockups}.
4784
+ */
4785
+ async renderSavedState(placement, stateId) {
4786
+ await this.connect();
4787
+ this.svc.sendCanvasStateRef(placement, stateId);
4788
+ }
4768
4789
  /** Update only the mockup ids to render (reuses the current state). */
4769
4790
  updateMockupIds(mockupIds) {
4770
4791
  if (this.product) this.product = { ...this.product, mockupIds };
@@ -4797,6 +4818,29 @@ var RenderSession = class {
4797
4818
  }
4798
4819
  };
4799
4820
 
4821
+ // src/realtime/design-state.ts
4822
+ var TRANSPARENT_1X1_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
4823
+ async function createDesignState(input) {
4824
+ const f = input.fetch ?? globalThis.fetch;
4825
+ const res = await f(`${input.base ?? DEFAULT_GRANT_BASE}/snowcone/design-states`, {
4826
+ method: "POST",
4827
+ headers: { "Content-Type": "application/json" },
4828
+ body: JSON.stringify({
4829
+ productId: input.productId,
4830
+ stateJson: JSON.stringify(input.state),
4831
+ previewBase64: input.previewBase64 ?? TRANSPARENT_1X1_PNG
4832
+ })
4833
+ });
4834
+ if (!res.ok) {
4835
+ const detail = await res.text().catch(() => "");
4836
+ throw new Error(`createDesignState failed: ${res.status}${detail ? ` ${detail}` : ""}`);
4837
+ }
4838
+ const body = await res.json();
4839
+ const ds = body?.designState;
4840
+ if (!ds?.id) throw new Error("createDesignState: response missing designState.id");
4841
+ return { stateId: ds.id, stateUrl: ds.stateUrl ?? "", previewUrl: ds.previewUrl ?? "" };
4842
+ }
4843
+
4800
4844
  // src/index.ts
4801
4845
  function getFetcher(config) {
4802
4846
  return config?.fetcher || globalThis.fetch.bind(globalThis);
@@ -4921,6 +4965,7 @@ export {
4921
4965
  createComponent,
4922
4966
  createContextProvider,
4923
4967
  createDesignForPlacements,
4968
+ createDesignState,
4924
4969
  createDevFetcher,
4925
4970
  createErrorHandler,
4926
4971
  createEventManager,
package/dist/react.cjs CHANGED
@@ -504,6 +504,32 @@ var RealtimeMockupService = class {
504
504
  }
505
505
  return true;
506
506
  }
507
+ /**
508
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). Sends a `stateId`
509
+ * instead of inline state; the server resolves it, fetches the referenced
510
+ * assets, and renders — nothing is uploaded and the client need not hold the
511
+ * canvas JSON. One-shot (no throttle). Results arrive like any other render.
512
+ */
513
+ sendCanvasStateRef(placement, stateId) {
514
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.isConfigured) {
515
+ const reason = !this.ws ? "no WebSocket" : this.ws.readyState !== WebSocket.OPEN ? `ws.readyState=${this.ws.readyState}` : "not configured";
516
+ console.log(`[WS] sendCanvasStateRef BLOCKED for "${placement}": ${reason}`);
517
+ return false;
518
+ }
519
+ this.lastSentVersion = ++this.requestVersion;
520
+ this.latestSentVersionByPlacement[placement] = this.lastSentVersion;
521
+ const message = JSON.stringify({
522
+ type: "canvas_state",
523
+ placement,
524
+ version: this.lastSentVersion,
525
+ stateId
526
+ });
527
+ this.ws.send(message);
528
+ this.lastBlobSentAt = Date.now();
529
+ this.addLog(`Sent canvas stateId "${stateId}" for "${placement}" (v${this.lastSentVersion})`, "sent");
530
+ this.callbacks.onBlobSent?.(placement);
531
+ return true;
532
+ }
507
533
  sendCanvasStateImmediately(placement, state) {
508
534
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
509
535
  console.log(`[WS] sendCanvasStateImmediately ABORTED: ws not open`);
package/dist/react.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { M as MockupResult, W as WebSocketConfig, P as PlacementSettings, F as FrameworkAdapter, b as ComponentProps, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, c as ComponentDescriptor, g as FrameworkUtilities } from './websocket-BQH1HYJp.cjs';
1
+ import { M as MockupResult, W as WebSocketConfig, P as PlacementSettings, F as FrameworkAdapter, b as ComponentProps, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, c as ComponentDescriptor, g as FrameworkUtilities } from './websocket-Poy8LZNA.cjs';
2
2
 
3
3
  interface UseRealtimeMockupOptions {
4
4
  wsUrl?: string;
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { M as MockupResult, W as WebSocketConfig, P as PlacementSettings, F as FrameworkAdapter, b as ComponentProps, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, c as ComponentDescriptor, g as FrameworkUtilities } from './websocket-BQH1HYJp.js';
1
+ import { M as MockupResult, W as WebSocketConfig, P as PlacementSettings, F as FrameworkAdapter, b as ComponentProps, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, c as ComponentDescriptor, g as FrameworkUtilities } from './websocket-Poy8LZNA.js';
2
2
 
3
3
  interface UseRealtimeMockupOptions {
4
4
  wsUrl?: string;
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RealtimeMockupService
3
- } from "./chunk-RPA3B6I3.js";
3
+ } from "./chunk-D5ZRGKA5.js";
4
4
 
5
5
  // src/realtime/react.ts
6
6
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -356,6 +356,13 @@ declare class RealtimeMockupService {
356
356
  * Alternative to sendCanvasBlob — the server renders the PNG instead of the client.
357
357
  */
358
358
  sendCanvasState(placement: string, state: object, mockupCount?: number, baseThrottleMs?: number): boolean;
359
+ /**
360
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). Sends a `stateId`
361
+ * instead of inline state; the server resolves it, fetches the referenced
362
+ * assets, and renders — nothing is uploaded and the client need not hold the
363
+ * canvas JSON. One-shot (no throttle). Results arrive like any other render.
364
+ */
365
+ sendCanvasStateRef(placement: string, stateId: string): boolean;
359
366
  private sendCanvasStateImmediately;
360
367
  private sendBlobImmediately;
361
368
  /**
@@ -356,6 +356,13 @@ declare class RealtimeMockupService {
356
356
  * Alternative to sendCanvasBlob — the server renders the PNG instead of the client.
357
357
  */
358
358
  sendCanvasState(placement: string, state: object, mockupCount?: number, baseThrottleMs?: number): boolean;
359
+ /**
360
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). Sends a `stateId`
361
+ * instead of inline state; the server resolves it, fetches the referenced
362
+ * assets, and renders — nothing is uploaded and the client need not hold the
363
+ * canvas JSON. One-shot (no throttle). Results arrive like any other render.
364
+ */
365
+ sendCanvasStateRef(placement: string, stateId: string): boolean;
359
366
  private sendCanvasStateImmediately;
360
367
  private sendBlobImmediately;
361
368
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowcone-app/sdk",
3
- "version": "0.1.15",
3
+ "version": "0.2.0",
4
4
  "description": "Snowcone SDK for product mockups and print-on-demand",
5
5
  "keywords": [
6
6
  "merch",