@snowcone-app/sdk 0.1.15 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#233](https://github.com/snowcone-app/snowcone-monorepo/pull/233) [`8b08283`](https://github.com/snowcone-app/snowcone-monorepo/commit/8b0828340ea0d2852655bffe8436f0cb71bde3aa) Thanks [@kevinsproles](https://github.com/kevinsproles)! - fix(realtime): `mintRealtimeGrant` now honors the keyless publishable path
8
+
9
+ Omitting `apiKey` (or passing `undefined`/`null`/`''`) no longer sends a
10
+ broken `Authorization: Bearer undefined` header that 401s. The header is now
11
+ sent only when a key is actually provided, so `mintRealtimeGrant({ shop })`
12
+ uses the publishable shop-id path exactly as documented. `apiKey` is now typed
13
+ as optional.
14
+
15
+ ## 0.2.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [#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.
20
+
3
21
  All notable changes to the Merch SDK will be documented in this file.
4
22
 
5
23
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
@@ -8,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
26
  ## [0.1.9] - 2025-10-16
9
27
 
10
28
  ### Added
29
+
11
30
  - **DOM-Based URL Resolution** - Intelligent URL resolution for transformed assets
12
31
  - Added `resolveUrlFromDOM()` to find actual rendered URLs in the DOM
13
32
  - Automatically resolves Vercel Blob Storage URLs from v0.dev
@@ -15,18 +34,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
34
  - Perfect for environments where image URLs are transformed at runtime
16
35
 
17
36
  ### Changed
37
+
18
38
  - **Enhanced URL Normalization** - Multi-tier resolution strategy
19
39
  1. First attempts to find actual URL in DOM (for v0/Blob Storage)
20
40
  2. Falls back to `window.location.origin` conversion
21
41
  3. Warns about localhost URLs for better developer experience
22
42
 
23
43
  ### Fixed
44
+
24
45
  - Relative URLs now work correctly in v0.dev and Vercel Blob Storage environments
25
46
  - Mockup generation can access transformed image URLs with hash suffixes
26
47
 
27
48
  ## [0.1.8] - 2025-10-16
28
49
 
29
50
  ### Added
51
+
30
52
  - **Automatic URL Normalization** - Relative artwork URLs are now automatically converted to absolute URLs
31
53
  - Added `normalizeImageUrl()` utility function in `utils/url.ts`
32
54
  - Supports `/images/art.jpg`, `./art.jpg`, `../art.jpg` patterns
@@ -34,6 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
34
56
  - SSR-safe with helpful warnings
35
57
 
36
58
  ### Changed
59
+
37
60
  - **Design Generation** - All artwork URLs are normalized in `createDesignForPlacements()`
38
61
  - Handles placement designs, provided images, and default URLs
39
62
  - Single source of truth for URL normalization
@@ -42,12 +65,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
42
65
  - Clear error messages for invalid formats
43
66
 
44
67
  ### Fixed
68
+
45
69
  - Artwork URLs no longer require absolute paths - relative paths work seamlessly
46
70
  - Better developer experience matching Next.js image patterns
47
71
 
48
72
  ## [0.1.7] - 2025-10-16
49
73
 
50
74
  ### Changed
75
+
51
76
  - Version bump to maintain parity with @snowcone-app/ui 0.1.11
52
77
  - No functional changes in this release
