@hyperframes/studio 0.8.184 → 0.8.186
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-DEncBxR7.js → hyperframes-player-B_skzvYC.js} +1 -1
- package/dist/assets/{index-Cc22mgvY.js → index-CgLbvCUo.js} +1 -1
- package/dist/assets/{index-BABXr78Q.js → index-Dn5FAk2e.js} +99 -99
- package/dist/index.html +1 -1
- package/dist/index.js +7 -2
- package/package.json +8 -8
- package/src/components/editor/audioFxTelemetry.ts +1 -1
- package/src/components/editor/domEditingAgentPrompt.test.ts +2 -2
- package/src/components/editor/domEditingAgentPrompt.ts +1 -1
- package/src/components/editor/useFxChainObserved.ts +1 -1
- package/src/components/feedback/projectProvenance.ts +1 -1
- package/src/components/renders/useRenderQueue.ts +8 -0
- package/src/components/renders/useRenderQueueMotionBlur.test.tsx +61 -0
- package/src/utils/previewSafeMargins.test.ts +6 -6
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-Dn5FAk2e.js"></script>
|
|
18
18
|
<link rel="stylesheet" crossorigin href="/assets/index-CESvLzz0.css">
|
|
19
19
|
</head>
|
|
20
20
|
<body>
|
package/dist/index.js
CHANGED
|
@@ -26696,7 +26696,7 @@ var GUARDRAIL_LINES = [
|
|
|
26696
26696
|
"- Make a targeted change to this element only, unless the request is a timeline edit.",
|
|
26697
26697
|
"- Preserve the rest of the composition and its timing, except what a timeline edit changes.",
|
|
26698
26698
|
"- Do not modify other elements' data-* attributes or positioning, except where the requested timeline edit requires it (split, retime, reorder, copy a group, swap media).",
|
|
26699
|
-
"- For timeline edits (trim, split, speed, volume, copy, swap),
|
|
26699
|
+
"- For timeline edits (trim, split, speed, volume, copy, swap), use the `hyperframes timeline` edit commands (`hyperframes-cli` skill) or the exact attribute forms in the `hyperframes` skill's `references/format.md`.",
|
|
26700
26700
|
"- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors."
|
|
26701
26701
|
];
|
|
26702
26702
|
function formatBoundingBox(bounds) {
|
|
@@ -72983,6 +72983,7 @@ function useRightPanelIntent() {
|
|
|
72983
72983
|
}
|
|
72984
72984
|
|
|
72985
72985
|
// src/components/renders/useRenderQueue.ts
|
|
72986
|
+
import { htmlInCanvasForm } from "@hyperframes/core/html-in-canvas";
|
|
72986
72987
|
import { useState as useState109, useEffect as useEffect106, useCallback as useCallback95, useRef as useRef119, useMemo as useMemo69 } from "react";
|
|
72987
72988
|
|
|
72988
72989
|
// src/components/renders/serverError.ts
|
|
@@ -73206,6 +73207,9 @@ function markFeedbackDismissed() {
|
|
|
73206
73207
|
}
|
|
73207
73208
|
|
|
73208
73209
|
// src/components/renders/useRenderQueue.ts
|
|
73210
|
+
function canPreviewMotionBlur() {
|
|
73211
|
+
return typeof document !== "undefined" && htmlInCanvasForm() !== null;
|
|
73212
|
+
}
|
|
73209
73213
|
function hiddenIdsKey(projectId) {
|
|
73210
73214
|
return `hf-studio-hidden-renders:${projectId}`;
|
|
73211
73215
|
}
|
|
@@ -73320,7 +73324,8 @@ function useRenderQueue(projectId, activeCompPathRef) {
|
|
|
73320
73324
|
const body = {
|
|
73321
73325
|
fps,
|
|
73322
73326
|
quality,
|
|
73323
|
-
format
|
|
73327
|
+
format,
|
|
73328
|
+
previewMotionBlur: canPreviewMotionBlur()
|
|
73324
73329
|
};
|
|
73325
73330
|
if (browserTelemetryAllowed()) {
|
|
73326
73331
|
body.telemetryDistinctId = getAnonymousId();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.186",
|
|
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.
|
|
63
|
-
"@hyperframes/
|
|
64
|
-
"@hyperframes/
|
|
65
|
-
"@hyperframes/
|
|
66
|
-
"@hyperframes/
|
|
67
|
-
"@hyperframes/
|
|
62
|
+
"@hyperframes/core": "0.8.186",
|
|
63
|
+
"@hyperframes/player": "0.8.186",
|
|
64
|
+
"@hyperframes/shader-transitions": "0.8.186",
|
|
65
|
+
"@hyperframes/studio-server": "0.8.186",
|
|
66
|
+
"@hyperframes/sdk": "0.8.186",
|
|
67
|
+
"@hyperframes/parsers": "0.8.186"
|
|
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.186"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"react": "19",
|
|
@@ -198,7 +198,7 @@ export function trackEqChanged(band: string, value: number): void {
|
|
|
198
198
|
*
|
|
199
199
|
* This is how agent-applied effects become visible at all. An agent asked to
|
|
200
200
|
* fix a mix does not drive the panel — it edits the composition HTML, or runs
|
|
201
|
-
* `scripts/carve.mjs`, and the rack simply finds the result already present.
|
|
201
|
+
* `skills/media-use/audio/scripts/carve.mjs`, and the rack simply finds the result already present.
|
|
202
202
|
* None of the events above will ever fire for that work.
|
|
203
203
|
*
|
|
204
204
|
* So the panel reports the shape of what it was handed, and how many edits this
|
|
@@ -81,7 +81,7 @@ describe("buildElementAgentPrompt", () => {
|
|
|
81
81
|
"- Make a targeted change to this element only, unless the request is a timeline edit.",
|
|
82
82
|
"- Preserve the rest of the composition and its timing, except what a timeline edit changes.",
|
|
83
83
|
"- Do not modify other elements' data-* attributes or positioning, except where the requested timeline edit requires it (split, retime, reorder, copy a group, swap media).",
|
|
84
|
-
"- For timeline edits (trim, split, speed, volume, copy, swap),
|
|
84
|
+
"- For timeline edits (trim, split, speed, volume, copy, swap), use the `hyperframes timeline` edit commands (`hyperframes-cli` skill) or the exact attribute forms in the `hyperframes` skill's `references/format.md`.",
|
|
85
85
|
"- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors.",
|
|
86
86
|
].join("\n"),
|
|
87
87
|
);
|
|
@@ -164,7 +164,7 @@ describe("buildPickerAgentPrompt", () => {
|
|
|
164
164
|
"- Make a targeted change to this element only, unless the request is a timeline edit.",
|
|
165
165
|
"- Preserve the rest of the composition and its timing, except what a timeline edit changes.",
|
|
166
166
|
"- Do not modify other elements' data-* attributes or positioning, except where the requested timeline edit requires it (split, retime, reorder, copy a group, swap media).",
|
|
167
|
-
"- For timeline edits (trim, split, speed, volume, copy, swap),
|
|
167
|
+
"- For timeline edits (trim, split, speed, volume, copy, swap), use the `hyperframes timeline` edit commands (`hyperframes-cli` skill) or the exact attribute forms in the `hyperframes` skill's `references/format.md`.",
|
|
168
168
|
"- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors.",
|
|
169
169
|
].join("\n");
|
|
170
170
|
expect(domPrompt.endsWith(guardrails)).toBe(true);
|
|
@@ -31,7 +31,7 @@ const GUARDRAIL_LINES = [
|
|
|
31
31
|
"- Make a targeted change to this element only, unless the request is a timeline edit.",
|
|
32
32
|
"- Preserve the rest of the composition and its timing, except what a timeline edit changes.",
|
|
33
33
|
"- Do not modify other elements' data-* attributes or positioning, except where the requested timeline edit requires it (split, retime, reorder, copy a group, swap media).",
|
|
34
|
-
"- For timeline edits (trim, split, speed, volume, copy, swap),
|
|
34
|
+
"- For timeline edits (trim, split, speed, volume, copy, swap), use the `hyperframes timeline` edit commands (`hyperframes-cli` skill) or the exact attribute forms in the `hyperframes` skill's `references/format.md`.",
|
|
35
35
|
"- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors.",
|
|
36
36
|
];
|
|
37
37
|
|
|
@@ -16,7 +16,7 @@ import type { DomEditSelection } from "./domEditingTypes";
|
|
|
16
16
|
/**
|
|
17
17
|
* This is the only way agent-applied effects become visible. An agent asked to
|
|
18
18
|
* fix a mix does not drive this panel — it edits the composition HTML, or runs
|
|
19
|
-
* `scripts/carve.mjs`, and the rack simply finds the work already done. Not
|
|
19
|
+
* `skills/media-use/audio/scripts/carve.mjs`, and the rack simply finds the work already done. Not
|
|
20
20
|
* one of the panel's own events fires for any of it.
|
|
21
21
|
*
|
|
22
22
|
* So: watch the chain's shape, and report it when it changes without a panel
|
|
@@ -3,7 +3,7 @@ import { buildProjectApiPath } from "../../utils/projectRouting";
|
|
|
3
3
|
// How this project came to exist, and roughly what shape it is.
|
|
4
4
|
//
|
|
5
5
|
// A crash report says what broke. It does not say what the user was working ON,
|
|
6
|
-
// and "a project made by /
|
|
6
|
+
// and "a project made by /slideshow with 14 compositions and 60 assets"
|
|
7
7
|
// reproduces a crash that "Cannot read properties of undefined" never will.
|
|
8
8
|
//
|
|
9
9
|
// Held at module scope on purpose: a crash unmounts the React tree, so anything
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { htmlInCanvasForm } from "@hyperframes/core/html-in-canvas";
|
|
1
2
|
import { buildProjectApiPath } from "../../utils/projectRouting";
|
|
2
3
|
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
|
3
4
|
import type { CanvasResolution } from "@hyperframes/parsers";
|
|
@@ -10,6 +11,11 @@ import { ffmpegInstallMessage, useFfmpegStatus } from "./useFfmpegStatus";
|
|
|
10
11
|
import { requestStudioFeedback, type FeedbackContext } from "../feedback/feedbackTrigger";
|
|
11
12
|
import { studioApiFetch } from "../../utils/studioApiFetch";
|
|
12
13
|
|
|
14
|
+
// WYSIWYG: the preview iframe runs in this browser, so a render blurs only if this one can.
|
|
15
|
+
function canPreviewMotionBlur(): boolean {
|
|
16
|
+
return typeof document !== "undefined" && htmlInCanvasForm() !== null;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
export interface RenderJob {
|
|
14
20
|
id: string;
|
|
15
21
|
status: "rendering" | "complete" | "failed" | "cancelled";
|
|
@@ -238,10 +244,12 @@ export function useRenderQueue(
|
|
|
238
244
|
gpu?: boolean;
|
|
239
245
|
telemetryDistinctId?: string;
|
|
240
246
|
telemetryOptOut?: boolean;
|
|
247
|
+
previewMotionBlur: boolean;
|
|
241
248
|
} = {
|
|
242
249
|
fps,
|
|
243
250
|
quality,
|
|
244
251
|
format,
|
|
252
|
+
previewMotionBlur: canPreviewMotionBlur(),
|
|
245
253
|
};
|
|
246
254
|
// The id is MINTED by getAnonymousId(), so calling it unconditionally
|
|
247
255
|
// created a telemetry identity for a profile that had opted out — and
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
// WYSIWYG: a render only blurs what the Studio preview could show, and only the browser knows
|
|
4
|
+
// whether it has html-in-canvas. The request body is how it tells the server.
|
|
5
|
+
|
|
6
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { startRenderAndReadBody, type MountedQueue } from "./renderQueueTestHarness";
|
|
8
|
+
|
|
9
|
+
vi.mock("../../telemetry/events", () => ({ trackStudioRenderStart: vi.fn() }));
|
|
10
|
+
|
|
11
|
+
const { useRenderQueue } = await import("./useRenderQueue");
|
|
12
|
+
|
|
13
|
+
let queue: MountedQueue | null = null;
|
|
14
|
+
|
|
15
|
+
async function startRenderBody(): Promise<Record<string, unknown>> {
|
|
16
|
+
const started = await startRenderAndReadBody(useRenderQueue, {
|
|
17
|
+
opts: { fps: 30, quality: "standard", format: "mp4" },
|
|
18
|
+
});
|
|
19
|
+
queue = started.queue;
|
|
20
|
+
return started.body;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function stubHtmlInCanvas(members: string[]): void {
|
|
24
|
+
vi.stubGlobal(
|
|
25
|
+
"CanvasRenderingContext2D",
|
|
26
|
+
class {
|
|
27
|
+
drawElementImage(): void {}
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
const proto = HTMLCanvasElement.prototype as unknown as Record<string, unknown>;
|
|
31
|
+
for (const name of ["requestPaint", ...members]) proto[name] = () => {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
queue?.unmount();
|
|
36
|
+
queue = null;
|
|
37
|
+
document.body.innerHTML = "";
|
|
38
|
+
for (const name of ["requestPaint", "layoutSubtree", "updateElementGeometry"]) {
|
|
39
|
+
delete (HTMLCanvasElement.prototype as unknown as Record<string, unknown>)[name];
|
|
40
|
+
}
|
|
41
|
+
vi.unstubAllGlobals();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("render request previewMotionBlur", () => {
|
|
45
|
+
it("is false in a browser without html-in-canvas", async () => {
|
|
46
|
+
expect((await startRenderBody())["previewMotionBlur"]).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it.each([
|
|
50
|
+
["the layoutsubtree form", "layoutSubtree"],
|
|
51
|
+
["the content=drawable form", "updateElementGeometry"],
|
|
52
|
+
])("is true in a browser with html-in-canvas in %s", async (_name, member) => {
|
|
53
|
+
stubHtmlInCanvas([member]);
|
|
54
|
+
expect((await startRenderBody())["previewMotionBlur"]).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("is false when html-in-canvas has neither known form", async () => {
|
|
58
|
+
stubHtmlInCanvas([]);
|
|
59
|
+
expect((await startRenderBody())["previewMotionBlur"]).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
4
|
import {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
safeBoxInsetPercent,
|
|
9
9
|
} from "./previewSafeMargins";
|
|
10
10
|
|
|
11
|
-
const SKILL_PATH = join(import.meta.dirname, "../../../../skills/hyperframes
|
|
11
|
+
const SKILL_PATH = join(import.meta.dirname, "../../../../skills/hyperframes/SKILL.md");
|
|
12
12
|
|
|
13
13
|
describe("safe boxes", () => {
|
|
14
14
|
it("are the 90% and 80% boxes, inset 5% and 10% from every edge", () => {
|
|
@@ -17,15 +17,15 @@ describe("safe boxes", () => {
|
|
|
17
17
|
});
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
it("has a safe-margins line naming both percentages", () => {
|
|
20
|
+
describe("the hyperframes skill matches the constants", () => {
|
|
21
|
+
it("has a safe-box line naming both percentages", () => {
|
|
23
22
|
const skill = readFileSync(SKILL_PATH, "utf8");
|
|
24
23
|
const line = skill
|
|
25
24
|
.split("\n")
|
|
26
25
|
.find(
|
|
27
26
|
(l) =>
|
|
28
|
-
|
|
27
|
+
/action-safe/i.test(l) &&
|
|
28
|
+
/title-safe/i.test(l) &&
|
|
29
29
|
l.includes(`${ACTION_SAFE_PERCENT}%`) &&
|
|
30
30
|
l.includes(`${TITLE_SAFE_PERCENT}%`),
|
|
31
31
|
);
|