@snowcone-app/ui 0.4.10 → 0.4.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowcone-app/ui",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "React components for merchandise visualization and customization",
5
5
  "keywords": [
6
6
  "react",
@@ -773,8 +773,19 @@ export function RealtimeProvider({
773
773
  if (firstSight && sessionLive) {
774
774
  processedFrameRef.current.set(result.mockupId, result.imageUrl);
775
775
  // Seed/refresh the frozen background — the freshest full frame is
776
- // always the best composite base.
777
- backgroundsRef.current.set(result.mockupId, { url: result.imageUrl });
776
+ // always the best composite base. Decode it NOW, not at composite
777
+ // time: with ws binary delivery `imageUrl` is the SDK's live blob:
778
+ // URL for this mockup, and the SDK revokes it the moment the next
779
+ // frame for the same mockup (the first cropped one) arrives. A
780
+ // lazily-loaded background would then fail with
781
+ // E_OVERLAY_FRAME_LOAD and kill cropped delivery for the session
782
+ // (observed: every edit cost 3 extra full renders). A decoded
783
+ // HTMLImageElement keeps its pixels after the URL is revoked.
784
+ const imgPromise = loadImageElement(result.imageUrl);
785
+ imgPromise.catch(() => {
786
+ /* surfaced by runComposite when it awaits the promise */
787
+ });
788
+ backgroundsRef.current.set(result.mockupId, { url: result.imageUrl, imgPromise });
778
789
  }
779
790
  // Full frames re-deliver on every run (matches pre-crop behavior —
780
791
  // consumers dedupe by URL; delivery also clears pending state).
@@ -158,6 +158,27 @@ describe("RealtimeProvider cropped delivery", () => {
158
158
  await seedAndFlip(rerender);
159
159
  });
160
160
 
161
+ it("decodes the frozen background at seed time, before the SDK can revoke its blob: URL", async () => {
162
+ // With ws binary delivery the full frame's imageUrl is the SDK's live
163
+ // blob: URL, revoked when the next frame for that mockup arrives. The
164
+ // background must be decoded while the URL is still alive.
165
+ const { rerender } = render(<Harness version={1} />);
166
+ await waitFor(() => expect(h.sendConfig).toHaveBeenCalled());
167
+ h.state.results = [fullResult("m1", "blob:live-m1"), fullResult("m2", "blob:live-m2")];
168
+ await act(async () => rerender(<Harness version={3} />));
169
+ await waitFor(() => {
170
+ expect(h.loadImageElement).toHaveBeenCalledWith("blob:live-m1");
171
+ expect(h.loadImageElement).toHaveBeenCalledWith("blob:live-m2");
172
+ });
173
+ // ...and not again at composite time — the decoded element is reused.
174
+ const loadsAfterSeed = h.loadImageElement.mock.calls.length;
175
+ await waitFor(() => expect(sentConfigs().some((c) => c.overlayCrop === "auto")).toBe(true));
176
+ h.state.results = [croppedResult("m1", "blob:live-m1-crop"), fullResult("m2", "blob:live-m2")];
177
+ await act(async () => rerender(<Harness version={4} />));
178
+ await waitFor(() => expect(h.compositeCroppedFrame).toHaveBeenCalled());
179
+ expect(h.loadImageElement.mock.calls.length).toBe(loadsAfterSeed);
180
+ });
181
+
161
182
  it("composites cropped frames — consumers only ever see full-frame URLs", async () => {
162
183
  const { rerender } = render(<Harness version={1} />);
163
184
  await waitFor(() => expect(h.sendConfig).toHaveBeenCalled());