@hyperframes/studio 0.8.180 → 0.8.183
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/assets/{hyperframes-player-1wdLVP-x.js → hyperframes-player-hz_VqxBZ.js} +7 -2
- package/dist/assets/{index-CaWPPTQ1.js → index-CV_ojgp5.js} +1 -1
- package/dist/assets/{index-mdtW0Ug-.js → index-S1ceEagc.js} +71 -71
- package/dist/index.html +1 -1
- package/dist/index.js +11 -0
- package/package.json +8 -8
- package/src/hooks/gsapValueAtPlayhead.browser.test.ts +2 -2
- package/src/player/hooks/useShadowPreviewReload.ts +12 -1
- package/src/player/hooks/useTimelinePlayer.shadowReload.test.ts +73 -0
- package/src/utils/gsapUndoRestore.browser.test.ts +2 -2
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-
|
|
17
|
+
<script type="module" crossorigin src="/assets/index-S1ceEagc.js"></script>
|
|
18
18
|
<link rel="stylesheet" crossorigin href="/assets/index-CESvLzz0.css">
|
|
19
19
|
</head>
|
|
20
20
|
<body>
|
package/dist/index.js
CHANGED
|
@@ -31726,8 +31726,19 @@ function useShadowPreviewReload({
|
|
|
31726
31726
|
(gen) => {
|
|
31727
31727
|
clearTimeout(readyTimerRef.current);
|
|
31728
31728
|
if (isDocumentHidden()) return;
|
|
31729
|
+
let drew = false;
|
|
31730
|
+
let starved = false;
|
|
31731
|
+
requestAnimationFrame(() => {
|
|
31732
|
+
if (!starved) drew = true;
|
|
31733
|
+
else if (budgetGenRef.current === gen) armReadyTimerRef.current(gen);
|
|
31734
|
+
});
|
|
31729
31735
|
readyTimerRef.current = setTimeout(() => {
|
|
31730
31736
|
if (gen !== shadowGenRef.current) return;
|
|
31737
|
+
if (!drew) {
|
|
31738
|
+
starved = true;
|
|
31739
|
+
logReload("shadow-starved", { gen });
|
|
31740
|
+
return;
|
|
31741
|
+
}
|
|
31731
31742
|
budgetsSpentRef.current += 1;
|
|
31732
31743
|
if (budgetsSpentRef.current < SHADOW_READY_BUDGETS) {
|
|
31733
31744
|
logReload("shadow-slow", { budgetsSpent: budgetsSpentRef.current });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.183",
|
|
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/
|
|
63
|
-
"@hyperframes/
|
|
64
|
-
"@hyperframes/
|
|
65
|
-
"@hyperframes/shader-transitions": "0.8.
|
|
66
|
-
"@hyperframes/
|
|
67
|
-
"@hyperframes/
|
|
62
|
+
"@hyperframes/parsers": "0.8.183",
|
|
63
|
+
"@hyperframes/player": "0.8.183",
|
|
64
|
+
"@hyperframes/core": "0.8.183",
|
|
65
|
+
"@hyperframes/shader-transitions": "0.8.183",
|
|
66
|
+
"@hyperframes/studio-server": "0.8.183",
|
|
67
|
+
"@hyperframes/sdk": "0.8.183"
|
|
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.
|
|
84
|
+
"@hyperframes/producer": "0.8.183"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"react": "19",
|
|
@@ -4,7 +4,7 @@ import { gsap } from "gsap";
|
|
|
4
4
|
import { parseGsapScriptAcorn } from "@hyperframes/parsers/gsap-parser-acorn";
|
|
5
5
|
import { replaceTweenWithKeyframesInScript } from "@hyperframes/parsers/gsap-writer-acorn";
|
|
6
6
|
import { expect, it, vi } from "vitest";
|
|
7
|
-
import { launchTestChrome, showWithRuntime } from "../../tests/chromeTestUtils";
|
|
7
|
+
import { closeTestChrome, launchTestChrome, showWithRuntime } from "../../tests/chromeTestUtils";
|
|
8
8
|
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
9
9
|
import { usePlayerStore } from "../player/store/playerStore";
|
|
10
10
|
import { planValueEdit } from "./gsapValueAtPlayhead";
|
|
@@ -59,6 +59,6 @@ it("a drag on a fromTo's start shows the dropped x there once the runtime seeks"
|
|
|
59
59
|
});
|
|
60
60
|
expect(shown).toBeCloseTo(90, 1);
|
|
61
61
|
} finally {
|
|
62
|
-
await browser
|
|
62
|
+
await closeTestChrome(browser);
|
|
63
63
|
}
|
|
64
64
|
});
|
|
@@ -25,7 +25,7 @@ import { usePlayerStore } from "../store/playerStore";
|
|
|
25
25
|
|
|
26
26
|
// One wait budget for a shadow: the player's 8s asset cap plus its 0.42s loader fade
|
|
27
27
|
// leaves about 6.5s for the document load and runtime boot. Nothing shorter may fail the swap.
|
|
28
|
-
//
|
|
28
|
+
// Spent only while frames are drawn (readiness needs them); a hidden tab or a covered Linux window draws none.
|
|
29
29
|
export const SHADOW_READY_TIMEOUT_MS = 15_000;
|
|
30
30
|
// A busy machine can need more than one budget; the shadow keeps loading for this many before it is dropped.
|
|
31
31
|
export const SHADOW_READY_BUDGETS = 3;
|
|
@@ -276,8 +276,19 @@ export function useShadowPreviewReload({
|
|
|
276
276
|
(gen: number) => {
|
|
277
277
|
clearTimeout(readyTimerRef.current);
|
|
278
278
|
if (isDocumentHidden()) return;
|
|
279
|
+
let drew = false;
|
|
280
|
+
let starved = false;
|
|
281
|
+
requestAnimationFrame(() => {
|
|
282
|
+
if (!starved) drew = true;
|
|
283
|
+
else if (budgetGenRef.current === gen) armReadyTimerRef.current(gen);
|
|
284
|
+
});
|
|
279
285
|
readyTimerRef.current = setTimeout(() => {
|
|
280
286
|
if (gen !== shadowGenRef.current) return;
|
|
287
|
+
if (!drew) {
|
|
288
|
+
starved = true;
|
|
289
|
+
logReload("shadow-starved", { gen });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
281
292
|
budgetsSpentRef.current += 1;
|
|
282
293
|
if (budgetsSpentRef.current < SHADOW_READY_BUDGETS) {
|
|
283
294
|
logReload("shadow-slow", { budgetsSpent: budgetsSpentRef.current });
|
|
@@ -545,6 +545,60 @@ describe("shadow reload readiness and failure", () => {
|
|
|
545
545
|
unmount(root);
|
|
546
546
|
});
|
|
547
547
|
|
|
548
|
+
// A window behind others on some Linux desktops draws no frames yet reports visible; readiness needs frames.
|
|
549
|
+
it("does not spend the budget while the window draws no frames, and promotes once they return", () => {
|
|
550
|
+
vi.useFakeTimers();
|
|
551
|
+
const frames = holdFrames();
|
|
552
|
+
const onPreviewReloadFailed = vi.fn();
|
|
553
|
+
const { getApi, gen, root } = beginReload({ onPreviewReloadFailed });
|
|
554
|
+
const shadow = makeShadowWithSpies();
|
|
555
|
+
act(() => {
|
|
556
|
+
getApi().setShadowIframeNode(shadow.iframe);
|
|
557
|
+
getApi().onShadowIframeLoad(gen);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
act(() => void vi.advanceTimersByTime(FULL_WAIT_MS * 2));
|
|
561
|
+
expect(onPreviewReloadFailed).not.toHaveBeenCalled();
|
|
562
|
+
expect(getApi().previewSlots).toHaveLength(2);
|
|
563
|
+
|
|
564
|
+
act(() => frames.resume());
|
|
565
|
+
act(() => getApi().onShadowReadyChange(gen, true));
|
|
566
|
+
expect(getApi().iframeRef.current).toBe(shadow.iframe);
|
|
567
|
+
unmount(root);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it("gives a shadow its full budget once frames return, then fails one that stays silent", () => {
|
|
571
|
+
vi.useFakeTimers();
|
|
572
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
573
|
+
const frames = holdFrames();
|
|
574
|
+
const onPreviewReloadFailed = vi.fn();
|
|
575
|
+
const { getApi, live, root } = beginReload({ onPreviewReloadFailed });
|
|
576
|
+
|
|
577
|
+
act(() => void vi.advanceTimersByTime(FULL_WAIT_MS));
|
|
578
|
+
act(() => frames.resume());
|
|
579
|
+
act(() => void vi.advanceTimersByTime(FULL_WAIT_MS - 1));
|
|
580
|
+
expect(onPreviewReloadFailed).not.toHaveBeenCalled();
|
|
581
|
+
act(() => void vi.advanceTimersByTime(1));
|
|
582
|
+
expect(onPreviewReloadFailed).toHaveBeenCalledWith(expect.stringContaining("too long"));
|
|
583
|
+
expect(getApi().iframeRef.current).toBe(live);
|
|
584
|
+
unmount(root);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it("keeps a reload's own budget when the frames a replaced one waited on return", () => {
|
|
588
|
+
vi.useFakeTimers();
|
|
589
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
590
|
+
const frames = holdFrames();
|
|
591
|
+
const onPreviewReloadFailed = vi.fn();
|
|
592
|
+
const { getApi, root } = beginReload({ onPreviewReloadFailed });
|
|
593
|
+
act(() => void vi.advanceTimersByTime(SHADOW_READY_TIMEOUT_MS + 1000));
|
|
594
|
+
|
|
595
|
+
act(() => getApi().refreshPlayer());
|
|
596
|
+
act(() => frames.resume());
|
|
597
|
+
act(() => void vi.advanceTimersByTime(FULL_WAIT_MS));
|
|
598
|
+
expect(onPreviewReloadFailed).toHaveBeenCalledWith(expect.stringContaining("too long"));
|
|
599
|
+
unmount(root);
|
|
600
|
+
});
|
|
601
|
+
|
|
548
602
|
it("still drops and reports a shadow whose window throws on access", () => {
|
|
549
603
|
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
550
604
|
const onPreviewReloadFailed = vi.fn();
|
|
@@ -806,6 +860,25 @@ function unmount(root: ReturnType<typeof createRoot>) {
|
|
|
806
860
|
});
|
|
807
861
|
}
|
|
808
862
|
|
|
863
|
+
/** A window that draws no frames although it reports visible: frames are held until `resume`, then one per 16 ms. */
|
|
864
|
+
function holdFrames() {
|
|
865
|
+
let held: FrameRequestCallback[] = [];
|
|
866
|
+
let flowing = false;
|
|
867
|
+
vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => {
|
|
868
|
+
if (flowing) return Number(setTimeout(() => callback(performance.now()), 16));
|
|
869
|
+
held.push(callback);
|
|
870
|
+
return held.length;
|
|
871
|
+
});
|
|
872
|
+
return {
|
|
873
|
+
resume: () => {
|
|
874
|
+
flowing = true;
|
|
875
|
+
const waiting = held;
|
|
876
|
+
held = [];
|
|
877
|
+
for (const callback of waiting) callback(performance.now());
|
|
878
|
+
},
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
|
|
809
882
|
function stubVisibility(initial: DocumentVisibilityState) {
|
|
810
883
|
let state = initial;
|
|
811
884
|
vi.spyOn(document, "visibilityState", "get").mockImplementation(() => state);
|
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import type { Browser } from "puppeteer-core";
|
|
7
7
|
import { build, type Plugin } from "esbuild";
|
|
8
8
|
import { afterAll, beforeAll, expect, it, vi } from "vitest";
|
|
9
|
-
import { launchTestChrome, showWithRuntime } from "../../tests/chromeTestUtils";
|
|
9
|
+
import { closeTestChrome, launchTestChrome, showWithRuntime } from "../../tests/chromeTestUtils";
|
|
10
10
|
import { writeFixture } from "../../tests/e2e/edit-accuracy/grid.mjs";
|
|
11
11
|
|
|
12
12
|
const CROP = "inset(0px 40px 0px 0px)";
|
|
@@ -47,7 +47,7 @@ beforeAll(async () => {
|
|
|
47
47
|
softReloadBundle = await bundle("./gsapSoftReload.ts", "hfSoftReload");
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
afterAll(() => browser
|
|
50
|
+
afterAll(() => closeTestChrome(browser));
|
|
51
51
|
|
|
52
52
|
type Spec = { gsap: string; placement: string; rotation: number; time: number };
|
|
53
53
|
|