@snowcone-app/ui 0.9.3 → 0.9.5

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,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4351](https://github.com/snowcone-app/snowcone-monorepo/pull/4351) [`f9910a8`](https://github.com/snowcone-app/snowcone-monorepo/commit/f9910a86ec7d3ee48d9d4fbd5099f4790ce91e98) Thanks [@kevinsproles](https://github.com/kevinsproles)! - A cropped frame whose blob: URL was already revoked (its newer frame reached the SDK before React state delivered it) no longer switches the session to full-frame delivery: it waits up to 2 s for the newer frame, and only counts as a real failure if none arrives.
8
+
9
+ ## 0.9.4
10
+
11
+ ### Patch Changes
12
+
13
+ - [#4344](https://github.com/snowcone-app/snowcone-monorepo/pull/4344) [`379a042`](https://github.com/snowcone-app/snowcone-monorepo/commit/379a042eb69a26f98fe0050f694c4ccf342e55b8) Thanks [@kevinsproles](https://github.com/kevinsproles)! - Per-placement render versions and an explicit re-render. SDK: `MockupResult.versions` (every placement folded into a frame, from boxes that stamp it), `getLatestSentVersions()`, and `rerender(mockupIds)` (answered by `rerender_queued`; refusals are typed `E_RERENDER_*` errors). UI: a picture's `data-render-state` is `current` only when every placement is at its latest sent version, and a picture whose cropped frame failed is re-rendered with `rerender` once the box stamps versions (falling back to forcing it with configs on an `E_RERENDER_*` refusal or an older box).
14
+
15
+ - Updated dependencies [[`379a042`](https://github.com/snowcone-app/snowcone-monorepo/commit/379a042eb69a26f98fe0050f694c4ccf342e55b8)]:
16
+ - @snowcone-app/sdk@0.25.0
17
+
3
18
  ## 0.9.3
4
19
 
5
20
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -6062,6 +6062,7 @@ async function compositeCroppedFrame(args) {
6062
6062
  var import_jsx_runtime52 = require("react/jsx-runtime");
6063
6063
  var DEFAULT_ARTBOARD_SIZE = 2e3;
6064
6064
  var MAX_CONCURRENT_BLOBS = 5;
6065
+ var FRAME_SUPERSEDE_GRACE_MS = 2e3;
6065
6066
  function mergeMockupIds(current, extra) {
6066
6067
  const out = [...current];
6067
6068
  for (const id of extra) if (!out.includes(id)) out.push(id);
@@ -6145,6 +6146,12 @@ function RealtimeProvider({
6145
6146
  },
6146
6147
  onError: (error, detail) => {
6147
6148
  console.error(`[RealtimeProvider] Render error [${detail.code}]:`, error);
6149
+ if (detail.code.startsWith("E_RERENDER_") && pendingRerenderRef.current.length > 0) {
6150
+ const ids = pendingRerenderRef.current;
6151
+ pendingRerenderRef.current = [];
6152
+ forceRerenderByConfigRef.current?.(ids);
6153
+ return;
6154
+ }
6148
6155
  if (detail.mockupId) {
6149
6156
  const id = detail.mockupId;
6150
6157
  setUnavailableMockups((prev) => prev.get(id)?.code === detail.code ? prev : new Map(prev).set(id, detail));
@@ -6170,7 +6177,9 @@ function RealtimeProvider({
6170
6177
  disconnect,
6171
6178
  flushPendingBlobs,
6172
6179
  hasPendingBlobs,
6173
- getLastSentVersion
6180
+ getLastSentVersion,
6181
+ getLatestSentVersions,
6182
+ rerender: rerenderRaw
6174
6183
  } = realtimeMockup;
6175
6184
  const mockupResultsRef = (0, import_react25.useRef)([]);
6176
6185
  const mockupSubscribersRef = (0, import_react25.useRef)(
@@ -6208,6 +6217,9 @@ function RealtimeProvider({
6208
6217
  const compositeBusyRef = (0, import_react25.useRef)(/* @__PURE__ */ new Set());
6209
6218
  const compositeCanvasRef = (0, import_react25.useRef)(null);
6210
6219
  const requestedMockupIdsRef = (0, import_react25.useRef)([]);
6220
+ const serverStampsVersionsRef = (0, import_react25.useRef)(false);
6221
+ const pendingRerenderRef = (0, import_react25.useRef)([]);
6222
+ const forceRerenderByConfigRef = (0, import_react25.useRef)(null);
6211
6223
  const fullFrameRerenderIdsRef = (0, import_react25.useRef)(/* @__PURE__ */ new Set());
6212
6224
  const fullFrameRerenderTimerRef = (0, import_react25.useRef)(null);
6213
6225
  (0, import_react25.useEffect)(
@@ -6260,6 +6272,36 @@ function RealtimeProvider({
6260
6272
  if (!croppedActiveRef.current) return;
6261
6273
  sendCropConfig(false);
6262
6274
  }, [sendCropConfig]);
6275
+ const sendFullFrameConfig = (0, import_react25.useCallback)(
6276
+ (mockupIds) => {
6277
+ const base = lastConfigBaseRef.current;
6278
+ if (!base) return;
6279
+ sendConfigRaw({
6280
+ ...base,
6281
+ mockupIds,
6282
+ placementSettings: { ...placementSettingsOverridesRef.current }
6283
+ });
6284
+ },
6285
+ [sendConfigRaw]
6286
+ );
6287
+ const forceRerenderByConfig = (0, import_react25.useCallback)(
6288
+ (failed) => {
6289
+ const inForce = requestedMockupIdsRef.current;
6290
+ const fp = (list) => [...list].sort().join(",");
6291
+ const step = fp(failed) !== fp(inForce) ? failed : inForce.slice(0, -1);
6292
+ if (step.length === 0) {
6293
+ console.error(
6294
+ `[RealtimeProvider] E_OVERLAY_RERENDER_UNAVAILABLE: cannot force a full-frame re-render of ${failed.join(", ")} (single-mockup session on a box without \`rerender\`); it updates on the next edit.`
6295
+ );
6296
+ sendFullFrameConfig(inForce);
6297
+ return;
6298
+ }
6299
+ sendFullFrameConfig(step);
6300
+ sendFullFrameConfig(inForce);
6301
+ },
6302
+ [sendFullFrameConfig]
6303
+ );
6304
+ forceRerenderByConfigRef.current = forceRerenderByConfig;
6263
6305
  const rerenderFullFrame = (0, import_react25.useCallback)(
6264
6306
  (mockupId) => {
6265
6307
  fullFrameRerenderIdsRef.current.add(mockupId);
@@ -6268,35 +6310,26 @@ function RealtimeProvider({
6268
6310
  fullFrameRerenderTimerRef.current = null;
6269
6311
  const failed = [...fullFrameRerenderIdsRef.current];
6270
6312
  fullFrameRerenderIdsRef.current.clear();
6271
- const base = lastConfigBaseRef.current;
6272
- if (!base || failed.length === 0) return;
6313
+ if (!lastConfigBaseRef.current || failed.length === 0) return;
6273
6314
  const previous = requestedMockupIdsRef.current;
6274
6315
  const ids = mergeMockupIds(previous, failed);
6275
6316
  croppedActiveRef.current = false;
6276
6317
  requestedMockupIdsRef.current = ids;
6277
- const send = (mockupIds) => sendConfigRaw({
6278
- ...base,
6279
- mockupIds,
6280
- placementSettings: { ...placementSettingsOverridesRef.current }
6281
- });
6282
- const fingerprint = (list) => [...list].sort().join(",");
6283
- if (fingerprint(ids) !== fingerprint(previous)) {
6284
- send(ids);
6318
+ const fp = (list) => [...list].sort().join(",");
6319
+ if (fp(ids) !== fp(previous)) {
6320
+ sendFullFrameConfig(ids);
6285
6321
  return;
6286
6322
  }
6287
- const step = fingerprint(failed) !== fingerprint(previous) ? failed : ids.slice(0, -1);
6288
- if (step.length === 0) {
6289
- console.error(
6290
- `[RealtimeProvider] E_OVERLAY_RERENDER_UNAVAILABLE: cannot force a full-frame re-render of ${failed.join(", ")} (single-mockup session); it updates on the next edit.`
6291
- );
6292
- send(ids);
6293
- return;
6323
+ if (serverStampsVersionsRef.current) {
6324
+ sendFullFrameConfig(ids);
6325
+ pendingRerenderRef.current = failed;
6326
+ if (rerenderRaw(failed)) return;
6327
+ pendingRerenderRef.current = [];
6294
6328
  }
6295
- send(step);
6296
- send(ids);
6329
+ forceRerenderByConfig(failed);
6297
6330
  }, 0);
6298
6331
  },
6299
- [sendConfigRaw]
6332
+ [sendFullFrameConfig, forceRerenderByConfig, rerenderRaw]
6300
6333
  );
6301
6334
  const maybeFlipCrop = (0, import_react25.useCallback)(() => {
6302
6335
  if (!croppedDeliveryEnabled || !cropCapableRef.current) return;
@@ -6331,6 +6364,10 @@ function RealtimeProvider({
6331
6364
  else merged.push(result);
6332
6365
  });
6333
6366
  mockupResultsRef.current = merged;
6367
+ if (pendingRerenderRef.current.length > 0) {
6368
+ const delivered = new Set(results.map((r) => r.mockupId));
6369
+ pendingRerenderRef.current = pendingRerenderRef.current.filter((id) => !delivered.has(id));
6370
+ }
6334
6371
  const clearedPlacements = /* @__PURE__ */ new Set();
6335
6372
  results.forEach((result) => {
6336
6373
  const wasPending = pendingMockupIdsRef.current.has(result.mockupId);
@@ -6418,6 +6455,22 @@ function RealtimeProvider({
6418
6455
  if (gen !== sceneGenRef.current || compositeQueueRef.current.has(result.mockupId) || processedFrameRef.current.get(result.mockupId) !== result.imageUrl) {
6419
6456
  return;
6420
6457
  }
6458
+ const message = err instanceof Error ? err.message : String(err);
6459
+ if (result.imageUrl.startsWith("blob:") && message.startsWith("E_OVERLAY_FRAME_LOAD") && message.includes(result.imageUrl.slice(0, 160))) {
6460
+ const failedUrl = result.imageUrl;
6461
+ setTimeout(() => {
6462
+ if (gen !== sceneGenRef.current) return;
6463
+ if (compositeQueueRef.current.has(result.mockupId)) return;
6464
+ if (processedFrameRef.current.get(result.mockupId) !== failedUrl) return;
6465
+ console.error(
6466
+ `[RealtimeProvider] E_OVERLAY_COMPOSITE_FAILED: frame for ${result.mockupId} failed to load and no newer frame arrived within ${FRAME_SUPERSEDE_GRACE_MS} ms \u2014 full-frame delivery for the rest of this session.`,
6467
+ err
6468
+ );
6469
+ cropCapableRef.current = false;
6470
+ rerenderFullFrame(result.mockupId);
6471
+ }, FRAME_SUPERSEDE_GRACE_MS);
6472
+ return;
6473
+ }
6421
6474
  console.error(
6422
6475
  "[RealtimeProvider] E_OVERLAY_COMPOSITE_FAILED: cropped-frame composite failed \u2014 full-frame delivery for the rest of this session.",
6423
6476
  err
@@ -6452,6 +6505,9 @@ function RealtimeProvider({
6452
6505
  }
6453
6506
  console.log(`[RTP] rawMockupResults changed: ${rawMockupResults.length} result(s)`, rawMockupResults.map((r) => `${r.mockupId}${r.imageUrl ? " \u2713url" : " \u2717NO-URL"}${r.cropRect ? " [crop]" : ""}`));
6454
6507
  const displayable = [];
6508
+ if (!serverStampsVersionsRef.current && rawMockupResults.some((r) => r.versions && typeof r.versions === "object")) {
6509
+ serverStampsVersionsRef.current = true;
6510
+ }
6455
6511
  rawMockupResults.forEach((result) => {
6456
6512
  if (!result.imageUrl) {
6457
6513
  console.log(`[RTP] Result for ${result.mockupId} has NO imageUrl \u2014 not caching, pending NOT cleared`);
@@ -6934,6 +6990,9 @@ function RealtimeProvider({
6934
6990
  const getLastSentVersionRef = (0, import_react25.useRef)(getLastSentVersion);
6935
6991
  getLastSentVersionRef.current = getLastSentVersion;
6936
6992
  const getCanvasVersion = (0, import_react25.useCallback)(() => getLastSentVersionRef.current?.() ?? 0, []);
6993
+ const getLatestSentVersionsRef = (0, import_react25.useRef)(getLatestSentVersions);
6994
+ getLatestSentVersionsRef.current = getLatestSentVersions;
6995
+ const getLatestSentVersionsStable = (0, import_react25.useCallback)(() => getLatestSentVersionsRef.current?.() ?? {}, []);
6937
6996
  const stableSubscribePipelineSettled = (0, import_react25.useCallback)(
6938
6997
  (callback) => {
6939
6998
  return subscribePipelineSettledRef.current(callback);
@@ -6983,6 +7042,7 @@ function RealtimeProvider({
6983
7042
  getMockupUrl: getMockupUrl3,
6984
7043
  getMockupUrls,
6985
7044
  getCanvasVersion,
7045
+ getLatestSentVersions: getLatestSentVersionsStable,
6986
7046
  isMockupLoading,
6987
7047
  registerBlobUrl,
6988
7048
  // WebRTC streaming
@@ -7015,6 +7075,7 @@ function RealtimeProvider({
7015
7075
  getMockupUrl3,
7016
7076
  getMockupUrls,
7017
7077
  getCanvasVersion,
7078
+ getLatestSentVersionsStable,
7018
7079
  isMockupLoading,
7019
7080
  registerBlobUrl
7020
7081
  ]
@@ -10769,6 +10830,8 @@ var HeroProductImage = (0, import_react46.memo)(function HeroProductImage2({
10769
10830
  };
10770
10831
  }, [mode, realtimeContext?.subscribePendingBlob, placement]);
10771
10832
  const renderVersionRef = (0, import_react46.useRef)(0);
10833
+ const renderVersionsRef = (0, import_react46.useRef)(null);
10834
+ const getLatestSentVersions = realtimeContext?.getLatestSentVersions;
10772
10835
  const refusalCodeRef = (0, import_react46.useRef)(null);
10773
10836
  refusalCodeRef.current = unavailable?.code ?? null;
10774
10837
  const sessionErrorCodeRef = (0, import_react46.useRef)(null);
@@ -10785,11 +10848,13 @@ var HeroProductImage = (0, import_react46.memo)(function HeroProductImage2({
10785
10848
  return;
10786
10849
  }
10787
10850
  const rendered = renderVersionRef.current;
10788
- const behind = rendered < canvas;
10851
+ const versions = renderVersionsRef.current;
10852
+ const sent = versions ? getLatestSentVersions?.() : void 0;
10853
+ const behind = versions && sent ? Object.entries(sent).some(([placement2, v]) => (versions[placement2] ?? -1) < v) : rendered < canvas;
10789
10854
  node.dataset.canvasVersion = String(canvas);
10790
10855
  node.dataset.renderVersion = String(rendered);
10791
10856
  node.dataset.renderState = refusalCodeRef.current || behind && sessionErrorCodeRef.current ? "refused" : behind ? "stale" : "current";
10792
- }, [getCanvasVersion]);
10857
+ }, [getCanvasVersion, getLatestSentVersions]);
10793
10858
  (0, import_react46.useEffect)(() => {
10794
10859
  applyRenderStamp();
10795
10860
  });
@@ -10799,8 +10864,9 @@ var HeroProductImage = (0, import_react46.memo)(function HeroProductImage2({
10799
10864
  if (!actualMockupId || !subscribeSent || !subscribeResult) return;
10800
10865
  const offSent = subscribeSent(() => applyRenderStamp());
10801
10866
  const offResult = subscribeResult(actualMockupId, (result) => {
10802
- if (typeof result?.requestVersion === "number" && result.requestVersion > renderVersionRef.current) {
10867
+ if (typeof result?.requestVersion === "number" && result.requestVersion >= renderVersionRef.current) {
10803
10868
  renderVersionRef.current = result.requestVersion;
10869
+ renderVersionsRef.current = result.versions ?? null;
10804
10870
  }
10805
10871
  applyRenderStamp();
10806
10872
  });