From 431a00d4c4a087bc2d7121de073d105ae99df555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sun, 29 Mar 2026 11:01:59 +0000 Subject: [PATCH 1/3] fix(cli): read actual data-width/data-height in info command The info command was hardcoding dimensions based on portrait/landscape detection, which defaulted to portrait because parseHtml() doesn't read data-width/data-height from composition root elements. Now reads the actual attribute values from the HTML. Reproducer: npx hyperframes init test --template blank --non-interactive cd test && npx hyperframes info # Shows "1080x1920" (portrait) but index.html has data-width="1920" data-height="1080" --- packages/cli/src/commands/info.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/commands/info.ts b/packages/cli/src/commands/info.ts index ac471998..735d2796 100644 --- a/packages/cli/src/commands/info.ts +++ b/packages/cli/src/commands/info.ts @@ -34,12 +34,20 @@ export default defineCommand({ ensureDOMParser(); const parsed = parseHtml(html); + // Read actual dimensions from root composition element via DOM query + // (same approach as compositions.ts — avoids regex matching wrong element) + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + const root = doc.querySelector("[data-composition-id]"); + const width = parseInt(root?.getAttribute("data-width") ?? "1920", 10); + const height = parseInt(root?.getAttribute("data-height") ?? "1080", 10); + const resolution = width > height ? "landscape" : "portrait"; + const tracks = new Set(parsed.elements.map((el) => el.zIndex)); const maxEnd = parsed.elements.reduce( (max, el) => Math.max(max, el.startTime + el.duration), 0, ); - const resolution = parsed.resolution === "portrait" ? "1080x1920" : "1920x1080"; const size = totalSize(project.dir); const typeCounts: Record = {}; @@ -55,9 +63,9 @@ export default defineCommand({ JSON.stringify( withMeta({ name: project.name, - resolution: parsed.resolution, - width: parsed.resolution === "portrait" ? 1080 : 1920, - height: parsed.resolution === "portrait" ? 1920 : 1080, + resolution: resolution as "landscape" | "portrait", + width, + height, duration: maxEnd, elements: parsed.elements.length, tracks: tracks.size, @@ -72,7 +80,7 @@ export default defineCommand({ } console.log(`${c.success("◇")} ${c.accent(project.name)}`); - console.log(label("Resolution", resolution)); + console.log(label("Resolution", `${width}x${height}`)); console.log(label("Duration", `${maxEnd.toFixed(1)}s`)); console.log(label("Elements", `${parsed.elements.length}${typeStr ? ` (${typeStr})` : ""}`)); console.log(label("Tracks", `${tracks.size}`)); From 5b5525503e784542b016ae84711807cd7bf31a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sun, 29 Mar 2026 11:02:11 +0000 Subject: [PATCH 2/3] fix(cli): include all 9 templates in build output The decision-tree, kinetic-type, product-promo, and nyt-graph templates existed in source but were not copied to dist/templates/ during build. Users selecting these templates from init --help got an unhandled ENOENT crash with a raw Node.js stack trace. Reproducer: npx hyperframes init test --template decision-tree --non-interactive # Crashes: Error: ENOENT: no such file or directory, lstat '.../dist/templates/decision-tree' --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 7016b241..3f9ce674 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -20,7 +20,7 @@ "build:fonts": "cd ../producer && tsx scripts/generate-font-data.ts", "build:studio": "cd ../studio && bun run build", "build:runtime": "tsx scripts/build-runtime.ts", - "build:copy": "mkdir -p dist/studio dist/docs dist/templates dist/skills && cp -r ../studio/dist/* dist/studio/ && cp -r src/templates/blank src/templates/warm-grain src/templates/play-mode src/templates/swiss-grid src/templates/vignelli src/templates/_shared dist/templates/ && cp -r ../../skills/hyperframes-compose ../../skills/hyperframes-captions dist/skills/ && (cp src/docs/*.md dist/docs/ 2>/dev/null || true)", + "build:copy": "mkdir -p dist/studio dist/docs dist/templates dist/skills && cp -r ../studio/dist/* dist/studio/ && cp -r src/templates/blank src/templates/warm-grain src/templates/play-mode src/templates/swiss-grid src/templates/vignelli src/templates/decision-tree src/templates/kinetic-type src/templates/product-promo src/templates/nyt-graph src/templates/_shared dist/templates/ && cp -r ../../skills/hyperframes-compose ../../skills/hyperframes-captions dist/skills/ && (cp src/docs/*.md dist/docs/ 2>/dev/null || true)", "typecheck": "tsc --noEmit" }, "dependencies": { From 817c044f09b87dc372edfb61f139d4c733d2ca7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sun, 29 Mar 2026 11:02:23 +0000 Subject: [PATCH 3/3] fix(cli): suppress ANSI escape codes in non-TTY progress output renderProgress() was writing \r\x1b[2K escape codes even when stdout is not a TTY, corrupting output for CI pipelines and AI agents. Now writes clean text lines at 10% intervals when not a TTY. Reproducer: cd my-video npx hyperframes render --output out.mp4 2>&1 | cat # Output contained raw escape codes: \x1b[2K --- packages/cli/src/ui/progress.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/ui/progress.ts b/packages/cli/src/ui/progress.ts index f4aa4c3e..02ef9bff 100644 --- a/packages/cli/src/ui/progress.ts +++ b/packages/cli/src/ui/progress.ts @@ -2,7 +2,19 @@ import { c } from "./colors.js"; const { stdout } = process; +let lastPrintedThreshold = -1; + export function renderProgress(percent: number, stage: string, row?: number): void { + // Non-TTY: write clean lines at 10% intervals, skip bar computation entirely + if (!stdout.isTTY) { + const rounded = Math.round(percent); + if ((rounded % 10 === 0 || rounded === 100) && rounded !== lastPrintedThreshold) { + lastPrintedThreshold = rounded; + stdout.write(` ${rounded}% ${stage}\n`); + } + return; + } + const width = 25; const filled = Math.floor(percent / (100 / width)); const empty = width - filled; @@ -10,7 +22,7 @@ export function renderProgress(percent: number, stage: string, row?: number): vo const line = ` ${bar} ${c.bold(String(Math.round(percent)) + "%")} ${c.dim(stage)}`; - if (row !== undefined && stdout.isTTY) { + if (row !== undefined) { stdout.write(`\x1b[${row};1H\x1b[2K${line}`); } else { stdout.write(`\r\x1b[2K${line}`);