@hyperframes/studio 0.8.164 → 0.8.166

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.html CHANGED
@@ -14,7 +14,7 @@
14
14
  document.documentElement.dataset.theme = "paper";
15
15
  }
16
16
  </script>
17
- <script type="module" crossorigin src="/assets/index-DHqUZqg6.js"></script>
17
+ <script type="module" crossorigin src="/assets/index-DQ_I6X0d.js"></script>
18
18
  <link rel="stylesheet" crossorigin href="/assets/index-DkQKsFxx.css">
19
19
  </head>
20
20
  <body>
package/dist/index.js CHANGED
@@ -31510,6 +31510,13 @@ function restoreSeekPainted(iframe) {
31510
31510
  return win?.__hfWaitForSeekCompletion?.().catch(() => {
31511
31511
  });
31512
31512
  }
31513
+ function shadowPlayerReachable(iframe) {
31514
+ try {
31515
+ return Boolean(iframe?.contentWindow?.__player);
31516
+ } catch {
31517
+ return false;
31518
+ }
31519
+ }
31513
31520
  function isDocumentHidden() {
31514
31521
  return typeof document !== "undefined" && document.visibilityState === "hidden";
31515
31522
  }
@@ -31565,6 +31572,7 @@ function useShadowPreviewReload({
31565
31572
  const failShadow = useCallback39(
31566
31573
  (gen, cause) => {
31567
31574
  if (gen !== shadowGenRef.current) return;
31575
+ const waitingFor = !shadowPlayerReachable(shadowIframeRef.current) ? "the page and its runtime to load" : pendingCommitRef.current?.gen !== gen ? "its timeline to be ready" : "its loaders to clear";
31568
31576
  shadowGenRef.current += 1;
31569
31577
  stopPendingShadow();
31570
31578
  shadowIframeRef.current = null;
@@ -31572,8 +31580,8 @@ function useShadowPreviewReload({
31572
31580
  pendingSeekRef.current = null;
31573
31581
  setPreviewSlots(planShadowDiscard);
31574
31582
  setPreviewReloading(false);
31575
- const message = `The preview did not reload (${cause}). The previous preview is still showing.`;
31576
- logReload("shadow-failed", { cause });
31583
+ const message = `The preview did not reload (${cause}). It was still waiting for ${waitingFor}. The previous preview is still showing.`;
31584
+ logReload("shadow-failed", { cause, waitingFor });
31577
31585
  console.error(`[studio] ${message}`);
31578
31586
  onReloadFailedRef.current?.(message);
31579
31587
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.8.164",
3
+ "version": "0.8.166",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -59,12 +59,12 @@
59
59
  "dockview-react": "8.3.1",
60
60
  "gsap": "^3.13.0",
61
61
  "mediabunny": "^1.45.3",
62
- "@hyperframes/core": "0.8.164",
63
- "@hyperframes/parsers": "0.8.164",
64
- "@hyperframes/player": "0.8.164",
65
- "@hyperframes/sdk": "0.8.164",
66
- "@hyperframes/studio-server": "0.8.164",
67
- "@hyperframes/shader-transitions": "0.8.164"
62
+ "@hyperframes/core": "0.8.166",
63
+ "@hyperframes/parsers": "0.8.166",
64
+ "@hyperframes/sdk": "0.8.166",
65
+ "@hyperframes/player": "0.8.166",
66
+ "@hyperframes/shader-transitions": "0.8.166",
67
+ "@hyperframes/studio-server": "0.8.166"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@tailwindcss/vite": "^4.3.3",
@@ -81,7 +81,7 @@
81
81
  "vite": "^6.4.2",
82
82
  "vitest": "^4.1.11",
83
83
  "zustand": "^5.0.0",
84
- "@hyperframes/producer": "0.8.164"
84
+ "@hyperframes/producer": "0.8.166"
85
85
  },
86
86
  "peerDependencies": {
87
87
  "react": "19",
@@ -35,6 +35,14 @@ function restoreSeekPainted(iframe: HTMLIFrameElement | null): Promise<void> | u
35
35
  return win?.__hfWaitForSeekCompletion?.().catch(() => {});
36
36
  }
37
37
 
38
+ function shadowPlayerReachable(iframe: HTMLIFrameElement | null): boolean {
39
+ try {
40
+ return Boolean((iframe?.contentWindow as IframeWindow | null | undefined)?.__player);
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
38
46
  function isDocumentHidden(): boolean {
39
47
  return typeof document !== "undefined" && document.visibilityState === "hidden";
40
48
  }
@@ -104,6 +112,11 @@ export function useShadowPreviewReload({
104
112
  const failShadow = useCallback(
105
113
  (gen: number, cause: string) => {
106
114
  if (gen !== shadowGenRef.current) return;
115
+ const waitingFor = !shadowPlayerReachable(shadowIframeRef.current)
116
+ ? "the page and its runtime to load"
117
+ : pendingCommitRef.current?.gen !== gen
118
+ ? "its timeline to be ready"
119
+ : "its loaders to clear";
107
120
  shadowGenRef.current += 1;
108
121
  stopPendingShadow();
109
122
  shadowIframeRef.current = null;
@@ -111,8 +124,8 @@ export function useShadowPreviewReload({
111
124
  pendingSeekRef.current = null;
112
125
  setPreviewSlots(planShadowDiscard);
113
126
  setPreviewReloading(false);
114
- const message = `The preview did not reload (${cause}). The previous preview is still showing.`;
115
- logReload("shadow-failed", { cause });
127
+ const message = `The preview did not reload (${cause}). It was still waiting for ${waitingFor}. The previous preview is still showing.`;
128
+ logReload("shadow-failed", { cause, waitingFor });
116
129
  console.error(`[studio] ${message}`);
117
130
  onReloadFailedRef.current?.(message);
118
131
  },
@@ -371,6 +371,9 @@ describe("shadow reload readiness and failure", () => {
371
371
  expect(getApi().iframeRef.current).toBe(live);
372
372
  expect(live.style.visibility).toBe("");
373
373
  expect(onPreviewReloadFailed).toHaveBeenCalledWith(expect.stringContaining("too long"));
374
+ expect(onPreviewReloadFailed).toHaveBeenCalledWith(
375
+ expect.stringContaining("still waiting for the page and its runtime to load"),
376
+ );
374
377
  expect(consoleError).toHaveBeenCalled();
375
378
  unmount(root);
376
379
  });
@@ -395,6 +398,48 @@ describe("shadow reload readiness and failure", () => {
395
398
  act(() => void vi.advanceTimersByTime(1));
396
399
  expect(getApi().previewSlots).toEqual([{ gen: 0, role: "live" }]);
397
400
  expect(onPreviewReloadFailed).toHaveBeenCalledWith(expect.stringContaining("too long"));
401
+ expect(onPreviewReloadFailed).toHaveBeenCalledWith(
402
+ expect.stringContaining("still waiting for the page and its runtime to load"),
403
+ );
404
+ unmount(root);
405
+ });
406
+
407
+ it("names the timeline when a shadow's runtime runs but never reports a length", () => {
408
+ vi.useFakeTimers();
409
+ vi.spyOn(console, "error").mockImplementation(() => {});
410
+ const onPreviewReloadFailed = vi.fn();
411
+ const { getApi, gen, root } = beginReload({ onPreviewReloadFailed });
412
+ const lengthless = makeFakeIframe(makeAdapterWindow({ duration: 0 }).win);
413
+ act(() => {
414
+ getApi().setShadowIframeNode(lengthless);
415
+ getApi().onShadowIframeLoad(gen);
416
+ getApi().onShadowReadyChange(gen, true);
417
+ });
418
+
419
+ act(() => void vi.advanceTimersByTime(FULL_WAIT_MS));
420
+
421
+ expect(onPreviewReloadFailed).toHaveBeenCalledWith(
422
+ expect.stringContaining("still waiting for its timeline to be ready"),
423
+ );
424
+ unmount(root);
425
+ });
426
+
427
+ it("names the loaders when a shadow with a ready timeline never clears them", () => {
428
+ vi.useFakeTimers();
429
+ vi.spyOn(console, "error").mockImplementation(() => {});
430
+ const onPreviewReloadFailed = vi.fn();
431
+ const { getApi, gen, root } = beginReload({ onPreviewReloadFailed });
432
+ const shadow = makeShadowWithSpies();
433
+ act(() => {
434
+ getApi().setShadowIframeNode(shadow.iframe);
435
+ getApi().onShadowIframeLoad(gen);
436
+ });
437
+
438
+ act(() => void vi.advanceTimersByTime(FULL_WAIT_MS));
439
+
440
+ expect(onPreviewReloadFailed).toHaveBeenCalledWith(
441
+ expect.stringContaining("still waiting for its loaders to clear"),
442
+ );
398
443
  unmount(root);
399
444
  });
400
445
 
@@ -475,6 +520,31 @@ describe("shadow reload readiness and failure", () => {
475
520
  unmount(root);
476
521
  });
477
522
 
523
+ it("still drops and reports a shadow whose window throws on access", () => {
524
+ vi.spyOn(console, "error").mockImplementation(() => {});
525
+ const onPreviewReloadFailed = vi.fn();
526
+ const { getApi, live, gen, root } = beginReload({ onPreviewReloadFailed });
527
+ const crossOrigin = makeFakeIframe(
528
+ Object.defineProperty({}, "__player", {
529
+ get: () => {
530
+ throw new DOMException("Blocked a frame", "SecurityError");
531
+ },
532
+ }),
533
+ );
534
+ act(() => getApi().setShadowIframeNode(crossOrigin));
535
+
536
+ act(() => getApi().onShadowError(gen, "Composition timeline not found after 8s"));
537
+
538
+ expect(getApi().previewSlots).toEqual([{ gen: 0, role: "live" }]);
539
+ expect(getApi().iframeRef.current).toBe(live);
540
+ expect(isPreviewChanging()).toBe(false);
541
+ expect(onPreviewReloadFailed).toHaveBeenCalledTimes(1);
542
+ expect(onPreviewReloadFailed).toHaveBeenCalledWith(
543
+ expect.stringContaining("still waiting for the page and its runtime to load"),
544
+ );
545
+ unmount(root);
546
+ });
547
+
478
548
  it("drops a shadow whose document reports an error and reports the cause", () => {
479
549
  vi.spyOn(console, "error").mockImplementation(() => {});
480
550
  const onPreviewReloadFailed = vi.fn();