feat(cli): add --resolution flag to hyperframes init for 4k scaffolding#661
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
vanceingalls
left a comment
There was a problem hiding this comment.
Verdict: approve. Reads cleanly, the regex-based rewriter is the right call for templates (preserves comments and indentation), tests cover both unit and end-to-end paths, and the unknown-flag rejection happens before any directory is created. Validation-up-front + bail-cleanly is the correct UX.
Important
-
important —
packages/cli/src/commands/init.ts:51—RESOLUTION_ALIASEShas"portrait-4k": "portrait-4k"— that's a self-map that does nothing becauseVALID_RESOLUTIONSalready matches the canonical name first. (#665 removes it during dedup.) Harmless today, but it's symptomatic — see the cross-PR comment below. -
important —
packages/cli/src/commands/init.ts:489-510—applyResolutionPresetregex-walks every HTML file and rewrites dimension fingerprints, but a template that only declares dimensions in an external.cssfile will silently keep 1080p#stagestyles. Templates today seem to inline their CSS (sample fixture in the test confirms), but that's a template-author convention, not a guaranteed invariant. Either narrow the doc comment ("inline-CSS templates only — external stylesheets are not patched") or also walk.csssiblings. Add a test that exercises a fixture with no dimension fingerprint at all (the function should be a no-op, not error). -
important —
packages/cli/src/commands/init.ts:476-486—bodyCssRerequireswidth:to appear beforeheight:and within the samehtml, body { ... }block. Hand-authored CSS where the order is reversed (height: 1080px; width: 1920px;) will silently not be rewritten. The htmlParser (PR #660) already learned both orderings viastageMatchReverse; this should too. Add a fixture test with reversed-order CSS — it'll demonstrate the gap.
Nits
-
nit —
packages/cli/src/commands/init.ts:485—viewportReonly matches whenwidth=precedesheight=in the content attribute. Same ordering caveat as above; mostly cosmetic since<meta viewport>ordering is conventional. -
nit —
packages/cli/src/commands/init.ts:445— JSDoc lists 5 fields rewritten; if any future template adds a sixth (e.g. anaria-labelwith dims, an OG image dim), this comment goes stale. Worth a short "this list is enforced by tests, not by code" note. -
nit —
packages/cli/src/commands/init.ts:495-498—dataWidthRehas/gand uses.test()followed by.replace(). With/g,.test()advanceslastIndexand the subsequent.replace()is fine because it's a fresh call, but the pattern is fragile if anyone refactors to reuse the same regex object. Either drop the/gfrom the test (and use.match()) or drop the test and just rely on.replace()(which is a no-op when nothing matches). -
nit —
packages/cli/src/commands/init.ts:64—normalizeResolutionFlaglowercases and looks up — fine. Worth confirming no Windows-y casing issue with--resolution 4K(uppercase K). Looks like.toLowerCase()covers it, but a one-line test would pin it.
Cross-PR
The duplication of VALID_RESOLUTIONS + RESOLUTION_ALIASES + normalizeResolutionFlag between init.ts (this PR) and render.ts (#663) is exactly what #665 has to undo. The right shape would have been to land #660 with normalizeResolutionFlag already exported from @hyperframes/core, then both CLI files consume it. The drift you found ("portrait-4k": "portrait-4k" in init only) is the canonical signal. Stack hygiene nit, not a blocker for this PR — just calling it out so the team's next foundation PR ships the helpers.
Praise
The applyResolutionPreset → scaffoldProject ordering (after patchVideoSrc, after writeTailwindSupport) composes cleanly with existing flags. The "exit before any work on bad input" pattern is correct. End-to-end test with --example blank --resolution 4k is exactly the right level of coverage.
— Vai
miguel-heygen
left a comment
There was a problem hiding this comment.
Requesting changes because --resolution scaffolding can produce internally inconsistent projects for real examples.
applyResolutionPreset() rewrites data-width / data-height, data-resolution, one very specific html, body { width; height; } CSS shape, and a width=..., height=... viewport. Registry examples are not all shaped like the blank template. For example, warm-grain uses:
body, html { width: 1920px; height: 1080px; }#main-composition { width: 1920px; height: 1080px; }<meta name="viewport" content="width=device-width, initial-scale=1.0" />
I reproduced this on the stacked head by applying applyResolutionPreset(..., "landscape-4k") to registry/examples/warm-grain: the data-width / data-height became 3840x2160, but the body and main composition CSS stayed 1920x1080. A generated project then declares 4K metadata while still laying out at 1080p.
Please broaden the rewrite to handle the actual template shapes we ship, and add a regression test against at least one registry-style example such as warm-grain.
I rechecked the live head before posting: dbbef868187a04562a96d862719e333631275df9.

What
Adds a
--resolutionflag tohyperframes initthat scaffolds projects at the chosen canvas preset:landscape(1920×1080),portrait(1080×1920),landscape-4k(3840×2160), orportrait-4k(2160×3840). Aliases:1080p,4k,uhd.Why
PR #660 added 4K to the
CanvasResolutiontype system but every bundled and remote template still shipsdata-width="1920" data-height="1080". Without this flag, the only way to author at 4K is to hand-edit the scaffolded HTML in five places (data-width,data-height,data-resolution, the inlinehtml, body { width/height }CSS, and the<meta name="viewport">tag). This is PR 2 of the 4K stack.Stacked on #660.
How
New
applyResolutionPreset(destDir, resolution)exported frominit.tswalks every.htmlfile in the scaffold and rewrites the dimension fingerprint by regex. Regex (not DOM-parsing) preserves template comments and indentation byte-for-byte — the scaffolded HTML is hand-authored content that templates author for review-friendly diffs.The function is invoked from
scaffoldProject()afterpatchVideoSrc()andwriteTailwindSupport()so it composes cleanly with existing flags (--video,--tailwind).Flag wiring:
--resolution <preset>with the alias map applied vianormalizeResolutionFlag(). Unknown values exit non-zero with a clear error before any directory is created.scaffoldProject().Test plan
init.test.ts:applyResolutionPresetrewrites every dimension fingerprint forlandscape-4kapplyResolutionPresetswaps to portrait dimensions forportrait-4khyperframes init --resolution 4k --example blank --non-interactiveproduces a project withdata-width="3840",data-height="2160",data-resolution="landscape-4k", and 4K CSS--resolution 8kexits non-zero, no directory createdbun run --cwd packages/cli test src/commands/init.test.ts(11/11 pass)docs/packages/cli.mdxhas the new flag in theinittable