@snowcone-app/ui 0.9.3 → 0.9.4

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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#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).
8
+
9
+ - Updated dependencies [[`379a042`](https://github.com/snowcone-app/snowcone-monorepo/commit/379a042eb69a26f98fe0050f694c4ccf342e55b8)]:
10
+ - @snowcone-app/sdk@0.25.0
11
+
3
12
  ## 0.9.3
4
13
 
5
14
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -6145,6 +6145,12 @@ function RealtimeProvider({
6145
6145
  },
6146
6146
  onError: (error, detail) => {
6147
6147
  console.error(`[RealtimeProvider] Render error [${detail.code}]:`, error);
6148
+ if (detail.code.startsWith("E_RERENDER_") && pendingRerenderRef.current.length > 0) {
6149
+ const ids = pendingRerenderRef.current;
6150
+ pendingRerenderRef.current = [];
6151
+ forceRerenderByConfigRef.current?.(ids);
6152
+ return;
6153
+ }
6148
6154
  if (detail.mockupId) {
6149
6155
  const id = detail.mockupId;
6150
6156
  setUnavailableMockups((prev) => prev.get(id)?.code === detail.code ? prev : new Map(prev).set(id, detail));
@@ -6170,7 +6176,9 @@ function RealtimeProvider({
6170
6176
  disconnect,
6171
6177
  flushPendingBlobs,
6172
6178
  hasPendingBlobs,
6173
- getLastSentVersion
6179
+ getLastSentVersion,
6180
+ getLatestSentVersions,
6181
+ rerender: rerenderRaw
6174
6182
  } = realtimeMockup;
6175
6183
  const mockupResultsRef = (0, import_react25.useRef)([]);
6176
6184
  const mockupSubscribersRef = (0, import_react25.useRef)(
@@ -6208,6 +6216,9 @@ function RealtimeProvider({
6208
6216
  const compositeBusyRef = (0, import_react25.useRef)(/* @__PURE__ */ new Set());
6209
6217
  const compositeCanvasRef = (0, import_react25.useRef)(null);
6210
6218
  const requestedMockupIdsRef = (0, import_react25.useRef)([]);
6219
+ const serverStampsVersionsRef = (0, import_react25.useRef)(false);
6220
+ const pendingRerenderRef = (0, import_react25.useRef)([]);
6221
+ const forceRerenderByConfigRef = (0, import_react25.useRef)(null);
6211
6222
  const fullFrameRerenderIdsRef = (0, import_react25.useRef)(/* @__PURE__ */ new Set());
6212
6223
  const fullFrameRerenderTimerRef = (0, import_react25.useRef)(null);
6213
6224
  (0, import_react25.useEffect)(
@@ -6260,6 +6271,36 @@ function RealtimeProvider({
6260
6271
  if (!croppedActiveRef.current) return;
6261
6272
  sendCropConfig(false);
6262
6273
  }, [sendCropConfig]);
6274
+ const sendFullFrameConfig = (0, import_react25.useCallback)(
6275
+ (mockupIds) => {
6276
+ const base = lastConfigBaseRef.current;
6277
+ if (!base) return;
6278
+ sendConfigRaw({
6279
+ ...base,
6280
+ mockupIds,
6281
+ placementSettings: { ...placementSettingsOverridesRef.current }
6282
+ });
6283
+ },
6284
+ [sendConfigRaw]
6285
+ );
6286
+ const forceRerenderByConfig = (0, import_react25.useCallback)(
6287
+ (failed) => {
6288
+ const inForce = requestedMockupIdsRef.current;
6289
+ const fp = (list) => [...list].sort().join(",");
6290
+ const step = fp(failed) !== fp(inForce) ? failed : inForce.slice(0, -1);
6291
+ if (step.length === 0) {
6292
+ console.error(
6293
+ `[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.`
6294
+ );
6295
+ sendFullFrameConfig(inForce);
6296
+ return;
6297
+ }
6298
+ sendFullFrameConfig(step);
6299
+ sendFullFrameConfig(inForce);
6300
+ },
6301
+ [sendFullFrameConfig]
6302
+ );
6303
+ forceRerenderByConfigRef.current = forceRerenderByConfig;
6263
6304
  const rerenderFullFrame = (0, import_react25.useCallback)(
6264
6305
  (mockupId) => {
6265
6306
  fullFrameRerenderIdsRef.current.add(mockupId);
@@ -6268,35 +6309,26 @@ function RealtimeProvider({
6268
6309
  fullFrameRerenderTimerRef.current = null;
6269
6310
  const failed = [...fullFrameRerenderIdsRef.current];
6270
6311
  fullFrameRerenderIdsRef.current.clear();
6271
- const base = lastConfigBaseRef.current;
6272
- if (!base || failed.length === 0) return;
6312
+ if (!lastConfigBaseRef.current || failed.length === 0) return;
6273
6313
  const previous = requestedMockupIdsRef.current;
6274
6314
  const ids = mergeMockupIds(previous, failed);
6275
6315
  croppedActiveRef.current = false;
6276
6316
  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);
6317
+ const fp = (list) => [...list].sort().join(",");
6318
+ if (fp(ids) !== fp(previous)) {
6319
+ sendFullFrameConfig(ids);
6285
6320
  return;
6286
6321
  }
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;
6322
+ if (serverStampsVersionsRef.current) {
6323
+ sendFullFrameConfig(ids);
6324
+ pendingRerenderRef.current = failed;
6325
+ if (rerenderRaw(failed)) return;
6326
+ pendingRerenderRef.current = [];
6294
6327
  }
6295
- send(step);
6296
- send(ids);
6328
+ forceRerenderByConfig(failed);
6297
6329
  }, 0);
6298
6330
  },
6299
- [sendConfigRaw]
6331
+ [sendFullFrameConfig, forceRerenderByConfig, rerenderRaw]
6300
6332
  );
6301
6333
  const maybeFlipCrop = (0, import_react25.useCallback)(() => {
6302
6334
  if (!croppedDeliveryEnabled || !cropCapableRef.current) return;
@@ -6331,6 +6363,10 @@ function RealtimeProvider({
6331
6363
  else merged.push(result);
6332
6364
  });
6333
6365
  mockupResultsRef.current = merged;
6366
+ if (pendingRerenderRef.current.length > 0) {
6367
+ const delivered = new Set(results.map((r) => r.mockupId));
6368
+ pendingRerenderRef.current = pendingRerenderRef.current.filter((id) => !delivered.has(id));
6369
+ }
6334
6370
  const clearedPlacements = /* @__PURE__ */ new Set();
6335
6371
  results.forEach((result) => {
6336
6372
  const wasPending = pendingMockupIdsRef.current.has(result.mockupId);
@@ -6452,6 +6488,9 @@ function RealtimeProvider({
6452
6488
  }
6453
6489
  console.log(`[RTP] rawMockupResults changed: ${rawMockupResults.length} result(s)`, rawMockupResults.map((r) => `${r.mockupId}${r.imageUrl ? " \u2713url" : " \u2717NO-URL"}${r.cropRect ? " [crop]" : ""}`));
6454
6490
  const displayable = [];
6491
+ if (!serverStampsVersionsRef.current && rawMockupResults.some((r) => r.versions && typeof r.versions === "object")) {
6492
+ serverStampsVersionsRef.current = true;
6493
+ }
6455
6494
  rawMockupResults.forEach((result) => {
6456
6495
  if (!result.imageUrl) {
6457
6496
  console.log(`[RTP] Result for ${result.mockupId} has NO imageUrl \u2014 not caching, pending NOT cleared`);
@@ -6934,6 +6973,9 @@ function RealtimeProvider({
6934
6973
  const getLastSentVersionRef = (0, import_react25.useRef)(getLastSentVersion);
6935
6974
  getLastSentVersionRef.current = getLastSentVersion;
6936
6975
  const getCanvasVersion = (0, import_react25.useCallback)(() => getLastSentVersionRef.current?.() ?? 0, []);
6976
+ const getLatestSentVersionsRef = (0, import_react25.useRef)(getLatestSentVersions);
6977
+ getLatestSentVersionsRef.current = getLatestSentVersions;
6978
+ const getLatestSentVersionsStable = (0, import_react25.useCallback)(() => getLatestSentVersionsRef.current?.() ?? {}, []);
6937
6979
  const stableSubscribePipelineSettled = (0, import_react25.useCallback)(
6938
6980
  (callback) => {
6939
6981
  return subscribePipelineSettledRef.current(callback);
@@ -6983,6 +7025,7 @@ function RealtimeProvider({
6983
7025
  getMockupUrl: getMockupUrl3,
6984
7026
  getMockupUrls,
6985
7027
  getCanvasVersion,
7028
+ getLatestSentVersions: getLatestSentVersionsStable,
6986
7029
  isMockupLoading,
6987
7030
  registerBlobUrl,
6988
7031
  // WebRTC streaming
@@ -7015,6 +7058,7 @@ function RealtimeProvider({
7015
7058
  getMockupUrl3,
7016
7059
  getMockupUrls,
7017
7060
  getCanvasVersion,
7061
+ getLatestSentVersionsStable,
7018
7062
  isMockupLoading,
7019
7063
  registerBlobUrl
7020
7064
  ]
@@ -10769,6 +10813,8 @@ var HeroProductImage = (0, import_react46.memo)(function HeroProductImage2({
10769
10813
  };
10770
10814
  }, [mode, realtimeContext?.subscribePendingBlob, placement]);
10771
10815
  const renderVersionRef = (0, import_react46.useRef)(0);
10816
+ const renderVersionsRef = (0, import_react46.useRef)(null);
10817
+ const getLatestSentVersions = realtimeContext?.getLatestSentVersions;
10772
10818
  const refusalCodeRef = (0, import_react46.useRef)(null);
10773
10819
  refusalCodeRef.current = unavailable?.code ?? null;
10774
10820
  const sessionErrorCodeRef = (0, import_react46.useRef)(null);
@@ -10785,11 +10831,13 @@ var HeroProductImage = (0, import_react46.memo)(function HeroProductImage2({
10785
10831
  return;
10786
10832
  }
10787
10833
  const rendered = renderVersionRef.current;
10788
- const behind = rendered < canvas;
10834
+ const versions = renderVersionsRef.current;
10835
+ const sent = versions ? getLatestSentVersions?.() : void 0;
10836
+ const behind = versions && sent ? Object.entries(sent).some(([placement2, v]) => (versions[placement2] ?? -1) < v) : rendered < canvas;
10789
10837
  node.dataset.canvasVersion = String(canvas);
10790
10838
  node.dataset.renderVersion = String(rendered);
10791
10839
  node.dataset.renderState = refusalCodeRef.current || behind && sessionErrorCodeRef.current ? "refused" : behind ? "stale" : "current";
10792
- }, [getCanvasVersion]);
10840
+ }, [getCanvasVersion, getLatestSentVersions]);
10793
10841
  (0, import_react46.useEffect)(() => {
10794
10842
  applyRenderStamp();
10795
10843
  });
@@ -10799,8 +10847,9 @@ var HeroProductImage = (0, import_react46.memo)(function HeroProductImage2({
10799
10847
  if (!actualMockupId || !subscribeSent || !subscribeResult) return;
10800
10848
  const offSent = subscribeSent(() => applyRenderStamp());
10801
10849
  const offResult = subscribeResult(actualMockupId, (result) => {
10802
- if (typeof result?.requestVersion === "number" && result.requestVersion > renderVersionRef.current) {
10850
+ if (typeof result?.requestVersion === "number" && result.requestVersion >= renderVersionRef.current) {
10803
10851
  renderVersionRef.current = result.requestVersion;
10852
+ renderVersionsRef.current = result.versions ?? null;
10804
10853
  }
10805
10854
  applyRenderStamp();
10806
10855
  });