@snowcone-app/ui 0.9.4 → 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 +6 -0
- package/dist/index.cjs +17 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/patterns/RealtimeProvider.tsx +29 -0
- package/src/patterns/__tests__/RealtimeProvider.cropDelivery.test.tsx +41 -0
package/package.json
CHANGED
|
@@ -62,6 +62,9 @@ const DEFAULT_ARTBOARD_SIZE = 2000;
|
|
|
62
62
|
// Enforce a conservative cap to prevent memory crashes
|
|
63
63
|
const MAX_CONCURRENT_BLOBS = 5;
|
|
64
64
|
|
|
65
|
+
/** How long a frame whose blob: URL was revoked waits for its newer frame before counting as a real failure. */
|
|
66
|
+
const FRAME_SUPERSEDE_GRACE_MS = 2000;
|
|
67
|
+
|
|
65
68
|
/** `current` in order, then any of `extra` not already in it. */
|
|
66
69
|
function mergeMockupIds(current: readonly string[], extra: readonly string[]): string[] {
|
|
67
70
|
const out = [...current];
|
|
@@ -943,6 +946,32 @@ export function RealtimeProvider({
|
|
|
943
946
|
) {
|
|
944
947
|
return;
|
|
945
948
|
}
|
|
949
|
+
// The FRAME (not the background) failed to load from a revoked blob:
|
|
950
|
+
// URL. The SDK revokes a mockup's frame only when that mockup's next
|
|
951
|
+
// frame arrives, so the newer frame exists even if React state hasn't
|
|
952
|
+
// delivered it here yet (prod 2026-11-11, phone: this race switched
|
|
953
|
+
// cropped delivery off for the whole session). Wait for it; if none
|
|
954
|
+
// shows up, it really failed.
|
|
955
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
956
|
+
if (
|
|
957
|
+
result.imageUrl.startsWith("blob:") &&
|
|
958
|
+
message.startsWith("E_OVERLAY_FRAME_LOAD") &&
|
|
959
|
+
message.includes(result.imageUrl.slice(0, 160))
|
|
960
|
+
) {
|
|
961
|
+
const failedUrl = result.imageUrl;
|
|
962
|
+
setTimeout(() => {
|
|
963
|
+
if (gen !== sceneGenRef.current) return;
|
|
964
|
+
if (compositeQueueRef.current.has(result.mockupId)) return;
|
|
965
|
+
if (processedFrameRef.current.get(result.mockupId) !== failedUrl) return;
|
|
966
|
+
console.error(
|
|
967
|
+
`[RealtimeProvider] E_OVERLAY_COMPOSITE_FAILED: frame for ${result.mockupId} failed to load and no newer frame arrived within ${FRAME_SUPERSEDE_GRACE_MS} ms — full-frame delivery for the rest of this session.`,
|
|
968
|
+
err
|
|
969
|
+
);
|
|
970
|
+
cropCapableRef.current = false;
|
|
971
|
+
rerenderFullFrame(result.mockupId);
|
|
972
|
+
}, FRAME_SUPERSEDE_GRACE_MS);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
946
975
|
console.error(
|
|
947
976
|
"[RealtimeProvider] E_OVERLAY_COMPOSITE_FAILED: cropped-frame composite failed — full-frame delivery for the rest of this session.",
|
|
948
977
|
err
|
|
@@ -412,5 +412,46 @@ describe("RealtimeProvider cropped delivery", () => {
|
|
|
412
412
|
expect(h.rerender).not.toHaveBeenCalled();
|
|
413
413
|
errSpy.mockRestore();
|
|
414
414
|
});
|
|
415
|
+
|
|
416
|
+
it("a frame whose blob: URL was revoked waits for its newer frame instead of switching the session to full frames (prod, phone, 2026-11-11)", async () => {
|
|
417
|
+
const { rerender } = render(<Harness version={1} />);
|
|
418
|
+
await waitFor(() => expect(h.sendConfig).toHaveBeenCalled());
|
|
419
|
+
await seedAndFlip(rerender);
|
|
420
|
+
h.sendConfig.mockClear();
|
|
421
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
422
|
+
|
|
423
|
+
// v3's frame was revoked by the SDK (its v4 already arrived there) before React state delivered v4 here.
|
|
424
|
+
h.compositeCroppedFrame.mockImplementationOnce(() =>
|
|
425
|
+
Promise.reject(new Error("E_OVERLAY_FRAME_LOAD: image failed to load: blob:v3-m1"))
|
|
426
|
+
);
|
|
427
|
+
h.state.results = [croppedResult("m1", "blob:v3-m1"), fullResult("m2", "https://r/m2-full")];
|
|
428
|
+
await act(async () => rerender(<Harness version={4} />));
|
|
429
|
+
// v4 lands within the grace window and composites.
|
|
430
|
+
h.state.results = [croppedResult("m1", "blob:v4-m1"), fullResult("m2", "https://r/m2-full")];
|
|
431
|
+
await act(async () => rerender(<Harness version={5} />));
|
|
432
|
+
await act(async () => { await new Promise((r) => setTimeout(r, 2300)); });
|
|
433
|
+
expect(h.compositeCroppedFrame).toHaveBeenLastCalledWith(expect.objectContaining({ frameUrl: "blob:v4-m1" }));
|
|
434
|
+
expect(h.sendConfig).not.toHaveBeenCalled(); // cropped delivery stays on, nothing re-rendered
|
|
435
|
+
errSpy.mockRestore();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("a revoked frame with no newer frame within the grace window is a real failure: full-frame re-render", async () => {
|
|
439
|
+
const { rerender } = render(<Harness version={1} />);
|
|
440
|
+
await waitFor(() => expect(h.sendConfig).toHaveBeenCalled());
|
|
441
|
+
await seedAndFlip(rerender);
|
|
442
|
+
h.sendConfig.mockClear();
|
|
443
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
444
|
+
h.compositeCroppedFrame.mockImplementationOnce(() =>
|
|
445
|
+
Promise.reject(new Error("E_OVERLAY_FRAME_LOAD: image failed to load: blob:v3-m1"))
|
|
446
|
+
);
|
|
447
|
+
h.state.results = [croppedResult("m1", "blob:v3-m1"), fullResult("m2", "https://r/m2-full")];
|
|
448
|
+
await act(async () => rerender(<Harness version={4} />));
|
|
449
|
+
await act(async () => { await new Promise((r) => setTimeout(r, 200)); });
|
|
450
|
+
expect(h.sendConfig).not.toHaveBeenCalled(); // still inside the grace window
|
|
451
|
+
await act(async () => { await new Promise((r) => setTimeout(r, 2100)); });
|
|
452
|
+
await waitFor(() => expect(sentConfigs().at(-1)?.mockupIds).toEqual(["m1", "m2"]));
|
|
453
|
+
expect(sentConfigs().every((c) => c.overlayCrop === undefined)).toBe(true);
|
|
454
|
+
errSpy.mockRestore();
|
|
455
|
+
});
|
|
415
456
|
});
|
|
416
457
|
|