Skip to content

feat(cli): auto-detect aspect_ratio from composition dims when --aspect-ratio is omitted#1145

Merged
jrusso1020 merged 1 commit into
mainfrom
feat/cli-auto-detect-aspect-ratio
Jun 1, 2026
Merged

feat(cli): auto-detect aspect_ratio from composition dims when --aspect-ratio is omitted#1145
jrusso1020 merged 1 commit into
mainfrom
feat/cli-auto-detect-aspect-ratio

Conversation

@jrusso1020
Copy link
Copy Markdown
Collaborator

What

When the user runs hyperframes cloud render without --aspect-ratio and the project source is a local directory, the CLI now parses the entry HTML's root <div data-composition-id ...> for data-width / data-height and picks the supported aspect ratio that matches within ±0.05 tolerance:

Ratio Decimal Example dims
16:9 ≈1.778 1920×1080, 1280×720, 3840×2160
9:16 ≈0.563 1080×1920
1:1 =1.0 1080×1080

Behavior summary:

  • Match found: CLI sets aspect_ratio in the submit body + prints Detected aspect ratio: <X> (from <entry.html> dims WxH) (suppressed in --json mode).
  • No match (4:5, 5:4, 21:9, weird custom ratio): one-line warning explaining the fallback; aspect_ratio left out of the body so the server's default of 16:9 applies. User can pass --aspect-ratio explicitly to override.
  • Explicit --aspect-ratio: always wins. Detection is skipped entirely.
  • --asset-id or --url project: detection skipped (composition zip isn't local). User gets a brief note + can pass --aspect-ratio.

Why

ef#38182's PR body called out auto as deferred to a follow-up that would have needed a server-side zip-parse capability. James pointed out that CLI-side detection is cleaner — the CLI already has the composition on disk for the common hyperframes cloud render . flow, and parsing two attributes off the root div is much cheaper than wiring zip-extraction into the API controller. No API change needed; the existing aspect_ratio field is already Optional on the server, and the new {16:9, 9:16, 1:1} enum is the only value set the CLI sends.

Net effect: a user authoring a 9:16 portrait composition (data-width="1080" data-height="1920") and running npx hyperframes cloud render gets a portrait render automatically, instead of having to remember --aspect-ratio 9:16.

How

Three files touched:

  1. New helper packages/cli/src/cloud/detectAspectRatio.ts — pure regex-based parse, no DOM library dep. Exports detectAspectRatioFromHtml(path) and detectAspectRatioFromHtmlString(html) returning a tagged union: matched | no-root-div | no-dims | invalid-dims | no-match | read-error. The tolerance constant is exported for tests + caller introspection.
  2. packages/cli/src/commands/cloud/render.ts — calls the helper from run() when --aspect-ratio is absent; helpers maybeAutoDetectAspectRatio + logDetection + summarizeDetection handle the project-kind dispatch and per-result messaging.
  3. New test file packages/cli/src/cloud/detectAspectRatio.test.ts — 23 tests covering:
    • Canonical matches for all three ratios (1920×1080, 1080×1920, 1080×1080, plus 1280×720 and 3840×2160 for 16:9).
    • In-band tolerance (1916×1080 still 16:9).
    • No-match cases that surface as warnings: 4:5 (864×1080), 5:4 (1350×1080), 3:2 (1500×1000), 21:9 ultrawide (2560×1080).
    • Structural edges: missing root div, missing data-width, missing data-height, zero/negative dims, attribute order, unquoted attrs, single-quoted attrs, self-closing tag, sub-composition not picked over root, whitespace-heavy opening tag.
    • Tolerance-cutoff sanity check (4:5 must NOT match 1:1 — silently mis-classifying portrait social as square would be the worst kind of bug).

Parsing approach uses two regexes: one to locate the first <div ... data-composition-id ...> opening tag, then \bdata-(width|height)\s*= extractors against just that tag's text. Quote-style permissive ("x" | 'x' | x). Case-insensitive. Pulling in jsdom or node-html-parser would be heavier than this problem needs.

Two fallow-ignore-next-line complexity pragmas added on maybeAutoDetectAspectRatio and summarizeDetection — both are flat dispatch on a 6-arm discriminated union (cognitive complexity 1 on summarizeDetection); the cyclomatic threshold doesn't fit pure switch tables. Same escape hatch used by hf#1140's audio-mixer dispatch.

Test plan

  • 23 unit tests in packages/cli/src/cloud/detectAspectRatio.test.ts — all green locally

  • bunx oxlint, bunx oxfmt --check, bunx fallow audit --base origin/main --fail-on-issues, and cd packages/core && bunx tsc --noEmit && cd ../studio && bunx tsc --noEmit all green via lefthook pre-commit

  • Manual smoke: ran with a 1080×1920 composition + no flags → logs Detected aspect ratio: 9:16 ...; ran with --aspect-ratio 16:9 → no detection log; ran with --asset-id ... → logs Auto-detect skipped (project is --asset-id) ...

  • Unit tests added/updated

  • Manual testing performed

  • Documentation updated — packages/cli/src/docs/rendering.md doesn't yet have a --aspect-ratio section; deferred to the eventual docs sweep that covers the post-hf#1143 flag surface as a whole

Closes the auto carve-out

auto-style fallback for the v3 HyperFrames render API was deferred in ef#38182 with two paths flagged: (a) server-side zip-parse, (b) CLI-side resolution. This PR ships path (b) — the cleanest one for the existing render-pipeline surface. The HyperframesAspectRatio enum on the API stays exactly {16:9, 9:16, 1:1} (no auto member); the CLI just doesn't pass the field when it can't detect, letting the server's 16:9 default apply.

— Created by Rames Jusso (hyperframes specialist, Rames team)

…ct-ratio is omitted

When the user runs `hyperframes cloud render` without `--aspect-ratio` and
the project source is a local directory, parse the entry HTML's root
`<div data-composition-id ...>` for `data-width` / `data-height` and pick
the supported aspect ratio that matches within ±0.05 tolerance:

- 16:9 (≈1.778) ← landscape 1920×1080, 4K 3840×2160, etc.
- 9:16 (≈0.563) ← portrait 1080×1920
- 1:1 (=1.0)    ← square 1080×1080

If the composition's ratio matches one of these, the CLI sets
`aspect_ratio` in the submit body and prints a one-line note
(`Detected aspect ratio: 9:16 (from index.html dims 1080×1920)`).

If the composition has no root div, no dims, or a ratio outside all three
tolerance bands (e.g. 4:5, 5:4, 21:9), the CLI logs a one-line warning
explaining the fallback and leaves `aspect_ratio` out of the submit body
— the server defaults to 16:9, and the user can pass `--aspect-ratio`
explicitly to override.

Explicit `--aspect-ratio` always wins. Detection is skipped for
`--asset-id` / `--url` project sources since the composition isn't on
disk; user gets a brief note in that case too.

New helper: `packages/cli/src/cloud/detectAspectRatio.ts` (pure regex
parse, no DOM library dep). 23 tests cover canonical matches, in-band
tolerance, all three non-match patterns (no root div, no dims, ratio out
of bands), and authoring edge cases (unquoted attrs, attribute order,
self-closing tags, multi-composition files).

Closes the `auto` carve-out flagged in ef#38182's deferred-scope note —
the CLI gets auto-detect without requiring a server-side zip-parse
capability (no API change).
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Regex approach is the right call — no heavy dep for two attributes off a well-known tag pattern.

One edge case: ROOT_COMPOSITION_DIV_RE uses [^>] spans, so a literal > inside a quoted attribute value on the root div would truncate the match → no-dims. Composition roots don't carry prose attributes so this is fine in practice; a comment noting the assumption would be enough.

Everything else is solid:

  • Tagged union covers all 6 outcomes — caller can't miss any branch
  • Tolerance math verified: 4:5 and 5:4 are ≥0.2 away from any supported ratio, well outside the 0.05 band. The tolerance sanity-check test pinning this is the right call.
  • JSON suppression is correct for machine-readable output
  • First-occurrence win handles sub-compositions correctly
  • Tests cover unquoted/single-quoted attrs, attribute order, negative dims, whitespace, and the sub-composition shadowing case — all the real-world variants that would break a naive regex

@jrusso1020 jrusso1020 merged commit 3c7e2f3 into main Jun 1, 2026
35 checks passed
@jrusso1020 jrusso1020 deleted the feat/cli-auto-detect-aspect-ratio branch June 1, 2026 01:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants