Skip to content

Commit 005e196

Browse files
committed
fix(studio): clean next alpha inspector artifacts
1 parent 0e0f1dd commit 005e196

27 files changed

Lines changed: 154 additions & 77 deletions

‎.gitignore‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ tmp/
6262
packages/core/src/generated/
6363
packages/producer/src/services/fontData.generated.ts
6464

65-
# Test artifacts
65+
# Local proof / test artifacts
66+
qa-artifacts/
6667
my-video/
6768
examples/
6869
packages/studio/data/

‎docs/packages/cli.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ This is suppressed in CI environments, non-TTY shells, and when `HYPERFRAMES_NO_
162162
| Flag | Description |
163163
|------|-------------|
164164
| `--example, -e` | Example to scaffold (required in default mode, interactive in `--human-friendly`) |
165-
| `--resolution` | Canvas preset: `landscape` (1920×1080), `portrait` (1080×1920), `landscape-4k` (3840×2160), `portrait-4k` (2160×3840). Aliases: `1080p`, `4k`, `uhd`. Default: keep template dimensions. |
165+
| `--resolution` | Canvas preset: `landscape` (1920×1080), `portrait` (1080×1920), `landscape-4k` (3840×2160), `portrait-4k` (2160×3840), `square` (1080×1080), `square-4k` (2160×2160). Aliases: `1080p`, `4k`, `uhd`, `1080p-square`, `square-1080p`, `4k-square`. Default: keep template dimensions. |
166166
| `--video, -V` | Path to a video file (MP4, WebM, MOV) |
167167
| `--audio, -a` | Path to an audio file (MP3, WAV, M4A) |
168168
| `--tailwind` | Add Tailwind CSS browser-runtime support to scaffolded HTML |
@@ -610,7 +610,7 @@ This is suppressed in CI environments, non-TTY shells, and when `HYPERFRAMES_NO_
610610
| `--quality` | draft, standard, high | standard | Encoding quality preset (drives CRF/bitrate) |
611611
| `--crf` | 0-51 | — | Override encoder CRF (lower = higher quality). Mutually exclusive with `--video-bitrate` |
612612
| `--video-bitrate` | e.g. `10M`, `5000k` | — | Target video bitrate. Mutually exclusive with `--crf` |
613-
| `--resolution` | landscape, portrait, landscape-4k, portrait-4k (aliases: `1080p`, `4k`, `uhd`) | — | Output resolution preset. Supersamples a smaller composition via Chrome `deviceScaleFactor` so the screenshot lands at the requested dimensions. Aspect ratio must match the composition; the scale must be an integer multiple. Not supported with `--hdr`. See [4K Rendering](/guides/4k-rendering) |
613+
| `--resolution` | landscape, portrait, landscape-4k, portrait-4k, square, square-4k (aliases: `1080p`, `4k`, `uhd`, `1080p-square`, `square-1080p`, `4k-square`) | — | Output resolution preset. Supersamples a smaller composition via Chrome `deviceScaleFactor` so the screenshot lands at the requested dimensions. Aspect ratio must match the composition; the scale must be an integer multiple. Not supported with `--hdr`. See [4K Rendering](/guides/4k-rendering) |
614614
| `--hdr` | — | off | Force HDR output even if no HDR sources are detected. MP4 only. See [HDR Rendering](/guides/hdr) |
615615
| `--sdr` | — | off | Force SDR output even if HDR sources are detected |
616616
| `--workers` | 1-8 | 4 | Parallel render workers |

‎docs/packages/core.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import type {
5151
TimelineElementType, // "video" | "image" | "text" | "audio" | "composition"
5252
CompositionSpec,
5353
CompositionVariable,
54-
CanvasResolution, // "landscape" | "portrait"
54+
CanvasResolution, // "landscape" | "portrait" | "landscape-4k" | "portrait-4k" | "square" | "square-4k"
5555
Orientation, // "16:9" | "9:16"
5656
FrameAdapter,
5757
FrameAdapterContext,

‎packages/cli/src/commands/init.test.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,38 @@ describe("applyResolutionPreset", () => {
207207
});
208208
});
209209

