Commit Graph
835 Commits
Author SHA1 Message Date
Vance Ingalls c691869e22 chore: release v0.7.78 2026-07-28 05:27:29 -07:00
Miguel Ángel 3c857d768b chore: release v0.7.77 2026-07-28 02:12:41 +00:00
James 5cad2bc312 chore: release v0.7.76 2026-07-27 05:08:40 +00:00
James 45b458c007 chore: release v0.7.75 2026-07-27 03:38:16 +00:00
James Russo 0cc78c1d42 chore: release v0.7.74 (#2818) 2026-07-26 21:57:54 -04:00
James 477defc7e7 fix(core): avoid live volume probe during render 2026-07-26 22:39:58 +00:00
James 527751c871 chore: release v0.7.73 2026-07-26 21:25:03 +00:00
Ular Kimsanov e2e61b0767 Merge pull request #2798 from heygen-com/feat/agent-native-color-grading
feat(cli): expose agent-native color grading
2026-07-26 03:23:29 -07:00
Ular Kimsanov 7fa85fb057 Merge pull request #2796 from heygen-com/feat/professional-color-grading-core
feat(core): add professional color grading controls
2026-07-26 02:38:42 -07:00
ukimsanov 20f4bde46a test(cli): harden media treatment parity 2026-07-26 01:29:05 -07:00
ukimsanov 9b504045f7 fix(core): register media analyzer subpath 2026-07-26 01:25:42 -07:00
ukimsanov c1dde28980 fix(cli): address media treatment review findings 2026-07-26 01:25:42 -07:00
ukimsanov e446de6023 fix(core): align secondary mask contracts 2026-07-26 01:25:16 -07:00
ukimsanov 9a515858c9 fix(core): preserve curve compiler subpath exports 2026-07-25 23:38:32 -07:00
ukimsanov 1ffef9a262 fix(core): address grading review findings 2026-07-25 23:16:50 -07:00
James Russo 72ae39e3d0 chore: release v0.7.72 (#2800) 2026-07-26 02:01:10 -04:00
ukimsanov f99fc4e568 feat(core): add professional color grading controls 2026-07-25 22:15:30 -07:00
Miguel Angel Simon Sierra 5acbf240cb feat(core): add deterministic keyframe ease runtime 2026-07-25 14:12:16 +02:00
ukimsanov 4582881d00 feat(cli): add agent-first media treatment tools 2026-07-24 18:42:07 -07:00
Ular Kimsanov 7cf64164ed Merge pull request #2752 from heygen-com/feat/media-treatment-runtime
feat(runtime): render media treatments deterministically
2026-07-24 12:05:14 -07:00
James d07d1874b6 chore: release v0.7.71 2026-07-24 19:03:16 +00:00
ukimsanov 944640c328 feat(runtime): render media treatments deterministically 2026-07-24 11:17:22 -07:00
ukimsanov 70213c5a85 feat(core): define media treatment capabilities 2026-07-24 08:56:24 -07:00
Vance Ingalls b9c3e3a4b6 chore: release v0.7.70 2026-07-23 20:59:30 -07:00
James f0034228f5 chore: release v0.7.69 2026-07-23 12:03:36 -07:00
James 71d84ff27f chore: release v0.7.68 2026-07-22 04:41:54 +00:00
James RussoandClaude Opus 4.7 1e2c7d673f fix(core): preserve nested-rule selectors in composition CSS scoping (#2721) (#2733)
Chrome 112+ / Firefox 117+ / Safari 16.5+ support native CSS Nesting.
A nested rule like '.title { … }' inside '[data-composition-id="intro"]
{ … }' resolves at match time to '<parent> .title' via the implicit
'&' prefix.

'scopeCssToComposition' walks every rule via 'root.walkRules' and re-
scopes selectors, but it did so for nested rules too — producing
'[…scope…] .title' inside '[…scope…] { … }', which nesting then
prepends AGAIN to '[…scope…] […scope…] .title'. Since the composition
root only appears once in the DOM, the doubly-scoped selector never
matches — the nested rule appears 'just ignored' as the reporter
described (#2721).

Reproduced on 0.7.66 with the reporter's exact composition. Fix: add
'isNestedInsideAnotherRule' predicate — mirrors the existing
'isInsideGlobalAtRule' — and skip nested rules in the walkRules
callback. Top-level rules still get scoped; their nested descendants
inherit scope naturally via CSS Nesting at match time.

Added two focused tests:
- 'preserves nested-rule selectors so CSS Nesting inheritance works
  (#2721)' — asserts nested '.title' and 'h2' selectors stay verbatim
  while top-level rules keep scoping.
- 'preserves deeply-nested CSS Nesting rules (#2721)' — same rule at
  depth 3.

All 37 existing scopeCssToComposition tests still pass.

Fixes #2721.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-22 00:24:59 -04:00
Xuanru Li 69446e7726 Revert "feat(producer): renderStretch to re-time short compositions across longer scenes (#2676)" (#2730)
This reverts commit e786b78b33.
2026-07-21 19:39:03 -07:00
James 4b6bb8ffa9 chore: release v0.7.67 2026-07-21 23:51:05 +00:00
Hblee 63539a0cde fix(core): apply position edits to SVG elements, not just HTML (#2724) 2026-07-21 16:48:33 -07:00
Xuanru Li e786b78b33 feat(producer): renderStretch to re-time short compositions across longer scenes (#2676)
Linear: VA-1859

## Problem

For a `fit_to_scene` B-roll where the composition's intrinsic timeline (e.g. `data-duration=1.0s` → 30 frames) is shorter than the scene it fills (e.g. 4.8s narration), the producer renders only the intrinsic 30 frames and the downstream compositor frame-holds/PTS-stretches that fixed clip to the scene length. Spreading 30 unique frames over 4.8s starves motion to ~6 effective fps → a visibly choppy result. Root cause: the producer welds one `composition.duration` to both the frame count and the 1:1 seek mapping, with no notion of a target output length.

## Fix

Add optional `renderStretch: number` (default `1.0` = no-op), `renderStretch = intrinsic / target`:

- **Frame count** comes from the target: `outputDuration = intrinsic / renderStretch`, `totalFrames = outputDuration × fps` (`probeStage.ts`). `composition.duration` stays intrinsic (drives video/audio windows).
- **Per-frame seek** is scaled: `time = (frameIndex / fps) × renderStretch`, so the N output frames map across `[0, intrinsic]` — a fresh frame per output frame.

All seek sites go through a single shared `outputFrameToTimelineSeconds(frameIndex, fps, renderStretch)` helper (`core.types.ts`), consumed by every capture path so none can silently diverge:
- parallel (`parallelCoordinator.ts`), `sdr_streaming` (`captureStreamingStage.ts` ×3), `sdr_disk` (`captureStage.ts`), HDR loops.
- DrawElement + static self-verify (`frameCapture.ts`) — ground-truth seek uses the same mapping, so PSNR compares like-for-like (no spurious verification failure on stretched comps).
- Distributed path: `renderStretch` threaded through `DistributedRenderConfig` → chunk workers, and **folded into the plan hash only when `!= 1`** so a pre-stretch cached plan is never reused.

With `renderStretch = 1` (or omitted → `?? 1`): every seek is `×1.0` (IEEE-754 identity), frame counts unchanged, and the plan hash is byte-identical — a provable no-op. `player.ts` absolute-seek is untouched.

## Verify

- typecheck (core + engine + producer): pass. lint/format/fallow/commitlint: pass. `planHash` + `renderRequest` unit suites: pass.
- Adversarial self-review found + fixed three capture-path gaps (streaming, self-verify, distributed) before this revision.
- **Not yet runtime-verified** on a real render — needs a fit_to_scene render at `renderStretch < 1` confirming N distinct frames over the target length (draft until then).

Paired with experiment-framework#42766, which computes and forwards `renderStretch = hf intrinsic / scene duration`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-21 13:30:44 -07:00
Miguel Angel Simon Sierra 84841b8dae chore: release v0.7.66 2026-07-21 14:36:22 +02:00
James 835fa899c7 chore: release v0.7.65 2026-07-21 04:45:59 +00:00
ViaandVia ac9f463108 fix(lint): recognize compiler-derived data-end as legitimate
`bundleToSingleHtml` compiles `data-duration` into `data-end` (in
`compileTimingAttrs`), then calls `validateHyperframeHtmlContract` against
the compiled HTML. The linter's `deprecated_data_end` rule fired on the
compiler's own consistent output — `<audio data-start="0" data-duration="18"
data-end="18">` — because `diagnoseDerivedEnd` unconditionally emitted a
`deprecated-end` diagnostic whenever both attributes were present, ignoring
whether the derived value matched.

Reporters routed this as "raw-source lint passes with 0 errors and 0
warnings, but `check --strict` still logs StaticGuard noise about
data-end without data-duration." Field cluster: cli-feedback crons 61-68,
n=25+ across darwin/arm64, darwin/x64, linux/x64, win32/x64, and versions
0.7.56 through 0.7.64. L3 reporter cite (ts=1784519869): "bundleToSingleHtml
compiles data-duration into data-end, then validates the compiled HTML and
reports its own generated data-end as deprecated."

Fix: `diagnoseDerivedEnd` now stays silent when the paired `data-end`
matches `data-start + data-duration` (within a 1ns epsilon to absorb
IEEE-754 residuals like `0.1 + 0.2 = 0.30000000000000004`). Truly-legacy
authoring shapes — `data-end` alone with no `data-duration`, or a
`data-end` that disagrees with `data-duration` — still fire
`deprecated_data_end`, with a refined message that names the drift on the
conflicting variant.

Facets covered: (a) validator treats compiler-derived `data-end` as legal
when paired with `data-duration`, and (e) recognizes the compile-time
rewrite site (`bundleToSingleHtml` → `compileHtml` → `compileTimingAttrs`).
Facets (b) stderr gating, (c) terminal JSON verdict, and (d) audio-not-
dropped are unblocked transitively — StaticGuard's `console.warn` is
already gated on `!isValid` and never drops audio; the check command
already emits JSON on every terminal path; so once the false-positive
diagnostic stops firing on compiler output, the noisy stderr line and the
misleading "check appears to fail" reporter framing both go away without
further wiring.

Co-Authored-By: Via <noreply@heygen.com>
2026-07-20 13:26:51 +00:00
Miguel Ángel d21883fe05 fix: recursive sub-composition inlining for depth-3+ nesting (#2660)
* fix(core): discover sub-composition hosts to a fixed point during inlining

inlineSubCompositions collected [data-composition-src] hosts from the
document once, before inlining began, then processed that fixed list in
a single flat loop. A host's inlined content could introduce new hosts
of its own (a sub-composition nesting another sub-composition), and
those were never discovered: two-level nesting worked because the
second level's host already existed in the root document, but any
third level's host only appeared inside content the single pass had
already finished walking, so its content silently never rendered.

Host discovery now runs as a work queue: after a host is inlined, the
newly inserted subtree is re-scanned for further hosts, which are
enqueued with their ancestry chain. A host whose source already appears
in its own ancestry is a circular reference and is reported through the
existing onMissingComposition channel instead of being inlined; a depth
ceiling backstops any gap in that check. Existing single- and two-level
fixtures are unaffected.

* fix(producer): assign runtime composition ids to hosts discovered mid-inline

The producer's per-instance runtime id assignment (assignBundledRuntimeCompositionIds)
ran once over the root document's initial hosts, before inlining. With
host discovery now iterating to a fixed point (previous commit), hosts
revealed inside an already-inlined sub-composition were never in that
pre-pass, so the identity map returned undefined for them: two sibling
instances of the same sub-composition, discovered mid-traversal, both
fell back to their shared authored id and clobbered each other's
variablesByComp entry.

hostIdentityMap is now a lazy map: a pre-pass cache hit returns
unchanged, a miss reads the host's authored data-composition-id,
allocates a collision-checked runtime id by scanning the live document,
writes it back, and caches it. No change to the shared inliner's call
signature. Existing single-instance and root-level reusable-template
cases (#2064) are unaffected.

* test(producer): add depth-3 nested sub-composition regression fixture

Adds a minimal three-level chain (root -> level-2 -> level-3) where the
deepest level renders a distinguishing full-frame marker, following the
existing sub-comp-* fixture pattern. Confirmed against the pre-fix
commit (735128a61) in a separate scratch checkout that the fixture fails
without the previous two commits (level-3's marker absent, ~0.4B fewer
matching pixels than the golden expects) and passes with them.

The golden reference video was captured on the team's render host
(devbox) rather than locally, to avoid PSNR drift from font/GPU
differences against whatever renders the regression suite in CI. Local
render against that golden also passes (PSNR ~24.5dB throughout, 0
failed frames of 100 checkpoints), confirming cross-machine consistency
as well.
2026-07-20 14:47:37 +02:00
Miguel Ángel 735128a61a fix(core): install runtime bridge after transport setup (#2558)
## Summary

The Studio preview now installs the runtime bridge only after its transport is ready, preventing initialization-order gaps without changing the bridge contract.

## Stack

Part 1 of 20. Parent: `main`. Next: heygen-com/hyperframes#2559. The golden reference, heygen-com/hyperframes#2387, remains open and unchanged.

## Test plan

- [x] Integrated Studio suite: 2,800 tests passed
- [x] Integrated parser suite: 853 tests passed
- [x] Studio and parser typechecks passed
- [x] Studio and parser production builds passed
- [ ] Per-PR CI completes on the submitted stack

## Post-Deploy Monitoring & Validation

Validation window: first 24 hours after the stack merges. Owner: Studio maintainers. Watch browser console and support reports for `[Timeline]`, `gsap-parser`, failed keyframe mutations, or preview/render easing mismatches. Healthy means edits persist and preview/render agree; revert the first failing layer if authored animation data changes unexpectedly.

---

[![Compound Engineering](https://img.shields.io/badge/Built_with-Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
![Codex](https://img.shields.io/badge/GPT--5-000000)
2026-07-19 21:49:02 -04:00
James 3454a80eb2 test(repo): execute packed subpaths 2026-07-18 18:27:44 -04:00
James 8d9d9c016e refactor(repo): centralize package subpaths 2026-07-18 15:58:23 -04:00
Miguel Ángel 11cd61d1e3 chore: release v0.7.64 (#2630)
Release HyperFrames v0.7.64.
2026-07-18 15:02:20 -04:00
James 562544f68d refactor(core): own edit protocol contract 2026-07-18 13:24:57 -04:00
James f0956e5944 test(compiler): enforce preview render parity 2026-07-18 12:26:52 -04:00
Miguel Ángel feb675a89d chore: release v0.7.63 (#2628) 2026-07-18 04:44:56 -04:00
Miguel Ángel 7f76170956 chore: release v0.7.62 (#2626) 2026-07-17 22:02:49 -04:00
Miguel Ángel e64a22893a fix(studio): preserve alpha proxy playback (#2625)
* fix(studio): preserve alpha proxy playback

* test(cli): pin VP8 alpha proxy pre-resolution

* fix(studio): emit textarea field-sizing CSS

* test(studio): verify textarea CSS generation
2026-07-17 21:56:27 -04:00
Miguel Ángel 2b65b4efce fix(studio): harden composition timeline reliability (#2615)
* fix(studio): preserve composition playback continuity

* feat(studio): drag compositions into the timeline

* fix(studio): collapse expanded composition move aliases

* fix(studio): make timeline cuts atomic

* fix(studio): group inspector gesture history

* test(studio): cover masked text selection

* fix(studio): harden composition timeline reliability

* fix(studio): satisfy CI source gates

* fix(studio): harden composition mutation requests
2026-07-17 14:15:30 -04:00
Miguel Ángel 2be8a62c00 fix(engine): stop compositing phantom duplicates on captureBeyondViewport (#2607)
* fix(core): stop the async media-metadata rebind once render capture starts seeking

scheduleMetadataDurationHydration re-resolves and can swap the captured
GSAP timeline off a debounced loadedmetadata/durationchange event, fully
uncoordinated with the producer's own per-frame renderSeek calls. When a
full-length <video>'s metadata resolves after capture has already begun
(slow I/O, Docker), this races the deterministic BeginFrame capture loop
and can reflow sub-composition state mid-render, producing phase-offset
duplicate content in captured frames (#2550).

Render-mode duration correction already happens deterministically during
the probe stage before capture starts, so once renderSeek has been called
once there is nothing left for this self-correction to do — gate it off
for the rest of the session.

* fix(core): scope the metadata-rebind guard to actual render/export pages

renderSeek isn't capture-exclusive — Studio's own preview iframe falls
back to it for compositions whose timeline overhangs every native
adapter's duration. Gating the HF#2550 fix on renderCaptureSeekStarted
alone silently disabled the metadata-driven duration self-correction for
that live-scrub case too, where it's still needed. Require the render/
export page signal (window.__HF_EXPORT_RENDER_SEEK_CONFIG, set only by
the producer's fileServer.ts) alongside it, and add a regression test
covering the Studio-preview case.

* fix(engine): stop requesting beyond-viewport capture for video comps that don't need it

Root-caused HF#2550 by reproducing the reporter's public repro end-to-end
(not just the timeline-rebind mechanism from the earlier commits in this
branch) on native Linux: instrumented the actual DOM state during a real
capture session and confirmed the sub-composition never double-mounts —
getBoundingClientRect and the timeline's own local time both match the
single, correct DOM tree throughout. The phantom second copy only exists
in the captured screenshot pixels.

Bisected it to captureBeyondViewport: resolveVideoCaptureBeyondViewport
(#1094's tall-portrait fix) forces `Page.captureScreenshot`'s beyond-viewport
path on for any render with a native <video>, regardless of whether the
page's content actually overflows the declared capture height. On
SwiftShader that beyond-viewport path can composite a stale, vertically
offset paint of the page alongside the fresh one for content that fits
entirely within the viewport — producing exactly the reported phase-offset
duplicate. Disabling captureBeyondViewport (repro's video still present)
eliminates the duplicate outright; re-enabling it reproduces the duplicate
byte-for-byte, isolating it as the actual cause.

Adds pageContentExceedsCaptureHeight, a ground-truth measurement of the
page's actual scrollHeight against the requested capture height, and wires
it into initializeSession to downgrade captureBeyondViewport back to false
once the page is settled and it's confirmed unnecessary — the "reliable
clip predictor" the original #1094 fix's ponytail comment flagged as
missing. This keeps #1094's fix intact for content that genuinely
overflows while closing the SwiftShader ghosting hazard for the (common)
case of video that fits inside its own viewport.

* test(producer): add HF#2550 video+sub-composition regression fixture

Checks in the reporter's confirmed real-world reproduction (media
regenerated via ffmpeg testsrc2, matching their public repro repo) as a
regression fixture, with a golden baseline rendered against the fix.

Verified end-to-end via the project's own Docker regression harness:
- Rendering this fixture with the fix produces the golden baseline
  (clean, single flowchart instance, captureBeyondViewport correctly
  downgraded).
- Direct CLI renders (not through this harness) against unpatched code
  reproduce the reported phantom-duplicate artifact reliably (10/10).

Caveat documented in meta.json: the underlying bug is timing-dependent.
Two harness runs against unpatched code, using this same fixture, did
not reproduce the artifact (0/2) — the harness's in-process render path
apparently doesn't hit the same race window a direct CLI process does on
this host. This fixture is a best-effort regression guard and a
preserved real-world repro, not the sole protection — the deterministic
guard is packages/engine/src/services/screenshotService.test.ts's
pageContentExceedsCaptureHeight unit tests, which exercise the actual
fix logic directly.

Also adds an .gitattributes LFS rule for this fixture's source
index.html (744 KB — carries the real project's embedded base64
assets, over the largefiles hook's 500 KB non-LFS limit).

* fix: route HF#2550 fixture binaries through LFS (were committed raw)

filter.lfs.clean/smudge were locally configured as a no-op "cat" in
this repo's shared .git/config, silently disabling LFS filtering for
every worktree. The previous commit's large binaries (output.mp4,
compiled.html, source index.html, source video) landed as raw blobs
instead of LFS pointers as a result. Ran `git lfs install --local
--force` to restore the correct filter commands, then re-staged the
affected files so they commit as proper LFS pointers.

* fix(engine): address capture viewport review feedback
2026-07-17 11:59:45 -04:00
Vance Ingalls c268f5ba85 chore: release v0.7.61 2026-07-17 01:07:09 -07:00
Vance Ingalls f084a7217d Merge pull request #2502 from heygen-com/task4-start-resolver-export
feat(core): export runtime start resolver with documentRef parameter
2026-07-17 00:47:42 -07:00
Vance Ingalls 1ddf9cc331 Merge pull request #2563 from heygen-com/via/thumbnail-id-escape
fix(studio,runtime): CSS.escape ids so digit-leading selectors don't crash
2026-07-17 00:43:31 -07:00
Miguel Ángel e8371a7acc feat(media): alpha-capable authoring proxies (#2598)
* feat(media): alpha-capable authoring proxies

Alpha sources were refused a proxy before the codec map ever asked whether the
browser could decode them, so a ProRes 4444 alpha file (which no browser
previews at all) rendered black forever, while an alpha WebM (which previews
fine) was already covered by the browser-safe check on the next line. The alpha
veto earned nothing and cost the one case that needed help.

Alpha is now a target-codec choice rather than a veto: alpha sources transcode
to VP9 + yuva420p in WebM, everything else keeps the existing H.264/MP4 path
byte for byte. Only files no browser can preview are proxied, which is the rule
the runtime already followed everywhere else.

WebM cannot carry AAC, so the VP9 path uses Opus and drops the MP4-only
faststart flag. PROXY_PARAMS_VERSION moves to v3 so clients stop serving the
previously cached proxies.

Safari does not decode VP9 alpha and still shows black for alpha sources, as it
does today: this is better on Chromium and Firefox and no worse anywhere.

* fix(media): infer proxy variant for rescue

* fix(media): preserve alpha proxy hardening after restack
2026-07-17 03:26:55 -04:00