@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 +9 -0
- package/dist/index.cjs +73 -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 +73 -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 +98 -37
- package/src/patterns/__tests__/RealtimeProvider.cropDelivery.test.tsx +56 -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
|
@@ -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
|
-
|
|
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
|
|
5919
|
-
|
|
5920
|
-
|
|
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
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
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
|
-
|
|
5937
|
-
send(ids);
|
|
5969
|
+
forceRerenderByConfig(failed);
|
|
5938
5970
|
}, 0);
|
|
5939
5971
|
},
|
|
5940
|
-
[
|
|
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
|
|
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
|
|
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
|
});
|