mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
fix(registry): animate mk card offsets with transforms, not top/left
mk-background and mk-clone-wall-transition tween the card's `top`/`left`. Layout properties snap to integer device pixels, so the move stutters under the seek-by-frame capture engine (lint: gsap_non_transform_motion). Both cards sit at top:0/left:0 in CSS, so the values carry straight over to x/y, and in clone-wall the later scale composes cleanly with the translate. Re-rendered both and diffed frames against the previous output — identical, as intended: this changes how the motion is computed, not how it looks. Adds scripts/lint-registry-items.mjs (bun run lint:registry-items), which mounts each item into a throwaway project and lints it. Registry items ship as `<name>.html`, so `hyperframes lint <dir>` fails with "No composition found" and these items had never actually been linted — which is how both errors reached main. Verified the script reproduces the original failure on the pre-fix source. Left as a local command rather than a CI gate for now; wiring it up needs two prior fixes, noted in the PR.
This commit is contained in:
+2
-1
@@ -53,7 +53,8 @@
|
||||
"generate:catalog-previews": "tsx scripts/generate-catalog-previews.ts",
|
||||
"package:codex-plugin": "node scripts/package-codex-plugin.mjs",
|
||||
"upload:docs-images": "bash scripts/upload-docs-images.sh",
|
||||
"prepare": "test -d .git && lefthook install || true"
|
||||
"prepare": "test -d .git && lefthook install || true",
|
||||
"lint:registry-items": "node scripts/lint-registry-items.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^20.5.0",
|
||||
|
||||
+8
-5
@@ -189,13 +189,16 @@
|
||||
);
|
||||
}
|
||||
|
||||
/* bar-mask demo: full-bleed -> rounded card over the stage -> full-bleed */
|
||||
/* bar-mask demo: full-bleed -> rounded card over the stage -> full-bleed.
|
||||
Offset via x/y transforms, not top/left: layout properties snap to
|
||||
integer device pixels and stutter under seek-by-frame capture. The
|
||||
card's CSS base is top:0/left:0, so the transform values match 1:1. */
|
||||
if (CONFIG.bar.demo) {
|
||||
tl.to(
|
||||
card,
|
||||
{
|
||||
top: CONFIG.bar.y,
|
||||
left: CONFIG.bar.x,
|
||||
y: CONFIG.bar.y,
|
||||
x: CONFIG.bar.x,
|
||||
width: CONFIG.bar.width,
|
||||
height: CONFIG.bar.height,
|
||||
borderRadius: CONFIG.bar.roundness,
|
||||
@@ -207,8 +210,8 @@
|
||||
tl.to(
|
||||
card,
|
||||
{
|
||||
top: 0,
|
||||
left: 0,
|
||||
y: 0,
|
||||
x: 0,
|
||||
width: W,
|
||||
height: H,
|
||||
borderRadius: 0,
|
||||
|
||||
@@ -182,13 +182,16 @@
|
||||
/* The wall sits in place from t=0, fully hidden beneath the
|
||||
full-bleed card — the card's shrink IS the wall's reveal. */
|
||||
|
||||
/* 0.2–0.95 outgoing frame shrinks to a rounded card on the wall */
|
||||
/* 0.2–0.95 outgoing frame shrinks to a rounded card on the wall.
|
||||
x/y rather than left/top — layout properties snap to integer device
|
||||
pixels under seek-by-frame capture. Base is top:0/left:0, so the
|
||||
values carry over unchanged, and the later scale composes cleanly. */
|
||||
if (CONFIG.card.enabled) {
|
||||
tl.to(
|
||||
card,
|
||||
{
|
||||
top: 330,
|
||||
left: 640,
|
||||
y: 330,
|
||||
x: 640,
|
||||
width: 640,
|
||||
height: 420,
|
||||
borderRadius: 40,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Lint registry blocks/components the way a user actually receives them.
|
||||
*
|
||||
* `hyperframes lint <dir>` needs an `index.html`, but registry items ship as
|
||||
* `<name>.html`, so pointing the linter at an item directory fails with "No
|
||||
* composition found" — which means registry items were never linted at all.
|
||||
* Two `gsap_non_transform_motion` errors reached main that way.
|
||||
*
|
||||
* This mounts each item into a throwaway project (exactly where `hyperframes
|
||||
* add` would put it) and lints that, reporting only findings for the item's
|
||||
* own file so the host scaffold's noise is ignored.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/lint-registry-items.mjs # every item
|
||||
* node scripts/lint-registry-items.mjs mk-background … # named items
|
||||
*/
|
||||
import {
|
||||
readdirSync,
|
||||
existsSync,
|
||||
mkdtempSync,
|
||||
mkdirSync,
|
||||
copyFileSync,
|
||||
writeFileSync,
|
||||
rmSync,
|
||||
} from "node:fs";
|
||||
import { join, resolve, dirname } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const cli = join(repoRoot, "packages/cli/src/cli.ts");
|
||||
|
||||
// A deliberately boring host: one clip, one registered timeline, so the only
|
||||
// findings that can appear are the item's own.
|
||||
const HOST = `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" data-composition-id="main" data-start="0" data-width="1920" data-height="1080" data-duration="5">
|
||||
<section id="host-slot" class="clip" data-start="0" data-duration="5" data-track-index="1"></section>
|
||||
</div>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
tl.to("#host-slot", { opacity: 1, duration: 0.1 }, 0);
|
||||
window.__timelines["main"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
/**
|
||||
* Rules that assume a standalone composition. Many components ship as
|
||||
* paste-able fragments with no root element at all, so these fire on ~47
|
||||
* long-standing items and would drown the signal. The host provides the root;
|
||||
* the item is not supposed to.
|
||||
*/
|
||||
const IGNORED = [
|
||||
"root_missing_composition_id",
|
||||
"root_missing_dimensions",
|
||||
"multiple_root_compositions",
|
||||
];
|
||||
|
||||
function discover() {
|
||||
const out = [];
|
||||
for (const kind of ["blocks", "components"]) {
|
||||
const dir = join(repoRoot, "registry", kind);
|
||||
if (!existsSync(dir)) continue;
|
||||
for (const name of readdirSync(dir)) {
|
||||
const html = join(dir, name, `${name}.html`);
|
||||
if (existsSync(html)) out.push({ name, kind, html });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const only = process.argv.slice(2);
|
||||
const items = discover().filter((i) => only.length === 0 || only.includes(i.name));
|
||||
if (items.length === 0) {
|
||||
console.error(
|
||||
only.length ? `No registry item matches: ${only.join(", ")}` : "No registry items found.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let failed = 0;
|
||||
for (const item of items) {
|
||||
const proj = mkdtempSync(join(tmpdir(), `hf-lint-${item.name}-`));
|
||||
try {
|
||||
mkdirSync(join(proj, "compositions"), { recursive: true });
|
||||
writeFileSync(join(proj, "index.html"), HOST);
|
||||
copyFileSync(item.html, join(proj, "compositions", `${item.name}.html`));
|
||||
|
||||
const res = spawnSync("bun", [cli, "lint", proj], { encoding: "utf-8" });
|
||||
const lines = `${res.stdout ?? ""}${res.stderr ?? ""}`.split("\n");
|
||||
// Only the item's own file — the host scaffold is not under review.
|
||||
const hits = lines.filter(
|
||||
(l) =>
|
||||
l.includes("✗") && l.includes(`${item.name}.html`) && !IGNORED.some((r) => l.includes(r)),
|
||||
);
|
||||
if (hits.length) {
|
||||
failed++;
|
||||
console.error(`\n✗ ${item.kind}/${item.name}`);
|
||||
for (const h of hits) console.error(` ${h.trim()}`);
|
||||
}
|
||||
} finally {
|
||||
rmSync(proj, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
console.error(`\n${failed} registry item(s) have lint errors.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Registry item lint passed — ${items.length} item(s), 0 errors.`);
|
||||
Reference in New Issue
Block a user