210+
it("swaps to square dimensions for square", () => {
211+
withFixture((dir) => {
212+
const file = join(dir, "index.html");
213+
writeFileSync(file, sampleHtml, "utf-8");
214+
215+
applyResolutionPreset(dir, "square");
216+
const out = readFileSync(file, "utf-8");
217+
218+
expect(out).toContain('data-width="1080"');
219+
expect(out).toContain('data-height="1080"');
220+
expect(out).toContain('data-resolution="square"');
221+
expect(out).toContain("width: 1080px");
222+
expect(out).toContain("height: 1080px");
223+
});
224+
});
225+
226+
it("swaps to square-4k dimensions", () => {
227+
withFixture((dir) => {
228+
const file = join(dir, "index.html");
229+
writeFileSync(file, sampleHtml, "utf-8");
230+
231+
applyResolutionPreset(dir, "square-4k");
232+
const out = readFileSync(file, "utf-8");
233+
234+
expect(out).toContain('data-width="2160"');
235+
expect(out).toContain('data-height="2160"');
236+
expect(out).toContain('data-resolution="square-4k"');
237+
expect(out).toContain("width: 2160px");
238+
expect(out).toContain("height: 2160px");
239+
});
240+
});
241+
210242
it("scaffolds a 4k project end-to-end via --resolution 4k", () => {
211243
const dir = mkdtempSync(join(tmpdir(), "hf-init-test-"));
212244
const target = join(dir, "proj");

‎packages/cli/src/commands/init.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ export default defineCommand({
626626
resolution: {
627627
type: "string",
628628
description:
629-
"Canvas resolution preset: landscape (1920x1080), portrait (1080x1920), landscape-4k (3840x2160), portrait-4k (2160x3840). Aliases: 1080p, 4k, uhd. Default: keep template dimensions (typically 1920x1080).",
629+
"Canvas resolution preset: landscape (1920x1080), portrait (1080x1920), landscape-4k (3840x2160), portrait-4k (2160x3840), square (1080x1080), square-4k (2160x2160). Aliases: 1080p, 4k, uhd, 1080p-square, square-1080p, 4k-square. Default: keep template dimensions (typically 1920x1080).",
630630
},
631631
},
632632
async run({ args }) {
@@ -658,7 +658,8 @@ export default defineCommand({
658658
console.error(
659659
c.error(
660660
`Invalid --resolution: "${args.resolution}". ` +
661-
`Use one of: landscape, portrait, landscape-4k, portrait-4k (or aliases 1080p, 4k, uhd).`,
661+
`Use one of: landscape, portrait, landscape-4k, portrait-4k, square, square-4k ` +
662+
`(or aliases 1080p, 4k, uhd, 1080p-square, square-1080p, 4k-square).`,
662663
),
663664
);
664665
process.exit(1);

‎packages/cli/src/commands/render.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ export default defineCommand({
186186
resolution: {
187187
type: "string",
188188
description:
189-
"Output resolution preset: landscape (1920x1080), portrait (1080x1920), landscape-4k (3840x2160), portrait-4k (2160x3840). Aliases: 1080p, 4k, uhd. The composition is unchanged — Chrome renders at higher DPR (deviceScaleFactor) so the captured screenshot lands at the requested dimensions. Aspect ratio must match the composition; the scale must be an integer multiple. Not yet supported with --hdr.",
189+
"Output resolution preset: landscape (1920x1080), portrait (1080x1920), landscape-4k (3840x2160), portrait-4k (2160x3840), square (1080x1080), square-4k (2160x2160). Aliases: 1080p, 4k, uhd, 1080p-square, square-1080p, 4k-square. The composition is unchanged — Chrome renders at higher DPR (deviceScaleFactor) so the captured screenshot lands at the requested dimensions. Aspect ratio must match the composition; the scale must be an integer multiple. Not yet supported with --hdr.",
190190
},
191191
},
192192
async run({ args }) {
@@ -224,7 +224,8 @@ export default defineCommand({
224224
if (!outputResolution) {
225225
errorBox(
226226
"Invalid resolution",
227-
`Got "${args.resolution}". Must be one of: landscape, portrait, landscape-4k, portrait-4k (or aliases 1080p, 4k, uhd).`,
227+
`Got "${args.resolution}". Must be one of: landscape, portrait, landscape-4k, portrait-4k, square, square-4k ` +
228+
`(or aliases 1080p, 4k, uhd, 1080p-square, square-1080p, 4k-square).`,
228229
);
229230
process.exit(1);
230231
}

‎packages/core/src/core.types.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export const CANVAS_DIMENSIONS = {
2424
portrait: { width: 1080, height: 1920 },
2525
"landscape-4k": { width: 3840, height: 2160 },
2626
"portrait-4k": { width: 2160, height: 3840 },
27+
square: { width: 1080, height: 1080 },
28+
"square-4k": { width: 2160, height: 2160 },
2729
} as const;
2830

2931
// Single source of truth: derive the type from the table so adding a preset
@@ -47,6 +49,9 @@ const RESOLUTION_ALIASES: Record<string, CanvasResolution> = {
4749
"4k": "landscape-4k",
4850
uhd: "landscape-4k",
4951
"4k-portrait": "portrait-4k",
52+
"1080p-square": "square",
53+
"square-1080p": "square",
54+
"4k-square": "square-4k",
5055
};
5156

5257
/**

‎packages/core/src/index.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ describe("@hyperframes/core public API exports", () => {
1010
expect(core.CANVAS_DIMENSIONS.portrait).toEqual({ width: 1080, height: 1920 });
1111
expect(core.CANVAS_DIMENSIONS["landscape-4k"]).toEqual({ width: 3840, height: 2160 });
1212
expect(core.CANVAS_DIMENSIONS["portrait-4k"]).toEqual({ width: 2160, height: 3840 });
13+
expect(core.CANVAS_DIMENSIONS.square).toEqual({ width: 1080, height: 1080 });
14+
expect(core.CANVAS_DIMENSIONS["square-4k"]).toEqual({ width: 2160, height: 2160 });
1315
});
1416

1517
it("exports VALID_CANVAS_RESOLUTIONS derived from CANVAS_DIMENSIONS", () => {
@@ -18,6 +20,8 @@ describe("@hyperframes/core public API exports", () => {
1820
"portrait",
1921
"landscape-4k",
2022
"portrait-4k",
23+
"square",
24+
"square-4k",
2125
]);
2226
});
2327

@@ -27,6 +31,10 @@ describe("@hyperframes/core public API exports", () => {
2731
expect(core.normalizeResolutionFlag("1080p")).toBe("landscape");
2832
expect(core.normalizeResolutionFlag("landscape-4k")).toBe("landscape-4k");
2933
expect(core.normalizeResolutionFlag("UHD")).toBe("landscape-4k");
34+
expect(core.normalizeResolutionFlag("square")).toBe("square");
35+
expect(core.normalizeResolutionFlag("square-4k")).toBe("square-4k");
36+
expect(core.normalizeResolutionFlag("1080p-square")).toBe("square");
37+
expect(core.normalizeResolutionFlag("4k-square")).toBe("square-4k");
3038
expect(core.normalizeResolutionFlag("8k")).toBeUndefined();
3139
expect(core.normalizeResolutionFlag(undefined)).toBeUndefined();
3240
});

‎packages/core/src/parsers/htmlParser.test.ts‎

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,21 @@ describe("parseHtml", () => {
197197
expect(result.resolution).toBe("portrait");
198198
});
199199

200+
it("keeps explicit portrait resolution even when dimensions are square", () => {
201+
const html = `
202+
<html data-resolution="portrait" data-composition-width="1080" data-composition-height="1080">
203+
<body>
204+
<div id="stage">
205+
<div id="text1" data-start="0" data-end="5"><div>Hello</div></div>
206+
</div>
207+
</body>
208+
</html>
209+
`;
210+
const result = parseHtml(html);
211+
212+
expect(result.resolution).toBe("portrait");
213+
});
214+
200215
it("defaults to portrait when no resolution info is available", () => {
201216
const html = `
202217
<html>
@@ -275,10 +290,7 @@ describe("parseHtml", () => {
275290
expect(result.resolution).toBe("landscape");
276291
});
277292

278-
it("classifies square compositions as portrait by convention", () => {
279-
// 1080×1080 has no obvious orientation. The parser collapses the tie to
280-
// portrait — same bias the prior `w > h ? landscape : portrait` ternary
281-
// had. Pinning so a future refactor doesn't silently flip it.
293+
it("infers square resolution from equal width/height", () => {
282294
const html = `
283295
<html data-composition-width="1080" data-composition-height="1080">
284296
<body>
@@ -290,7 +302,22 @@ describe("parseHtml", () => {
290302
`;
291303
const result = parseHtml(html);
292304

293-
expect(result.resolution).toBe("portrait");
305+
expect(result.resolution).toBe("square");
306+
});
307+
308+
it("infers square-4k from equal width/height >= 2160", () => {
309+
const html = `
310+
<html data-composition-width="2160" data-composition-height="2160">
311+
<body>
312+
<div id="stage">
313+
<div id="text1" data-start="0" data-end="5"><div>Hello</div></div>
314+
</div>
315+
</body>
316+
</html>
317+
`;
318+
const result = parseHtml(html);
319+
320+
expect(result.resolution).toBe("square-4k");
294321
});
295322

296323
it("extracts x, y, scale, opacity from data attributes", () => {

‎packages/core/src/parsers/htmlParser.ts‎

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ function parseResolutionFromHtml(doc: Document): CanvasResolution | null {
124124
resolutionAttr === "landscape" ||
125125
resolutionAttr === "portrait" ||
126126
resolutionAttr === "landscape-4k" ||
127-
resolutionAttr === "portrait-4k"
127+
resolutionAttr === "portrait-4k" ||
128+
resolutionAttr === "square" ||
129+
resolutionAttr === "square-4k"
128130
) {
129131
return resolutionAttr;
130132
}
@@ -143,17 +145,17 @@ function parseResolutionFromHtml(doc: Document): CanvasResolution | null {
143145
}
144146

145147
function resolveResolutionFromDimensions(width: number, height: number): CanvasResolution {
146-
// `width === height` (square) falls into the portrait branch by convention —
147-
// the same bias the previous `w > h ? landscape : portrait` ternary used.
148-
// Square compositions are rare; pick portrait-as-default so we don't surprise
149-
// the existing call sites that depend on this behavior.
150-
const isLandscape = width > height;
151148
const longSide = Math.max(width, height);
152-
// UHD cutoff is the long side of `landscape-4k` / `portrait-4k` (3840). A
153-
// looser threshold (e.g. ≥ 2560) would silently misclassify QHD/1440p
154-
// (2560×1440) as 4K, which is the wrong default for a common authoring
155-
// resolution closer to 1080p than to UHD. Authors who genuinely want the
156-
// 4K preset can still set `data-resolution="landscape-4k"` explicitly.
149+
// UHD cutoff is the long side of the 4K presets (3840 for `landscape-4k` /
150+
// `portrait-4k`, 2160 for `square-4k`). A looser threshold (e.g. >= 2560)
151+
// would silently misclassify QHD/1440p (2560x1440) as 4K, which is the
152+
// wrong default for a common authoring resolution closer to 1080p than to
153+
// UHD. Authors who genuinely want the 4K preset can still set
154+
// `data-resolution="..."` explicitly.
155+
if (width === height) {
156+
return longSide >= 2160 ? "square-4k" : "square";
157+
}
158+
const isLandscape = width > height;
157159
const isUhd = longSide >= 3840;
158160
if (isLandscape) return isUhd ? "landscape-4k" : "landscape";
159161
return isUhd ? "portrait-4k" : "portrait";

0 commit comments

Comments
 (0)