@hyperframes/studio 0.7.80 → 0.7.82

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": "@hyperframes/studio",
3
- "version": "0.7.80",
3
+ "version": "0.7.82",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,11 +46,11 @@
46
46
  "gsap": "^3.13.0",
47
47
  "marked": "^14.1.4",
48
48
  "mediabunny": "^1.45.3",
49
- "@hyperframes/core": "0.7.80",
50
- "@hyperframes/parsers": "0.7.80",
51
- "@hyperframes/player": "0.7.80",
52
- "@hyperframes/sdk": "0.7.80",
53
- "@hyperframes/studio-server": "0.7.80"
49
+ "@hyperframes/core": "0.7.82",
50
+ "@hyperframes/parsers": "0.7.82",
51
+ "@hyperframes/player": "0.7.82",
52
+ "@hyperframes/sdk": "0.7.82",
53
+ "@hyperframes/studio-server": "0.7.82"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/react": "19",
@@ -65,7 +65,7 @@
65
65
  "vite": "^6.4.2",
66
66
  "vitest": "^3.2.4",
67
67
  "zustand": "^5.0.0",
68
- "@hyperframes/producer": "0.7.80"
68
+ "@hyperframes/producer": "0.7.82"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "react": "19",
@@ -0,0 +1,62 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+ import { RenderQueue } from "./RenderQueue";
7
+
8
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
9
+
10
+ let root: Root | null = null;
11
+
12
+ afterEach(() => {
13
+ if (root) act(() => root?.unmount());
14
+ root = null;
15
+ document.body.innerHTML = "";
16
+ });
17
+
18
+ function mountRenderQueue(onStartRender: ReturnType<typeof vi.fn>) {
19
+ const host = document.createElement("div");
20
+ document.body.append(host);
21
+ root = createRoot(host);
22
+ act(() => {
23
+ root?.render(
24
+ <RenderQueue
25
+ jobs={[]}
26
+ projectId="demo"
27
+ onDelete={vi.fn()}
28
+ onClearCompleted={vi.fn()}
29
+ onStartRender={onStartRender}
30
+ isRendering={false}
31
+ compositionDimensions={{ width: 1920, height: 1080 }}
32
+ />,
33
+ );
34
+ });
35
+ return host;
36
+ }
37
+
38
+ describe("RenderQueue resolution submission", () => {
39
+ it("submits the canonical landscape 4K preset selected by the user", () => {
40
+ const onStartRender = vi.fn();
41
+ const host = mountRenderQueue(onStartRender);
42
+ const resolutionSelect = [...host.querySelectorAll("select")].find((select) =>
43
+ [...select.options].some((option) => option.textContent?.startsWith("4K")),
44
+ );
45
+ if (!resolutionSelect) throw new Error("resolution selector did not render");
46
+
47
+ act(() => {
48
+ resolutionSelect.value = "4k";
49
+ resolutionSelect.dispatchEvent(new Event("change", { bubbles: true }));
50
+ });
51
+
52
+ const exportButton = [...host.querySelectorAll("button")].find(
53
+ (button) => button.textContent === "Export",
54
+ );
55
+ if (!exportButton) throw new Error("export button did not render");
56
+ act(() => {
57
+ exportButton.dispatchEvent(new MouseEvent("click", { bubbles: true }));
58
+ });
59
+
60
+ expect(onStartRender).toHaveBeenCalledWith("mp4", "standard", "landscape-4k", 30);
61
+ });
62
+ });
@@ -1,4 +1,5 @@
1
1
  import { memo, useState, useRef, useEffect, useId } from "react";
2
+ import { CANVAS_DIMENSIONS } from "@hyperframes/parsers";
2
3
  import { RenderQueueItem } from "./RenderQueueItem";
3
4
  import { Button } from "../ui/Button";
4
5
  import type { RenderJob, ResolutionPreset } from "./useRenderQueue";
@@ -53,17 +54,6 @@ const SCALE_LABEL: Record<RenderScale, string> = {
53
54
  "4k": "4K",
54
55
  };
55
56
 
