@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/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-BABXr78Q.js"></script>
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), follow the creator-editing-recipes reference of the hyperframes-core skill and use its exact attribute forms.",
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.184",
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.184",
63
- "@hyperframes/parsers": "0.8.184",
64
- "@hyperframes/sdk": "0.8.184",
65
- "@hyperframes/shader-transitions": "0.8.184",
66
- "@hyperframes/studio-server": "0.8.184",
67
- "@hyperframes/player": "0.8.184"
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.184"
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), follow the creator-editing-recipes reference of the hyperframes-core skill and use its exact attribute forms.",
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), follow the creator-editing-recipes reference of the hyperframes-core skill and use its exact attribute forms.",
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), follow the creator-editing-recipes reference of the hyperframes-core skill and use its exact attribute forms.",
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 /faceless-explainer with 14 compositions and 60 assets"
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 { existsSync, readFileSync } from "node:fs";
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-studio/SKILL.md");
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
- // Skipped until the hyperframes-studio skill exists on this branch.
21
- describe.runIf(existsSync(SKILL_PATH))("hyperframes-studio skill matches the constants", () => {
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
- /\bsafe margins?\b/i.test(l) &&
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
  );