53
78
 
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`);
@@ -5459,12 +5486,11 @@ ${versionToSend}
5459
5486
  var DEFAULT_GRANT_BASE = "https://api.snowcone.app";
5460
5487
  async function mintRealtimeGrant(opts) {
5461
5488
  const f = opts.fetch ?? globalThis.fetch;
5489
+ const headers = { "Content-Type": "application/json" };
5490
+ if (opts.apiKey) headers.Authorization = `Bearer ${opts.apiKey}`;
5462
5491
  const res = await f(`${opts.base ?? DEFAULT_GRANT_BASE}/realtime/grant`, {
5463
5492
  method: "POST",
5464
- headers: {
5465
- "Content-Type": "application/json",
5466
- Authorization: `Bearer ${opts.apiKey}`
5467
- },
5493
+ headers,
5468
5494
  body: JSON.stringify({ shop: opts.shop })
5469
5495
  });
5470
5496
  if (!res.ok) {
@@ -5566,11 +5592,32 @@ var RenderSession = class {
5566
5592
  * the assets referenced inside — no pixels are uploaded. Results arrive via
5567
5593
  * {@link RenderSession.onMockups}. Safe to call repeatedly (e.g. on every
5568
5594
  * canvas edit); pass `throttleMs` to debounce during live dragging.
5595
+ *
5596
+ * `placement` is the print-area NAME (e.g. `'Front'`, the product's
5597
+ * `placements[].label`) — a different id from the product's `mockupIds`, and it
5598
+ * must match an artboard in `state`. The server renders only once it has a state
5599
+ * for EVERY required placement, so on a multi-placement product call this once
5600
+ * per placement; a missing one surfaces as an `incomplete_canvas_placements`
5601
+ * error on {@link RenderSession.onError}.
5602
+ *
5603
+ * `state` is the flattened `serializeStateForServer()` shape — NOT the
5604
+ * un-flattened shape `createDesignState` takes.
5569
5605
  */
5570
5606
  async renderState(placement, state, throttleMs = 0) {
5571
5607
  await this.connect();
5572
5608
  this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
5573
5609
  }
5610
+ /**
5611
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
5612
+ * the `stateId`, fetches the referenced assets, and renders — no pixels are
5613
+ * uploaded and you don't need to hold the canvas JSON. Ideal for re-rendering
5614
+ * a stored design (fulfillment, agents, server-to-server). Results arrive via
5615
+ * {@link RenderSession.onMockups}.
5616
+ */
5617
+ async renderSavedState(placement, stateId) {
5618
+ await this.connect();
5619
+ this.svc.sendCanvasStateRef(placement, stateId);
5620
+ }
5574
5621
  /** Update only the mockup ids to render (reuses the current state). */
5575
5622
  updateMockupIds(mockupIds) {
5576
5623
  if (this.product) this.product = { ...this.product, mockupIds };
@@ -5603,6 +5650,29 @@ var RenderSession = class {
5603
5650
  }
5604
5651
  };
5605
5652
 
5653
+ // src/realtime/design-state.ts
5654
+ var TRANSPARENT_1X1_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
5655
+ async function createDesignState(input) {
5656
+ const f = input.fetch ?? globalThis.fetch;
5657
+ const res = await f(`${input.base ?? DEFAULT_GRANT_BASE}/snowcone/design-states`, {
5658
+ method: "POST",
5659
+ headers: { "Content-Type": "application/json" },
5660
+ body: JSON.stringify({
5661
+ productId: input.productId,
5662
+ stateJson: JSON.stringify(input.state),
5663
+ previewBase64: input.previewBase64 ?? TRANSPARENT_1X1_PNG
5664
+ })
5665
+ });
5666
+ if (!res.ok) {
5667
+ const detail = await res.text().catch(() => "");
5668
+ throw new Error(`createDesignState failed: ${res.status}${detail ? ` ${detail}` : ""}`);
5669
+ }
5670
+ const body = await res.json();
5671
+ const ds = body?.designState;
5672
+ if (!ds?.id) throw new Error("createDesignState: response missing designState.id");
5673
+ return { stateId: ds.id, stateUrl: ds.stateUrl ?? "", previewUrl: ds.previewUrl ?? "" };
5674
+ }
5675
+
5606
5676
  // src/index.ts
5607
5677
  function getFetcher(config) {
5608
5678
  return config?.fetcher || globalThis.fetch.bind(globalThis);
@@ -5728,6 +5798,7 @@ function createClient(config) {
5728
5798
  createComponent,
5729
5799
  createContextProvider,
5730
5800
  createDesignForPlacements,
5801
+ createDesignState,
5731
5802
  createDevFetcher,
5732
5803
  createErrorHandler,
5733
5804
  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?: {
@@ -2519,14 +2518,26 @@ declare const DEFAULT_GRANT_BASE = "https://api.snowcone.app";
2519
2518
  * own the target `shop`. Returns a 60s grant your browser code opens the WS
2520
2519
  * with (hand it down via your own endpoint + {@link fetchRealtimeGrant}).
2521
2520
  *
2521
+ * Omit `apiKey` (or pass `undefined`/`null`/`''`) to use the **keyless
2522
+ * publishable path**: no `Authorization` header is sent and the grant is
2523
+ * authorized by the publishable `shop` id alone. Passing a key uses the
2524
+ * secret (sk_) path.
2525
+ *
2522
2526
  * @example
2523
2527
  * // your backend route: POST /api/realtime/grant
2524
2528
  * const grant = await mintRealtimeGrant({ apiKey: process.env.SNOWCONE_API_KEY!, shop });
2525
2529
  * return Response.json(grant);
2530
+ *
2531
+ * @example
2532
+ * // keyless publishable path — quick trials, no API key:
2533
+ * const grant = await mintRealtimeGrant({ shop });
2526
2534
  */
2527
2535
  declare function mintRealtimeGrant(opts: {
2528
- /** Secret API key (sk_) with the `mockups:realtime` or `mockups` scope. */
2529
- apiKey: string;
2536
+ /**
2537
+ * Secret API key (sk_) with the `mockups:realtime` or `mockups` scope.
2538
+ * Omit (or pass `undefined`/`null`/`''`) for the keyless publishable path.
2539
+ */
2540
+ apiKey?: string | null;
2530
2541
  /** Target shop id (= shop.id). Must belong to the key's organization. */
2531
2542
  shop: string;
2532
2543
  /** Backend base URL. Defaults to {@link DEFAULT_GRANT_BASE}. */
@@ -2581,8 +2592,18 @@ interface RenderState {
2581
2592
  /** Product the session renders against (Snowcone catalog ids). */
2582
2593
  interface RenderProduct {
2583
2594
  productId: string;
2595
+ /**
2596
+ * The mockup scenes to render — opaque catalog codes (the product's
2597
+ * `mockups[].id`, e.g. `'FV1qjO'`). These are a DIFFERENT namespace from the
2598
+ * `placement` you pass to {@link RenderSession.renderState} (a print-area name
2599
+ * like `'Front'`). Don't pass a placement name here.
2600
+ */
2584
2601
  mockupIds: string[];
2585
- /** Optional variant (gvid). Omit if the product has no variant axis. */
2602
+ /**
2603
+ * Optional variant (gvid). Omit if the product has no variant axis. Required
2604
+ * for products with a color option — it auto-fills the color placement; without
2605
+ * it the render waits for a color blob that never comes.
2606
+ */
2586
2607
  variantId?: string;
2587
2608
  /** Render width in px (default 1000). */
2588
2609
  width?: number;
@@ -2631,8 +2652,26 @@ declare class RenderSession {
2631
2652
  * the assets referenced inside — no pixels are uploaded. Results arrive via
2632
2653
  * {@link RenderSession.onMockups}. Safe to call repeatedly (e.g. on every
2633
2654
  * canvas edit); pass `throttleMs` to debounce during live dragging.
2655
+ *
2656
+ * `placement` is the print-area NAME (e.g. `'Front'`, the product's
2657
+ * `placements[].label`) — a different id from the product's `mockupIds`, and it
2658
+ * must match an artboard in `state`. The server renders only once it has a state
2659
+ * for EVERY required placement, so on a multi-placement product call this once
2660
+ * per placement; a missing one surfaces as an `incomplete_canvas_placements`
2661
+ * error on {@link RenderSession.onError}.
2662
+ *
2663
+ * `state` is the flattened `serializeStateForServer()` shape — NOT the
2664
+ * un-flattened shape `createDesignState` takes.
2634
2665
  */
2635
2666
  renderState(placement: string, state: RenderState, throttleMs?: number): Promise<void>;
2667
+ /**
2668
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
2669
+ * the `stateId`, fetches the referenced assets, and renders — no pixels are
2670
+ * uploaded and you don't need to hold the canvas JSON. Ideal for re-rendering
2671
+ * a stored design (fulfillment, agents, server-to-server). Results arrive via
2672
+ * {@link RenderSession.onMockups}.
2673
+ */
2674
+ renderSavedState(placement: string, stateId: string): Promise<void>;
2636
2675
  /** Update only the mockup ids to render (reuses the current state). */
2637
2676
  updateMockupIds(mockupIds: string[]): void;
2638
2677
  /** Current rendered results. */
@@ -2644,6 +2683,45 @@ declare class RenderSession {
2644
2683
  private toConfig;
2645
2684
  }
2646
2685
 
2686
+ interface CreateDesignStateInput {
2687
+ /** Catalog product code the design targets. */
2688
+ productId: string;
2689
+ /**
2690
+ * The canvas state to persist — the **raw, un-flattened** stored shape:
2691
+ * top-level `elements` with `transformType` + nested `transformData`, plus
2692
+ * bare `artboards` metadata. The server flattens this at render time
2693
+ * (`normalizeCanvasState`).
2694
+ *
2695
+ * ⚠️ This is NOT the `serializeStateForServer()` output you pass to
2696
+ * `RenderSession.renderState` — that shape is already flattened and will
2697
+ * mis-render if stored here. Persist the raw canvas state (e.g. `toJSON()`
2698
+ * from the editor), not the server-render payload.
2699
+ */
2700
+ state: RenderState | Record<string, unknown>;
2701
+ /**
2702
+ * Preview thumbnail as a base64 PNG/JPEG (no data: prefix). Optional —
2703
+ * defaults to a 1×1 transparent PNG, which is fine for render-only use.
2704
+ */
2705
+ previewBase64?: string;
2706
+ /** Backend base URL. Defaults to {@link DEFAULT_GRANT_BASE}. */
2707
+ base?: string;
2708
+ /** Override fetch (tests / non-global-fetch runtimes). */
2709
+ fetch?: typeof fetch;
2710
+ }
2711
+ interface CreatedDesignState {
2712
+ /** The id to pass to `renderSavedState(placement, stateId)`. */
2713
+ stateId: string;
2714
+ /** Content-addressed URL of the persisted state JSON. */
2715
+ stateUrl: string;
2716
+ /** Content-addressed URL of the preview image. */
2717
+ previewUrl: string;
2718
+ }
2719
+ /**
2720
+ * Persist a canvas state and return its `stateId` (+ URLs). Pair with
2721
+ * `RenderSession.renderSavedState(placement, stateId)`.
2722
+ */
2723
+ declare function createDesignState(input: CreateDesignStateInput): Promise<CreatedDesignState>;
2724
+
2647
2725
  interface ProductPlacement {
2648
2726
  label: string;
2649
2727
  x: number;
@@ -2698,4 +2776,4 @@ declare function getProduct(idOrSlug: string, config?: Partial<SdkConfig>): Prom
2698
2776
  */
2699
2777
  declare function createClient(config: SdkConfig): SnowconeClient;
2700
2778
 
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 };
2779
+ 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?: {
@@ -2519,14 +2518,26 @@ declare const DEFAULT_GRANT_BASE = "https://api.snowcone.app";
2519
2518
  * own the target `shop`. Returns a 60s grant your browser code opens the WS
2520
2519
  * with (hand it down via your own endpoint + {@link fetchRealtimeGrant}).
2521
2520
  *
2521
+ * Omit `apiKey` (or pass `undefined`/`null`/`''`) to use the **keyless
2522
+ * publishable path**: no `Authorization` header is sent and the grant is
2523
+ * authorized by the publishable `shop` id alone. Passing a key uses the
2524
+ * secret (sk_) path.
2525
+ *
2522
2526
  * @example
2523
2527
  * // your backend route: POST /api/realtime/grant
2524
2528
  * const grant = await mintRealtimeGrant({ apiKey: process.env.SNOWCONE_API_KEY!, shop });
2525
2529
  * return Response.json(grant);
2530
+ *
2531
+ * @example
2532
+ * // keyless publishable path — quick trials, no API key:
2533
+ * const grant = await mintRealtimeGrant({ shop });
2526
2534
  */
2527
2535
  declare function mintRealtimeGrant(opts: {
2528
- /** Secret API key (sk_) with the `mockups:realtime` or `mockups` scope. */
2529
- apiKey: string;
2536
+ /**
2537
+ * Secret API key (sk_) with the `mockups:realtime` or `mockups` scope.
2538
+ * Omit (or pass `undefined`/`null`/`''`) for the keyless publishable path.
2539
+ */
2540
+ apiKey?: string | null;
2530
2541
  /** Target shop id (= shop.id). Must belong to the key's organization. */
2531
2542
  shop: string;
2532
2543
  /** Backend base URL. Defaults to {@link DEFAULT_GRANT_BASE}. */
@@ -2581,8 +2592,18 @@ interface RenderState {
2581
2592
  /** Product the session renders against (Snowcone catalog ids). */
2582
2593
  interface RenderProduct {
2583
2594
  productId: string;
2595
+ /**
2596
+ * The mockup scenes to render — opaque catalog codes (the product's
2597
+ * `mockups[].id`, e.g. `'FV1qjO'`). These are a DIFFERENT namespace from the
2598
+ * `placement` you pass to {@link RenderSession.renderState} (a print-area name
2599
+ * like `'Front'`). Don't pass a placement name here.
2600
+ */
2584
2601
  mockupIds: string[];
2585
- /** Optional variant (gvid). Omit if the product has no variant axis. */
2602
+ /**
2603
+ * Optional variant (gvid). Omit if the product has no variant axis. Required
2604
+ * for products with a color option — it auto-fills the color placement; without
2605
+ * it the render waits for a color blob that never comes.
2606
+ */
2586
2607
  variantId?: string;
2587
2608
  /** Render width in px (default 1000). */
2588
2609
  width?: number;
@@ -2631,8 +2652,26 @@ declare class RenderSession {
2631
2652
  * the assets referenced inside — no pixels are uploaded. Results arrive via
2632
2653
  * {@link RenderSession.onMockups}. Safe to call repeatedly (e.g. on every
2633
2654
  * canvas edit); pass `throttleMs` to debounce during live dragging.
2655
+ *
2656
+ * `placement` is the print-area NAME (e.g. `'Front'`, the product's
2657
+ * `placements[].label`) — a different id from the product's `mockupIds`, and it
2658
+ * must match an artboard in `state`. The server renders only once it has a state
2659
+ * for EVERY required placement, so on a multi-placement product call this once
2660
+ * per placement; a missing one surfaces as an `incomplete_canvas_placements`
2661
+ * error on {@link RenderSession.onError}.
2662
+ *
2663
+ * `state` is the flattened `serializeStateForServer()` shape — NOT the
2664
+ * un-flattened shape `createDesignState` takes.
2634
2665
  */
2635
2666
  renderState(placement: string, state: RenderState, throttleMs?: number): Promise<void>;
2667
+ /**
2668
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
2669
+ * the `stateId`, fetches the referenced assets, and renders — no pixels are
2670
+ * uploaded and you don't need to hold the canvas JSON. Ideal for re-rendering
2671
+ * a stored design (fulfillment, agents, server-to-server). Results arrive via
2672
+ * {@link RenderSession.onMockups}.
2673
+ */
2674
+ renderSavedState(placement: string, stateId: string): Promise<void>;
2636
2675
  /** Update only the mockup ids to render (reuses the current state). */
2637
2676
  updateMockupIds(mockupIds: string[]): void;
2638
2677
  /** Current rendered results. */
@@ -2644,6 +2683,45 @@ declare class RenderSession {
2644
2683
  private toConfig;
2645
2684
  }
2646
2685
 
2686
+ interface CreateDesignStateInput {
2687
+ /** Catalog product code the design targets. */
2688
+ productId: string;
2689
+ /**
2690
+ * The canvas state to persist — the **raw, un-flattened** stored shape:
2691
+ * top-level `elements` with `transformType` + nested `transformData`, plus
2692
+ * bare `artboards` metadata. The server flattens this at render time
2693
+ * (`normalizeCanvasState`).
2694
+ *
2695
+ * ⚠️ This is NOT the `serializeStateForServer()` output you pass to
2696
+ * `RenderSession.renderState` — that shape is already flattened and will
2697
+ * mis-render if stored here. Persist the raw canvas state (e.g. `toJSON()`
2698
+ * from the editor), not the server-render payload.
2699
+ */
2700
+ state: RenderState | Record<string, unknown>;
2701
+ /**
2702
+ * Preview thumbnail as a base64 PNG/JPEG (no data: prefix). Optional —
2703
+ * defaults to a 1×1 transparent PNG, which is fine for render-only use.
2704
+ */
2705
+ previewBase64?: string;
2706
+ /** Backend base URL. Defaults to {@link DEFAULT_GRANT_BASE}. */
2707
+ base?: string;
2708
+ /** Override fetch (tests / non-global-fetch runtimes). */
2709
+ fetch?: typeof fetch;
2710
+ }
2711
+ interface CreatedDesignState {
2712
+ /** The id to pass to `renderSavedState(placement, stateId)`. */
2713
+ stateId: string;
2714
+ /** Content-addressed URL of the persisted state JSON. */
2715
+ stateUrl: string;
2716
+ /** Content-addressed URL of the preview image. */
2717
+ previewUrl: string;
2718
+ }
2719
+ /**
2720
+ * Persist a canvas state and return its `stateId` (+ URLs). Pair with
2721
+ * `RenderSession.renderSavedState(placement, stateId)`.
2722
+ */
2723
+ declare function createDesignState(input: CreateDesignStateInput): Promise<CreatedDesignState>;
2724
+
2647
2725
  interface ProductPlacement {
2648
2726
  label: string;
2649
2727
  x: number;
@@ -2698,4 +2776,4 @@ declare function getProduct(idOrSlug: string, config?: Partial<SdkConfig>): Prom
2698
2776
  */
2699
2777
  declare function createClient(config: SdkConfig): SnowconeClient;
2700
2778
 
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 };
2779
+ 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";
@@ -4653,12 +4653,11 @@ function createSvelteComponent(descriptor, svelte) {
4653
4653
  var DEFAULT_GRANT_BASE = "https://api.snowcone.app";
4654
4654
  async function mintRealtimeGrant(opts) {
4655
4655
  const f = opts.fetch ?? globalThis.fetch;
4656
+ const headers = { "Content-Type": "application/json" };
4657
+ if (opts.apiKey) headers.Authorization = `Bearer ${opts.apiKey}`;
4656
4658
  const res = await f(`${opts.base ?? DEFAULT_GRANT_BASE}/realtime/grant`, {
4657
4659
  method: "POST",
4658
- headers: {
4659
- "Content-Type": "application/json",
4660
- Authorization: `Bearer ${opts.apiKey}`
4661
- },
4660
+ headers,
4662
4661
  body: JSON.stringify({ shop: opts.shop })
4663
4662
  });
4664
4663
  if (!res.ok) {
@@ -4760,11 +4759,32 @@ var RenderSession = class {
4760
4759
  * the assets referenced inside — no pixels are uploaded. Results arrive via
4761
4760
  * {@link RenderSession.onMockups}. Safe to call repeatedly (e.g. on every
4762
4761
  * canvas edit); pass `throttleMs` to debounce during live dragging.
4762
+ *
4763
+ * `placement` is the print-area NAME (e.g. `'Front'`, the product's
4764
+ * `placements[].label`) — a different id from the product's `mockupIds`, and it
4765
+ * must match an artboard in `state`. The server renders only once it has a state
4766
+ * for EVERY required placement, so on a multi-placement product call this once
4767
+ * per placement; a missing one surfaces as an `incomplete_canvas_placements`
4768
+ * error on {@link RenderSession.onError}.
4769
+ *
4770
+ * `state` is the flattened `serializeStateForServer()` shape — NOT the
4771
+ * un-flattened shape `createDesignState` takes.
4763
4772
  */
4764
4773
  async renderState(placement, state, throttleMs = 0) {
4765
4774
  await this.connect();
4766
4775
  this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
4767
4776
  }
4777
+ /**
4778
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
4779
+ * the `stateId`, fetches the referenced assets, and renders — no pixels are
4780
+ * uploaded and you don't need to hold the canvas JSON. Ideal for re-rendering
4781
+ * a stored design (fulfillment, agents, server-to-server). Results arrive via
4782
+ * {@link RenderSession.onMockups}.
4783
+ */
4784
+ async renderSavedState(placement, stateId) {
4785
+ await this.connect();
4786
+ this.svc.sendCanvasStateRef(placement, stateId);
4787
+ }
4768
4788
  /** Update only the mockup ids to render (reuses the current state). */
4769
4789
  updateMockupIds(mockupIds) {
4770
4790
  if (this.product) this.product = { ...this.product, mockupIds };
@@ -4797,6 +4817,29 @@ var RenderSession = class {
4797
4817
  }
4798
4818
  };
4799
4819
 
4820
+ // src/realtime/design-state.ts
4821
+ var TRANSPARENT_1X1_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
4822
+ async function createDesignState(input) {
4823
+ const f = input.fetch ?? globalThis.fetch;
4824
+ const res = await f(`${input.base ?? DEFAULT_GRANT_BASE}/snowcone/design-states`, {
4825
+ method: "POST",
4826
+ headers: { "Content-Type": "application/json" },
4827
+ body: JSON.stringify({
4828
+ productId: input.productId,
4829
+ stateJson: JSON.stringify(input.state),
4830
+ previewBase64: input.previewBase64 ?? TRANSPARENT_1X1_PNG
4831
+ })
4832
+ });
4833
+ if (!res.ok) {
4834
+ const detail = await res.text().catch(() => "");
4835
+ throw new Error(`createDesignState failed: ${res.status}${detail ? ` ${detail}` : ""}`);
4836
+ }
4837
+ const body = await res.json();
4838
+ const ds = body?.designState;
4839
+ if (!ds?.id) throw new Error("createDesignState: response missing designState.id");
4840
+ return { stateId: ds.id, stateUrl: ds.stateUrl ?? "", previewUrl: ds.previewUrl ?? "" };
4841
+ }
4842
+
4800
4843
  // src/index.ts
4801
4844
  function getFetcher(config) {
4802
4845
  return config?.fetcher || globalThis.fetch.bind(globalThis);
@@ -4921,6 +4964,7 @@ export {
4921
4964
  createComponent,
4922
4965
  createContextProvider,
4923
4966
  createDesignForPlacements,
4967
+ createDesignState,
4924
4968
  createDevFetcher,
4925
4969
  createErrorHandler,
4926
4970
  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.1",
4
4
  "description": "Snowcone SDK for product mockups and print-on-demand",
5
5
  "keywords": [
6
6
  "merch",