56
- // Mirrors `CANVAS_DIMENSIONS` in @hyperframes/core. Studio can't import from
57
- // the core barrel (it transitively pulls in node:fs) and the values are stable.
58
- const CANVAS_DIMENSIONS: Record<ResolutionPreset, CompositionDimensions> = {
59
- landscape: { width: 1920, height: 1080 },
60
- portrait: { width: 1080, height: 1920 },
61
- "landscape-4k": { width: 3840, height: 2160 },
62
- "portrait-4k": { width: 2160, height: 3840 },
63
- square: { width: 1080, height: 1080 },
64
- "square-4k": { width: 2160, height: 2160 },
65
- };
66
-
67
57
  type CompAspect = "landscape" | "portrait" | "square";
68
58
 
69
59
  function compAspect(dims: CompositionDimensions | null | undefined): CompAspect {
@@ -254,7 +244,7 @@ function FormatExportButton({
254
244
  const persisted = getPersistedRenderSettings();
255
245
  const [format, setFormat] = useState<"mp4" | "webm" | "mov">(persisted.format);
256
246
  const [quality, setQuality] = useState<"draft" | "standard" | "high">(persisted.quality);
257
- const [resolution, setResolution] = useState<ResolutionPreset | "auto">("auto");
247
+ const [resolution, setResolution] = useState<RenderScale>("auto");
258
248
  const [fps, setFps] = useState<24 | 30 | 60>(persisted.fps);
259
249
 
260
250
  // MOV (ProRes) is a fixed-quality codec — quality selector has no effect.
@@ -290,7 +280,7 @@ function FormatExportButton({
290
280
  <span className="text-[10px] text-panel-text-4">Resolution</span>
291
281
  <select
292
282
  value={resolution}
293
- onChange={(e) => setResolution(e.target.value as ResolutionPreset | "auto")}
283
+ onChange={(e) => setResolution(e.target.value as RenderScale)}
294
284
  disabled={isRendering}
295
285
  className={selectCls}
296
286
  >
@@ -352,8 +342,9 @@ function FormatExportButton({
352
342
  // loading already disables the button; this guard also stops a
353
343
  // double-click in the same frame from enqueueing two renders.
354
344
  if (isRendering) return;
355
- trackStudioEvent("render_start", { format, quality, resolution, fps });
356
- void onStartRender(format, quality, resolution, fps);
345
+ const outputResolution = resolveResolution(resolution, compositionDimensions);
346
+ trackStudioEvent("render_start", { format, quality, resolution: outputResolution, fps });
347
+ void onStartRender(format, quality, outputResolution, fps);
357
348
  }}
358
349
  className="w-full text-[11px] font-semibold"
359
350
  >
@@ -1,4 +1,5 @@
1
1
  import { useState, useEffect, useCallback, useRef, useMemo } from "react";
2
+ import type { CanvasResolution } from "@hyperframes/parsers";
2
3
  import { trackStudioRenderStart } from "../../telemetry/events";
3
4
  import { getAnonymousId } from "../../telemetry/config";
4
5
  import { generateId } from "../../utils/generateId";
@@ -14,17 +15,10 @@ export interface RenderJob {
14
15
  durationMs?: number;
15
16
  }
16
17
 
17
- // Mirrors `CanvasResolution` from @hyperframes/core. Kept local because
18
- // studio's tsconfig doesn't include node types, and the core barrel
19
- // transitively pulls in modules with `node:fs` imports. Drift risk is
20
- // low (6 string literals kept in sync manually with CANVAS_DIMENSIONS).
21
- export type ResolutionPreset =
22
- | "landscape"
23
- | "portrait"
24
- | "landscape-4k"
25
- | "portrait-4k"
26
- | "square"
27
- | "square-4k";
18
+ // The CLI consumes this same source through @hyperframes/core's re-export.
19
+ // Importing from the browser-safe parsers package avoids the core barrel's
20
+ // Node-only transitive modules without duplicating the preset union in Studio.
21
+ export type ResolutionPreset = CanvasResolution;
28
22
 
29
23
  export interface StartRenderOptions {
30
24
  fps?: number;