@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 +15 -0
- package/dist/index.cjs +90 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +90 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/composed/HeroProductImage.tsx +14 -3
- package/src/patterns/RealtimeProvider.tsx +127 -37
- package/src/patterns/__tests__/RealtimeProvider.cropDelivery.test.tsx +97 -2
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
|
@@ -5703,6 +5703,7 @@ async function compositeCroppedFrame(args) {
|
|
|
5703
5703
|
import { jsx as jsx52 } from "react/jsx-runtime";
|
|
5704
5704
|
var DEFAULT_ARTBOARD_SIZE = 2e3;
|
|
5705
5705
|
var MAX_CONCURRENT_BLOBS = 5;
|
|
5706
|
+
var FRAME_SUPERSEDE_GRACE_MS = 2e3;
|
|
5706
5707
|
function mergeMockupIds(current, extra) {
|
|
5707
5708
|
const out = [...current];
|
|
5708
5709
|
for (const id of extra) if (!out.includes(id)) out.push(id);
|
|
@@ -5786,6 +5787,12 @@ function RealtimeProvider({
|
|
|
5786
5787
|
},
|
|
5787
5788
|
onError: (error, detail) => {
|
|
5788
5789
|
console.error(`[RealtimeProvider] Render error [${detail.code}]:`, error);
|
|
5790
|
+
if (detail.code.startsWith("E_RERENDER_") && pendingRerenderRef.current.length > 0) {
|
|
5791
|
+
const ids = pendingRerenderRef.current;
|
|
5792
|
+
pendingRerenderRef.current = [];
|
|
5793
|
+
forceRerenderByConfigRef.current?.(ids);
|
|
5794
|
+
return;
|
|
5795
|
+
}
|
|
5789
5796
|
if (detail.mockupId) {
|
|
5790
5797
|
const id = detail.mockupId;
|
|
5791
5798
|
setUnavailableMockups((prev) => prev.get(id)?.code === detail.code ? prev : new Map(prev).set(id, detail));
|
|
@@ -5811,7 +5818,9 @@ function RealtimeProvider({
|
|
|
5811
5818
|
disconnect,
|
|
5812
5819
|
flushPendingBlobs,
|
|
5813
5820
|
hasPendingBlobs,
|
|
5814
|
-
getLastSentVersion
|
|
5821
|
+
getLastSentVersion,
|
|
5822
|
+
getLatestSentVersions,
|
|
5823
|
+
rerender: rerenderRaw
|
|
5815
5824
|
} = realtimeMockup;
|
|
5816
5825
|
const mockupResultsRef = useRef12([]);
|
|
5817
5826
|
const mockupSubscribersRef = useRef12(
|
|
@@ -5849,6 +5858,9 @@ function RealtimeProvider({
|
|
|
5849
5858
|
const compositeBusyRef = useRef12(/* @__PURE__ */ new Set());
|
|
5850
5859
|
const compositeCanvasRef = useRef12(null);
|
|
5851
5860
|
const requestedMockupIdsRef = useRef12([]);
|
|
5861
|
+
const serverStampsVersionsRef = useRef12(false);
|
|
5862
|
+
const pendingRerenderRef = useRef12([]);
|
|
5863
|
+
const forceRerenderByConfigRef = useRef12(null);
|
|
5852
5864
|
const fullFrameRerenderIdsRef = useRef12(/* @__PURE__ */ new Set());
|
|
5853
5865
|
const fullFrameRerenderTimerRef = useRef12(null);
|
|
5854
5866
|
useEffect17(
|
|
@@ -5901,6 +5913,36 @@ function RealtimeProvider({
|
|
|
5901
5913
|
if (!croppedActiveRef.current) return;
|
|
5902
5914
|
sendCropConfig(false);
|
|
5903
5915
|
}, [sendCropConfig]);
|
|
5916
|
+
const sendFullFrameConfig = useCallback10(
|
|
5917
|
+
(mockupIds) => {
|
|
5918
|
+
const base = lastConfigBaseRef.current;
|
|
5919
|
+
if (!base) return;
|
|
5920
|
+
sendConfigRaw({
|
|
5921
|
+
...base,
|
|
5922
|
+
mockupIds,
|
|
5923
|
+
placementSettings: { ...placementSettingsOverridesRef.current }
|
|
5924
|
+
});
|
|
5925
|
+
},
|
|
5926
|
+
[sendConfigRaw]
|
|
5927
|
+
);
|
|
5928
|
+
const forceRerenderByConfig = useCallback10(
|
|
5929
|
+
(failed) => {
|
|
5930
|
+
const inForce = requestedMockupIdsRef.current;
|
|
5931
|
+
const fp = (list) => [...list].sort().join(",");
|
|
5932
|
+
const step = fp(failed) !== fp(inForce) ? failed : inForce.slice(0, -1);
|
|
5933
|
+
if (step.length === 0) {
|
|
5934
|
+
console.error(
|
|
5935
|
+
`[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.`
|
|
5936
|
+
);
|
|
5937
|
+
sendFullFrameConfig(inForce);
|
|
5938
|
+
return;
|
|
5939
|
+
}
|
|
5940
|
+
sendFullFrameConfig(step);
|
|
5941
|
+
sendFullFrameConfig(inForce);
|
|
5942
|
+
},
|
|
5943
|
+
[sendFullFrameConfig]
|
|
5944
|
+
);
|
|
5945
|
+
forceRerenderByConfigRef.current = forceRerenderByConfig;
|
|
5904
5946
|
const rerenderFullFrame = useCallback10(
|
|
5905
5947
|
(mockupId) => {
|
|
5906
5948
|
fullFrameRerenderIdsRef.current.add(mockupId);
|
|
@@ -5909,35 +5951,26 @@ function RealtimeProvider({
|
|
|
5909
5951
|
fullFrameRerenderTimerRef.current = null;
|
|
5910
5952
|
const failed = [...fullFrameRerenderIdsRef.current];
|
|
5911
5953
|
fullFrameRerenderIdsRef.current.clear();
|
|
5912
|
-
|
|
5913
|
-
if (!base || failed.length === 0) return;
|
|
5954
|
+
if (!lastConfigBaseRef.current || failed.length === 0) return;
|
|
5914
5955
|
const previous = requestedMockupIdsRef.current;
|
|
5915
5956
|
const ids = mergeMockupIds(previous, failed);
|
|
5916
5957
|
croppedActiveRef.current = false;
|
|
5917
5958
|
requestedMockupIdsRef.current = ids;
|
|
5918
|
-
const
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
placementSettings: { ...placementSettingsOverridesRef.current }
|
|
5922
|
-
});
|
|
5923
|
-
const fingerprint = (list) => [...list].sort().join(",");
|
|
5924
|
-
if (fingerprint(ids) !== fingerprint(previous)) {
|
|
5925
|
-
send(ids);
|
|
5959
|
+
const fp = (list) => [...list].sort().join(",");
|
|
5960
|
+
if (fp(ids) !== fp(previous)) {
|
|
5961
|
+
sendFullFrameConfig(ids);
|
|
5926
5962
|
return;
|
|
5927
5963
|
}
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
send(ids);
|
|
5934
|
-
return;
|
|
5964
|
+
if (serverStampsVersionsRef.current) {
|
|
5965
|
+
sendFullFrameConfig(ids);
|
|
5966
|
+
pendingRerenderRef.current = failed;
|
|
5967
|
+
if (rerenderRaw(failed)) return;
|
|
5968
|
+
pendingRerenderRef.current = [];
|
|
5935
5969
|
}
|
|
5936
|
-
|
|
5937
|
-
send(ids);
|
|
5970
|
+
forceRerenderByConfig(failed);
|
|
5938
5971
|
}, 0);
|
|
5939
5972
|
},
|
|
5940
|
-
[
|
|
5973
|
+
[sendFullFrameConfig, forceRerenderByConfig, rerenderRaw]
|
|
5941
5974
|
);
|
|
5942
5975
|
const maybeFlipCrop = useCallback10(() => {
|
|
5943
5976
|
if (!croppedDeliveryEnabled || !cropCapableRef.current) return;
|
|
@@ -5972,6 +6005,10 @@ function RealtimeProvider({
|
|
|
5972
6005
|
else merged.push(result);
|
|
5973
6006
|
});
|
|
5974
6007
|
mockupResultsRef.current = merged;
|
|
6008
|
+
if (pendingRerenderRef.current.length > 0) {
|
|
6009
|
+
const delivered = new Set(results.map((r) => r.mockupId));
|
|
6010
|
+
pendingRerenderRef.current = pendingRerenderRef.current.filter((id) => !delivered.has(id));
|
|
6011
|
+
}
|
|
5975
6012
|
const clearedPlacements = /* @__PURE__ */ new Set();
|
|
5976
6013
|
results.forEach((result) => {
|
|
5977
6014
|
const wasPending = pendingMockupIdsRef.current.has(result.mockupId);
|
|
@@ -6059,6 +6096,22 @@ function RealtimeProvider({
|
|
|
6059
6096
|
if (gen !== sceneGenRef.current || compositeQueueRef.current.has(result.mockupId) || processedFrameRef.current.get(result.mockupId) !== result.imageUrl) {
|
|
6060
6097
|
return;
|
|
6061
6098
|
}
|
|
6099
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6100
|
+
if (result.imageUrl.startsWith("blob:") && message.startsWith("E_OVERLAY_FRAME_LOAD") && message.includes(result.imageUrl.slice(0, 160))) {
|
|
6101
|
+
const failedUrl = result.imageUrl;
|
|
6102
|
+
setTimeout(() => {
|
|
6103
|
+
if (gen !== sceneGenRef.current) return;
|
|
6104
|
+
if (compositeQueueRef.current.has(result.mockupId)) return;
|
|
6105
|
+
if (processedFrameRef.current.get(result.mockupId) !== failedUrl) return;
|
|
6106
|
+
console.error(
|
|
6107
|
+
`[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.`,
|
|
6108
|
+
err
|
|
6109
|
+
);
|
|
6110
|
+
cropCapableRef.current = false;
|
|
6111
|
+
rerenderFullFrame(result.mockupId);
|
|
6112
|
+
}, FRAME_SUPERSEDE_GRACE_MS);
|
|
6113
|
+
return;
|
|
6114
|
+
}
|
|
6062
6115
|
console.error(
|
|
6063
6116
|
"[RealtimeProvider] E_OVERLAY_COMPOSITE_FAILED: cropped-frame composite failed \u2014 full-frame delivery for the rest of this session.",
|
|
6064
6117
|
err
|
|
@@ -6093,6 +6146,9 @@ function RealtimeProvider({
|
|
|
6093
6146
|
}
|
|
6094
6147
|
console.log(`[RTP] rawMockupResults changed: ${rawMockupResults.length} result(s)`, rawMockupResults.map((r) => `${r.mockupId}${r.imageUrl ? " \u2713url" : " \u2717NO-URL"}${r.cropRect ? " [crop]" : ""}`));
|
|
6095
6148
|
const displayable = [];
|
|
6149
|
+
if (!serverStampsVersionsRef.current && rawMockupResults.some((r) => r.versions && typeof r.versions === "object")) {
|
|
6150
|
+
serverStampsVersionsRef.current = true;
|
|
6151
|
+
}
|
|
6096
6152
|
rawMockupResults.forEach((result) => {
|
|
6097
6153
|
if (!result.imageUrl) {
|
|
6098
6154
|
console.log(`[RTP] Result for ${result.mockupId} has NO imageUrl \u2014 not caching, pending NOT cleared`);
|
|
@@ -6575,6 +6631,9 @@ function RealtimeProvider({
|
|
|
6575
6631
|
const getLastSentVersionRef = useRef12(getLastSentVersion);
|
|
6576
6632
|
getLastSentVersionRef.current = getLastSentVersion;
|
|
6577
6633
|
const getCanvasVersion = useCallback10(() => getLastSentVersionRef.current?.() ?? 0, []);
|
|
6634
|
+
const getLatestSentVersionsRef = useRef12(getLatestSentVersions);
|
|
6635
|
+
getLatestSentVersionsRef.current = getLatestSentVersions;
|
|
6636
|
+
const getLatestSentVersionsStable = useCallback10(() => getLatestSentVersionsRef.current?.() ?? {}, []);
|
|
6578
6637
|
const stableSubscribePipelineSettled = useCallback10(
|
|
6579
6638
|
(callback) => {
|
|
6580
6639
|
return subscribePipelineSettledRef.current(callback);
|
|
@@ -6624,6 +6683,7 @@ function RealtimeProvider({
|
|
|
6624
6683
|
getMockupUrl: getMockupUrl3,
|
|
6625
6684
|
getMockupUrls,
|
|
6626
6685
|
getCanvasVersion,
|
|
6686
|
+
getLatestSentVersions: getLatestSentVersionsStable,
|
|
6627
6687
|
isMockupLoading,
|
|
6628
6688
|
registerBlobUrl,
|
|
6629
6689
|
// WebRTC streaming
|
|
@@ -6656,6 +6716,7 @@ function RealtimeProvider({
|
|
|
6656
6716
|
getMockupUrl3,
|
|
6657
6717
|
getMockupUrls,
|
|
6658
6718
|
getCanvasVersion,
|
|
6719
|
+
getLatestSentVersionsStable,
|
|
6659
6720
|
isMockupLoading,
|
|
6660
6721
|
registerBlobUrl
|
|
6661
6722
|
]
|
|
@@ -10451,6 +10512,8 @@ var HeroProductImage = memo3(function HeroProductImage2({
|
|
|
10451
10512
|
};
|
|
10452
10513
|
}, [mode, realtimeContext?.subscribePendingBlob, placement]);
|
|
10453
10514
|
const renderVersionRef = useRef23(0);
|
|
10515
|
+
const renderVersionsRef = useRef23(null);
|
|
10516
|
+
const getLatestSentVersions = realtimeContext?.getLatestSentVersions;
|
|
10454
10517
|
const refusalCodeRef = useRef23(null);
|
|
10455
10518
|
refusalCodeRef.current = unavailable?.code ?? null;
|
|
10456
10519
|
const sessionErrorCodeRef = useRef23(null);
|
|
@@ -10467,11 +10530,13 @@ var HeroProductImage = memo3(function HeroProductImage2({
|
|
|
10467
10530
|
return;
|
|
10468
10531
|
}
|
|
10469
10532
|
const rendered = renderVersionRef.current;
|
|
10470
|
-
const
|
|
10533
|
+
const versions = renderVersionsRef.current;
|
|
10534
|
+
const sent = versions ? getLatestSentVersions?.() : void 0;
|
|
10535
|
+
const behind = versions && sent ? Object.entries(sent).some(([placement2, v]) => (versions[placement2] ?? -1) < v) : rendered < canvas;
|
|
10471
10536
|
node.dataset.canvasVersion = String(canvas);
|
|
10472
10537
|
node.dataset.renderVersion = String(rendered);
|
|
10473
10538
|
node.dataset.renderState = refusalCodeRef.current || behind && sessionErrorCodeRef.current ? "refused" : behind ? "stale" : "current";
|
|
10474
|
-
}, [getCanvasVersion]);
|
|
10539
|
+
}, [getCanvasVersion, getLatestSentVersions]);
|
|
10475
10540
|
useEffect32(() => {
|
|
10476
10541
|
applyRenderStamp();
|
|
10477
10542
|
});
|
|
@@ -10481,8 +10546,9 @@ var HeroProductImage = memo3(function HeroProductImage2({
|
|
|
10481
10546
|
if (!actualMockupId || !subscribeSent || !subscribeResult) return;
|
|
10482
10547
|
const offSent = subscribeSent(() => applyRenderStamp());
|
|
10483
10548
|
const offResult = subscribeResult(actualMockupId, (result) => {
|
|
10484
|
-
if (typeof result?.requestVersion === "number" && result.requestVersion
|
|
10549
|
+
if (typeof result?.requestVersion === "number" && result.requestVersion >= renderVersionRef.current) {
|
|
10485
10550
|
renderVersionRef.current = result.requestVersion;
|
|
10551
|
+
renderVersionsRef.current = result.versions ?? null;
|
|
10486
10552
|
}
|
|
10487
10553
|
applyRenderStamp();
|
|
10488
10554
|
});
|