@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/dist/index.d.cts CHANGED
@@ -2930,6 +2930,8 @@ interface MockupResult {
2930
2930
  imageUrl: string;
2931
2931
  /** The canvas version this frame was rendered from (see getCanvasVersion). */
2932
2932
  requestVersion?: number;
2933
+ /** Per-placement versions folded into this frame (servers with per-placement stamps; see getLatestSentVersions). */
2934
+ versions?: Record<string, number>;
2933
2935
  }
2934
2936
  type RealtimeState = RealtimeContextValue;
2935
2937
  interface RealtimeContextValue {
@@ -3052,6 +3054,12 @@ interface RealtimeContextValue {
3052
3054
  * older design. Read it after a `subscribeBlobSent` notification.
3053
3055
  */
3054
3056
  getCanvasVersion: () => number;
3057
+ /**
3058
+ * The latest canvas version sent per placement. With a server that stamps
3059
+ * `versions` on every frame, a picture is current exactly when, for every
3060
+ * placement here, its frame's `versions[placement]` is at least this.
3061
+ */
3062
+ getLatestSentVersions: () => Record<string, number>;
3055
3063
  flushPendingBlobs: () => void;
3056
3064
  hasPendingBlobs: () => boolean;
3057
3065
  subscribePipelineSettled: (callback: () => void) => () => void;
package/dist/index.d.ts CHANGED
@@ -2930,6 +2930,8 @@ interface MockupResult {
2930
2930
  imageUrl: string;
2931
2931
  /** The canvas version this frame was rendered from (see getCanvasVersion). */
2932
2932
  requestVersion?: number;
2933
+ /** Per-placement versions folded into this frame (servers with per-placement stamps; see getLatestSentVersions). */
2934
+ versions?: Record<string, number>;
2933
2935
  }
2934
2936
  type RealtimeState = RealtimeContextValue;
2935
2937
  interface RealtimeContextValue {
@@ -3052,6 +3054,12 @@ interface RealtimeContextValue {
3052
3054
  * older design. Read it after a `subscribeBlobSent` notification.
3053
3055
  */
3054
3056
  getCanvasVersion: () => number;
3057
+ /**
3058
+ * The latest canvas version sent per placement. With a server that stamps
3059
+ * `versions` on every frame, a picture is current exactly when, for every
3060
+ * placement here, its frame's `versions[placement]` is at least this.
3061
+ */
3062
+ getLatestSentVersions: () => Record<string, number>;
3055
3063
  flushPendingBlobs: () => void;
3056
3064
  hasPendingBlobs: () => boolean;
3057
3065
  subscribePipelineSettled: (callback: () => void) => () => void;
package/dist/index.js CHANGED
@@ -5786,6 +5786,12 @@ function RealtimeProvider({
5786
5786
  },
5787
5787
  onError: (error, detail) => {
5788
5788
  console.error(`[RealtimeProvider] Render error [${detail.code}]:`, error);
5789
+ if (detail.code.startsWith("E_RERENDER_") && pendingRerenderRef.current.length > 0) {
5790
+ const ids = pendingRerenderRef.current;
5791
+ pendingRerenderRef.current = [];
5792
+ forceRerenderByConfigRef.current?.(ids);
5793
+ return;
5794
+ }
5789
5795
  if (detail.mockupId) {
5790
5796
  const id = detail.mockupId;
5791
5797
  setUnavailableMockups((prev) => prev.get(id)?.code === detail.code ? prev : new Map(prev).set(id, detail));
@@ -5811,7 +5817,9 @@ function RealtimeProvider({
5811
5817
  disconnect,
5812
5818
  flushPendingBlobs,
5813
5819
  hasPendingBlobs,
5814
- getLastSentVersion
5820
+ getLastSentVersion,
5821
+ getLatestSentVersions,
5822
+ rerender: rerenderRaw
5815
5823
  } = realtimeMockup;
5816
5824
  const mockupResultsRef = useRef12([]);
5817
5825
  const mockupSubscribersRef = useRef12(
@@ -5849,6 +5857,9 @@ function RealtimeProvider({
5849
5857
  const compositeBusyRef = useRef12(/* @__PURE__ */ new Set());
5850
5858
  const compositeCanvasRef = useRef12(null);
5851
5859
  const requestedMockupIdsRef = useRef12([]);
5860
+ const serverStampsVersionsRef = useRef12(false);
5861
+ const pendingRerenderRef = useRef12([]);
5862
+ const forceRerenderByConfigRef = useRef12(null);
5852
5863
  const fullFrameRerenderIdsRef = useRef12(/* @__PURE__ */ new Set());
5853
5864
  const fullFrameRerenderTimerRef = useRef12(null);
5854
5865
  useEffect17(
@@ -5901,6 +5912,36 @@ function RealtimeProvider({
5901
5912
  if (!croppedActiveRef.current) return;
5902
5913
  sendCropConfig(false);
5903
5914
  }, [sendCropConfig]);
5915
+ const sendFullFrameConfig = useCallback10(
5916
+ (mockupIds) => {
5917
+ const base = lastConfigBaseRef.current;
5918
+ if (!base) return;
5919
+ sendConfigRaw({
5920
+ ...base,
5921
+ mockupIds,
5922
+ placementSettings: { ...placementSettingsOverridesRef.current }
5923
+ });
5924
+ },
5925
+ [sendConfigRaw]
5926
+ );
5927
+ const forceRerenderByConfig = useCallback10(
5928
+ (failed) => {
5929
+ const inForce = requestedMockupIdsRef.current;
5930
+ const fp = (list) => [...list].sort().join(",");
5931
+ const step = fp(failed) !== fp(inForce) ? failed : inForce.slice(0, -1);
5932
+ if (step.length === 0) {
5933
+ console.error(
5934
+ `[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.`
5935
+ );
5936
+ sendFullFrameConfig(inForce);
5937
+ return;
5938
+ }
5939
+ sendFullFrameConfig(step);
5940
+ sendFullFrameConfig(inForce);
5941
+ },
5942
+ [sendFullFrameConfig]
5943
+ );
5944
+ forceRerenderByConfigRef.current = forceRerenderByConfig;
5904
5945
  const rerenderFullFrame = useCallback10(
5905
5946
  (mockupId) => {
5906
5947
  fullFrameRerenderIdsRef.current.add(mockupId);
@@ -5909,35 +5950,26 @@ function RealtimeProvider({
5909
5950
  fullFrameRerenderTimerRef.current = null;
5910
5951
  const failed = [...fullFrameRerenderIdsRef.current];
5911
5952
  fullFrameRerenderIdsRef.current.clear();
5912
- const base = lastConfigBaseRef.current;
5913
- if (!base || failed.length === 0) return;
5953
+ if (!lastConfigBaseRef.current || failed.length === 0) return;
5914
5954
  const previous = requestedMockupIdsRef.current;
5915
5955
  const ids = mergeMockupIds(previous, failed);
5916
5956
  croppedActiveRef.current = false;
5917
5957
  requestedMockupIdsRef.current = ids;
5918
- const send = (mockupIds) => sendConfigRaw({
5919
- ...base,
5920
- mockupIds,
5921
- placementSettings: { ...placementSettingsOverridesRef.current }
5922
- });
5923
- const fingerprint = (list) => [...list].sort().join(",");
5924
- if (fingerprint(ids) !== fingerprint(previous)) {
5925
- send(ids);
5958
+ const fp = (list) => [...list].sort().join(",");
5959
+ if (fp(ids) !== fp(previous)) {
5960
+ sendFullFrameConfig(ids);
5926
5961
  return;
5927
5962
  }
5928
- const step = fingerprint(failed) !== fingerprint(previous) ? failed : ids.slice(0, -1);
5929
- if (step.length === 0) {
5930
- console.error(
5931
- `[RealtimeProvider] E_OVERLAY_RERENDER_UNAVAILABLE: cannot force a full-frame re-render of ${failed.join(", ")} (single-mockup session); it updates on the next edit.`
5932
- );
5933
- send(ids);
5934
- return;
5963
+ if (serverStampsVersionsRef.current) {
5964
+ sendFullFrameConfig(ids);
5965
+ pendingRerenderRef.current = failed;
5966
+ if (rerenderRaw(failed)) return;
5967
+ pendingRerenderRef.current = [];
5935
5968
  }
5936
- send(step);
5937
- send(ids);
5969
+ forceRerenderByConfig(failed);
5938
5970
  }, 0);
5939
5971
  },
5940
- [sendConfigRaw]
5972
+ [sendFullFrameConfig, forceRerenderByConfig, rerenderRaw]
5941
5973
  );
5942
5974
  const maybeFlipCrop = useCallback10(() => {
5943
5975
  if (!croppedDeliveryEnabled || !cropCapableRef.current) return;
@@ -5972,6 +6004,10 @@ function RealtimeProvider({
5972
6004
  else merged.push(result);
5973
6005
  });
5974
6006
  mockupResultsRef.current = merged;
6007
+ if (pendingRerenderRef.current.length > 0) {
6008
+ const delivered = new Set(results.map((r) => r.mockupId));
6009
+ pendingRerenderRef.current = pendingRerenderRef.current.filter((id) => !delivered.has(id));
6010
+ }
5975
6011
  const clearedPlacements = /* @__PURE__ */ new Set();
5976
6012
  results.forEach((result) => {
5977
6013
  const wasPending = pendingMockupIdsRef.current.has(result.mockupId);
@@ -6093,6 +6129,9 @@ function RealtimeProvider({
6093
6129
  }
6094
6130
  console.log(`[RTP] rawMockupResults changed: ${rawMockupResults.length} result(s)`, rawMockupResults.map((r) => `${r.mockupId}${r.imageUrl ? " \u2713url" : " \u2717NO-URL"}${r.cropRect ? " [crop]" : ""}`));
6095
6131
  const displayable = [];
6132
+ if (!serverStampsVersionsRef.current && rawMockupResults.some((r) => r.versions && typeof r.versions === "object")) {
6133
+ serverStampsVersionsRef.current = true;
6134
+ }
6096
6135
  rawMockupResults.forEach((result) => {
6097
6136
  if (!result.imageUrl) {
6098
6137
  console.log(`[RTP] Result for ${result.mockupId} has NO imageUrl \u2014 not caching, pending NOT cleared`);
@@ -6575,6 +6614,9 @@ function RealtimeProvider({
6575
6614
  const getLastSentVersionRef = useRef12(getLastSentVersion);
6576
6615
  getLastSentVersionRef.current = getLastSentVersion;
6577
6616
  const getCanvasVersion = useCallback10(() => getLastSentVersionRef.current?.() ?? 0, []);
6617
+ const getLatestSentVersionsRef = useRef12(getLatestSentVersions);
6618
+ getLatestSentVersionsRef.current = getLatestSentVersions;
6619
+ const getLatestSentVersionsStable = useCallback10(() => getLatestSentVersionsRef.current?.() ?? {}, []);
6578
6620
  const stableSubscribePipelineSettled = useCallback10(
6579
6621
  (callback) => {
6580
6622
  return subscribePipelineSettledRef.current(callback);
@@ -6624,6 +6666,7 @@ function RealtimeProvider({
6624
6666
  getMockupUrl: getMockupUrl3,
6625
6667
  getMockupUrls,
6626
6668
  getCanvasVersion,
6669
+ getLatestSentVersions: getLatestSentVersionsStable,
6627
6670
  isMockupLoading,
6628
6671
  registerBlobUrl,
6629
6672
  // WebRTC streaming
@@ -6656,6 +6699,7 @@ function RealtimeProvider({
6656
6699
  getMockupUrl3,
6657
6700
  getMockupUrls,
6658
6701
  getCanvasVersion,
6702
+ getLatestSentVersionsStable,
6659
6703
  isMockupLoading,
6660
6704
  registerBlobUrl
6661
6705
  ]
@@ -10451,6 +10495,8 @@ var HeroProductImage = memo3(function HeroProductImage2({
10451
10495
  };
10452
10496
  }, [mode, realtimeContext?.subscribePendingBlob, placement]);
10453
10497
  const renderVersionRef = useRef23(0);
10498
+ const renderVersionsRef = useRef23(null);
10499
+ const getLatestSentVersions = realtimeContext?.getLatestSentVersions;
10454
10500
  const refusalCodeRef = useRef23(null);
10455
10501
  refusalCodeRef.current = unavailable?.code ?? null;
10456
10502
  const sessionErrorCodeRef = useRef23(null);
@@ -10467,11 +10513,13 @@ var HeroProductImage = memo3(function HeroProductImage2({
10467
10513
  return;
10468
10514
  }
10469
10515
  const rendered = renderVersionRef.current;
10470
- const behind = rendered < canvas;
10516
+ const versions = renderVersionsRef.current;
10517
+ const sent = versions ? getLatestSentVersions?.() : void 0;
10518
+ const behind = versions && sent ? Object.entries(sent).some(([placement2, v]) => (versions[placement2] ?? -1) < v) : rendered < canvas;
10471
10519
  node.dataset.canvasVersion = String(canvas);
10472
10520
  node.dataset.renderVersion = String(rendered);
10473
10521
  node.dataset.renderState = refusalCodeRef.current || behind && sessionErrorCodeRef.current ? "refused" : behind ? "stale" : "current";
10474
- }, [getCanvasVersion]);
10522
+ }, [getCanvasVersion, getLatestSentVersions]);
10475
10523
  useEffect32(() => {
10476
10524
  applyRenderStamp();
10477
10525
  });
@@ -10481,8 +10529,9 @@ var HeroProductImage = memo3(function HeroProductImage2({
10481
10529
  if (!actualMockupId || !subscribeSent || !subscribeResult) return;
10482
10530
  const offSent = subscribeSent(() => applyRenderStamp());
10483
10531
  const offResult = subscribeResult(actualMockupId, (result) => {
10484
- if (typeof result?.requestVersion === "number" && result.requestVersion > renderVersionRef.current) {
10532
+ if (typeof result?.requestVersion === "number" && result.requestVersion >= renderVersionRef.current) {
10485
10533
  renderVersionRef.current = result.requestVersion;
10534
+ renderVersionsRef.current = result.versions ?? null;
10486
10535
  }
10487
10536
  applyRenderStamp();
10488
10537
  });