Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ca6ac33
fix(core): make timing masks safe for source loaders
jrusso1020 Jul 10, 2026
db21552
test(producer): gate source tests by execution lane
jrusso1020 Jul 10, 2026
39655bb
fix(core): capture authored inline opacity at parse time and follow s…
miguel-heygen Jul 11, 2026
b9c899f
fix(studio): stop tween re-inits from baking runtime opacity transients
miguel-heygen Jul 11, 2026
4be78e8
fix(studio): correct gesture commits for scaled and graded elements
miguel-heygen Jul 11, 2026
fc82ec2
fix(studio): crop tool stands down for clips it cannot edit
miguel-heygen Jul 11, 2026
945d18d
fix(studio): route panel property commits to a group-owning set
miguel-heygen Jul 11, 2026
0c81cac
fix(parsers): never emit duplicate vars keys in either writer
miguel-heygen Jul 11, 2026
1a8d4ea
fix(lint): anchor the opacity-zero probe so 0.98 stops matching
miguel-heygen Jul 11, 2026
e99caa8
fix(studio): crop restore value is owned by the lifted element's gesture
miguel-heygen Jul 11, 2026
11b4960
refactor: address review nits
miguel-heygen Jul 11, 2026
f10ef8c
fix(studio): address review findings on graded-element editing
miguel-heygen Jul 11, 2026
42408be
fix(studio): draw the crop UI in the element's rotated frame
miguel-heygen Jul 11, 2026
a83088c
chore: release v0.7.53
miguel-heygen Jul 11, 2026
3e2cf63
fix(producer,engine): gate drawelement on ancestor background-image +…
vanceingalls Jul 11, 2026
860ad7d
fix(producer): withhold de parallel router below a machine ram floor
vanceingalls Jul 11, 2026
2767f81
fix(engine): densify drawelement self-verify with parallel worker count
vanceingalls Jul 11, 2026
2731e31
fix(cli): settle post-seek font subset loads before audit screenshots
vanceingalls Jul 11, 2026
50cfd48
feat(cli): preview --browser-no-gpu for gpu-unstable hosts
vanceingalls Jul 11, 2026
2a17557
fix(producer): disk-headroom gate before hdr raw frame pre-extraction
vanceingalls Jul 11, 2026
bd0b3ec
refactor(producer): stop coupling the hdr disk-headroom gate's contro…
vanceingalls Jul 11, 2026
6683b43
feat(studio): timeline leaf helpers — audio inspector, zoom math, UI …
ukimsanov Jul 11, 2026
2c35874
feat(studio): seek-restore contract for player reloads
ukimsanov Jul 11, 2026
db3b224
feat(studio-server): files route extensions
ukimsanov Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "hyperframes",
"description": "HyperFrames by HeyGen. Write HTML, render video. Compositions, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website-to-video capture for HyperFrames.",
"version": "0.7.52",
"version": "0.7.53",
"author": {
"name": "HeyGen",
"email": "hyperframes@heygen.com",
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hyperframes",
"version": "0.7.52",
"version": "0.7.53",
"description": "Write HTML, render video. Compositions, Tailwind v4 styles, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website-to-video capture for HyperFrames.",
"author": {
"name": "HeyGen",
Expand Down
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://cursor.com/schemas/cursor-plugin/plugin.json",
"name": "hyperframes",
"displayName": "HyperFrames by HeyGen",
"version": "0.7.52",
"version": "0.7.53",
"description": "Write HTML, render video. Compositions, Tailwind v4 styles, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website-to-video capture for HyperFrames.",
"author": {
"name": "HeyGen",
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,36 @@ jobs:
- run: bun run --cwd packages/core build:hyperframes-runtime
- run: bun run --filter '!@hyperframes/producer' test

producer-source-tests:
name: "Producer: ${{ matrix.lane }} tests"
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
lane: [unit, integration]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
lfs: true
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
- name: Install FFmpeg for integration tests
if: matrix.lane == 'integration'
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends ffmpeg
- uses: ./.github/actions/prepare-ffmpeg-bin
- run: bun install --frozen-lockfile
- run: bun run --filter '@hyperframes/{parsers,lint,studio-server}' build
- run: bun run --cwd packages/core build
- run: bun run --filter @hyperframes/engine build
- run: bun run producer:test:${{ matrix.lane }}

# Tests under skills/**/*.test.mjs are bare `node --test` files with only
# `node:` built-in imports. They aren't part of any workspace package, and
# the main `Test` job's `code` path filter excludes `skills/**`, so without
Expand Down
27 changes: 14 additions & 13 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions docs/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,42 @@ Recent HyperFrames releases, including user-facing features, fixes, and migratio

{/* New release entries are prepended by `bun run changelog:draft <version> --write`. */}

<Update
label="HyperFrames v0.7.53"
description="Released - 2026-07-11"
tags={["Release", "Studio", "Producer", "Core"]}
>
This release makes studio manual editing safe for color-graded compositions: edits no longer bake runtime opacity transients into tween bounds (the "element disappears after editing" bug), resize and rotate commit clean per-group values, selecting an element no longer rewrites or destroys its authored clip-path, and the crop tool draws in the element's rotated frame. The GSAP writers also stop accumulating duplicate keys in saved compositions.

## Fixes

- **Studio:** Draw the crop UI in the element's rotated frame ([39f33bd3a](https://github.com/heygen-com/hyperframes/commit/39f33bd3a3bcd2ea20980bca1e871bbc9c6592f3))
- **Studio:** Address review findings on graded-element editing ([5d1cafff8](https://github.com/heygen-com/hyperframes/commit/5d1cafff8225177ee1ae99da85a0ce0ce4e5babd))
- **Studio:** Crop restore value is owned by the lifted element's gesture ([b7fddd548](https://github.com/heygen-com/hyperframes/commit/b7fddd548acf1d268294a7fe71adc9a946ccaa40))
- **Core:** Make timing masks safe for source loaders ([179c942cd](https://github.com/heygen-com/hyperframes/commit/179c942cd2bb3fb37236368fa33b16c4c1ad58a0))
- **Lint:** Anchor the opacity-zero probe so 0.98 stops matching ([fe5296672](https://github.com/heygen-com/hyperframes/commit/fe529667283ad07a2f3cd8440e285ad3d0fc8937))
- **Parsers:** Never emit duplicate vars keys in either writer ([90d77d016](https://github.com/heygen-com/hyperframes/commit/90d77d016ffb75264491bfebb4c227072904d63d))
- **Studio:** Route panel property commits to a group-owning set ([066c3dae3](https://github.com/heygen-com/hyperframes/commit/066c3dae37bbe3397cfc35642076dd30da9743f3))
- **Studio:** Crop tool stands down for clips it cannot edit ([cbfb6ba94](https://github.com/heygen-com/hyperframes/commit/cbfb6ba943ea458bb79f89bbc0611cd81c817831))
- **Studio:** Correct gesture commits for scaled and graded elements ([3525c7ff5](https://github.com/heygen-com/hyperframes/commit/3525c7ff52139a5d63093f7a8863e7fb8ff980e6))
- **Studio:** Stop tween re-inits from baking runtime opacity transients ([5cc14c222](https://github.com/heygen-com/hyperframes/commit/5cc14c2221bb6262ddd0631c9f2df1891f266217))
- **Core:** Capture authored inline opacity at parse time and follow source geometry ([3147c8e06](https://github.com/heygen-com/hyperframes/commit/3147c8e063a72d999447aca8bb0528480bdcd043))
- **Product Launch:** Hoist approved frame videos during assembly ([2aadf450e](https://github.com/heygen-com/hyperframes/commit/2aadf450e718497ac4b0edb7286cf4c8f3da06bc), [#2226](https://github.com/heygen-com/hyperframes/pull/2226))
- **Hooks:** Scope pre-commit build check to target repo, fix stale symlinks ([522d7c93b](https://github.com/heygen-com/hyperframes/commit/522d7c93b7acb16c6ade1376040237ff615dca7b), [#2175](https://github.com/heygen-com/hyperframes/pull/2175))
- **Lint:** Parse HTML structure without regex ([05c3b5503](https://github.com/heygen-com/hyperframes/commit/05c3b5503faf5da1efe5d64985b63db3f762f765), [#2223](https://github.com/heygen-com/hyperframes/pull/2223))
- **Telemetry:** Expose stalled render stages ([87618eef4](https://github.com/heygen-com/hyperframes/commit/87618eef4c65aff78f99f94246c60114a579af0f), [#2220](https://github.com/heygen-com/hyperframes/pull/2220))
- **Lint:** Flag digit-leading element ids ([d51fa7eba](https://github.com/heygen-com/hyperframes/commit/d51fa7eba2e369fd6212cdc59ebaef71e2024f7e), [#2222](https://github.com/heygen-com/hyperframes/pull/2222))
- **CLI:** Correct strict warning hint ([5f22209a8](https://github.com/heygen-com/hyperframes/commit/5f22209a82afbdf3c2a8d1a788b94e9aeeaac689), [#2221](https://github.com/heygen-com/hyperframes/pull/2221))

## Internal

- Address review nits ([67cfae258](https://github.com/heygen-com/hyperframes/commit/67cfae2587e15b0dd6a064d9fdbd4790831905cf))
- **Producer:** Gate source tests by execution lane ([9e7b11998](https://github.com/heygen-com/hyperframes/commit/9e7b11998cb2727b05337f9133eae4647c979644))
- **Repo:** Forbid tracked generated artifacts ([585aa9f6b](https://github.com/heygen-com/hyperframes/commit/585aa9f6b24561fe45ea298787a291390ba8eea2))

[View the full commit range](https://github.com/heygen-com/hyperframes/compare/v0.7.52...v0.7.53).
</Update>

<Update
label="HyperFrames v0.7.52"
description="Released - 2026-07-11"
Expand Down
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@
"lint:fix": "oxlint --fix .",
"check:tracked-artifacts": "node scripts/check-tracked-artifacts.mjs",
"format": "oxfmt .",
"test": "bun run --filter '*' test",
"test": "bun run test:unit",
"test:unit": "bun run --filter '*' test",
"producer:test:classification": "bun run --cwd packages/producer test:classification",
"producer:test:unit": "bun run --cwd packages/producer test:unit",
"producer:test:unit:bun": "bun run --cwd packages/producer test:unit:bun",
"producer:test:unit:vitest": "bun run --cwd packages/producer test:unit:vitest",
"producer:test:integration": "bun run --cwd packages/producer test:integration",
"test:regression": "bun run --cwd packages/producer test:regression",
"player:perf": "bun run --filter @hyperframes/player perf",
"format:check": "oxfmt --check .",
"knip": "knip",
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-lambda/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hyperframes/aws-lambda",
"version": "0.7.52",
"version": "0.7.53",
"description": "AWS Lambda adapter for HyperFrames distributed rendering — handler, client-side SDK, and CDK construct.",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hyperframes/cli",
"version": "0.7.52",
"version": "0.7.53",
"description": "HyperFrames CLI — create, preview, and render HTML video compositions",
"repository": {
"type": "git",
Expand Down
16 changes: 14 additions & 2 deletions packages/cli/src/capture/captureCompositionFrame.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,26 @@ afterEach(() => {
});

describe("seekCompositionTimeline", () => {
it("keeps the existing raced double-frame settle as the default", async () => {
it("keeps the raced double-frame settle and adds a bounded font wait by default", async () => {
const { page, evaluate, waitForFunction } = fakeSeekPage();

await seekCompositionTimeline(page, 1.25);

expect(waitForFunction).not.toHaveBeenCalled();
expect(evaluate).toHaveBeenCalledTimes(2);
expect(evaluate).toHaveBeenCalledTimes(3);
expect(evaluate).toHaveBeenNthCalledWith(1, expect.any(Function), 1.25, false);
expect(evaluate.mock.calls[1]?.[0]).toContain("window.setTimeout(finish, 100)");
// Post-seek font settle: a seek can reveal glyphs whose unicode-range
// subsets only start loading after the next layout (CJK snapshot reports).
expect(evaluate).toHaveBeenNthCalledWith(3, expect.any(Function), 500);
});

it("waitForFontsMs: 0 disables the post-seek font wait", async () => {
const { page, evaluate } = fakeSeekPage();

await seekCompositionTimeline(page, 1.25, { waitForFontsMs: 0 });

expect(evaluate).toHaveBeenCalledTimes(2);
});

it("prefers renderSeek so the runtime synchronizes clip visibility", async () => {
Expand Down Expand Up @@ -128,6 +139,7 @@ describe("seekCompositionTimeline", () => {
fallbackToBridgeAndTimelines: true,
waitForPreferredSeekTargetMs: 500,
animationFrameSettle: "none",
waitForFontsMs: 0,
settleMs: 150,
});
await vi.advanceTimersByTimeAsync(150);
Expand Down
41 changes: 33 additions & 8 deletions packages/cli/src/capture/captureCompositionFrame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { resolveCompositionViewportFromHtml } from "../utils/compositionViewport
const SHADER_TRANSITIONS_TIMEOUT_MS = 90_000;
const CAPTURE_SETTLE_MS = 1500;
const PREFERRED_SEEK_TARGET_WAIT_MS = 500;
const DEFAULT_POST_SEEK_FONT_WAIT_MS = 500;

// The audit-grade seek tuning shared by check and the deprecated inspect/layout:
// bridge+timeline fallback, ordered double-rAF settle, bounded font wait, sleep.
Expand Down Expand Up @@ -257,8 +258,13 @@ export async function seekCompositionTimeline(
);
}

if (options.waitForFontsMs !== undefined) {
await waitForCompositionFonts(page, options.waitForFontsMs);
// On by default: every seek caller here is a visual-audit path (snapshot,
// check, compare, validate, layout) and a seek that reveals unrequested
// glyphs must not screenshot before their font subsets load. Costs one
// evaluate + one rAF when nothing is loading. Pass 0 to disable.
const waitForFontsMs = options.waitForFontsMs ?? DEFAULT_POST_SEEK_FONT_WAIT_MS;
if (waitForFontsMs > 0) {
await waitForCompositionFonts(page, waitForFontsMs);
}
if (options.settleMs !== undefined) {
const settleMs = Math.max(0, options.settleMs);
Expand Down Expand Up @@ -299,12 +305,31 @@ export async function waitForCompositionFonts(
.evaluate((ms: number) => {
const fonts = Reflect.get(document, "fonts");
if (typeof fonts !== "object" || fonts === null) return Promise.resolve();
const ready = Reflect.get(fonts, "ready");
if (!ready) return Promise.resolve();
return Promise.race([
Promise.resolve(ready).then(() => undefined),
new Promise<void>((resolve) => setTimeout(resolve, ms)),
]);
// A seek can reveal glyphs whose font faces were never requested —
// CJK @font-face splits into unicode-range subsets that only load when
// first laid out. `fonts.ready` is already-resolved at that moment, so
// awaiting it immediately races the load request and screenshots blank
// glyphs (wild reports: Traditional Chinese / Microsoft YaHei check
// snapshots). Force a synchronous layout so pending subsets actually
// start loading, give the loader one frame to flip `fonts.status`,
// THEN await readiness.
void document.body?.offsetHeight;
return new Promise<void>((resolveWait) => {
const deadline = setTimeout(resolveWait, ms);
requestAnimationFrame(() => {
const status = Reflect.get(fonts, "status");
const ready = Reflect.get(fonts, "ready");
if (status !== "loading" || !ready) {
clearTimeout(deadline);
resolveWait();
return;
}
Promise.resolve(ready).then(() => {
clearTimeout(deadline);
resolveWait();
});
});
});
}, timeoutMs)
.catch(() => {});
}
Expand Down
Loading