Commit Graph
2126 Commits
Author SHA1 Message Date
Vance Ingalls 35c231e35f Merge pull request #2255 from heygen-com/07-11-fix_cli_surface_video_frame-injection_failures_in_snapshot_output
fix(cli): surface video frame-injection failures in snapshot output
2026-07-11 15:36:41 -07:00
vanceingalls 6d5a7711f3 feat(producer): route eligible parallel screenshot/beginframe renders to streaming 2026-07-11 22:34:46 +00:00
vanceingalls 2416e9bcd5 feat(producer): routing predicate for non-drawelement parallel streaming 2026-07-11 22:34:13 +00:00
Miguel Ángel 9d91c2a23e fix(render): scale timeout for long video encodes (#2244) 2026-07-11 18:32:48 -04:00
Miguel Ángel 687883124f fix(skills): hold frame content through transitions (#2235)
* fix(skills): hold frame content through transitions

* test(storyboard): cover normal transition worker roots

* style(skills): format transition injectors

* chore: refresh skills manifest
2026-07-11 18:32:18 -04:00
Miguel Ángel c18391b98c fix(lint): honor data-no-timeline on root compositions (#2228) 2026-07-11 18:32:15 -04:00
Miguel Ángel 6b7431bf45 fix(producer): render -c <scene> uses the scene's own duration, not the project's (#2087)
* fix(producer): render -c <scene> uses the scene's own duration, not the project's

When rendering a single sub-composition standalone (`hyperframes render -c
compositions/scene.html`), the producer extracts the scene's mount from
index.html and wraps it in a shallow clone of the master root. That clone
kept the master's `data-duration`, so the standalone composition advertised
the whole project's length instead of the scene's own: a 2s scene rendered
for the full 12s project, and a master that derives its length from sibling
mounts (now removed) produced "Composition has zero duration".

Re-point the extracted wrapper's `data-duration` at the scene's own, read
from the scene file's `<template>` root (the source of truth for that scene),
with a fallback to the mount's `data-duration`. Full-project renders are
unaffected — they never take the extraction branch.

Verified end-to-end via the pre-capture duration gate: a 2s scene now resolves
to 2s and a 10s scene to 10s (both were 12s), while the full index render stays
at 12s. Adds unit coverage for both the scene-file and mount-fallback paths.

* test(producer): distinguish scene and mount durations
2026-07-11 18:32:12 -04:00
Vance Ingalls fecd7dc1d3 Merge pull request #2248 from heygen-com/bf-reuse-telemetry
feat(producer): surface beginframe no-damage reuse counters in perf summary and telemetry
2026-07-11 15:31:58 -07:00
Vance Ingalls edc0a1624b fix(cli): surface video frame-injection failures in snapshot output 2026-07-11 14:42:44 -07:00
Vance Ingalls b3d851d65a fix(shader-transitions): pin clones to their measured live box, not composition size
Addresses review feedback on #2254: the previous fix hardcoded every clone
to the full composition width/height regardless of the source scene's own
sizing, silently overriding any authored explicit width/height. Measuring
each scene's getBoundingClientRect() while still live in the document (its
inset:0 already resolves correctly against the real ancestor chain there)
and reapplying that exact box to the clone fixes the 0x0 collapse without
ever clobbering an author's own layout.
2026-07-11 14:42:43 -07:00
Vance Ingalls 9e3ff630f4 fix(shader-transitions): pin cloned scene roots to composition dimensions 2026-07-11 14:42:43 -07:00
Vance Ingalls 3338a6f350 fix(cli): fold canvas pixel hashes into the frozen-sweep fingerprint 2026-07-11 14:42:43 -07:00
Vance Ingalls c20e20235b refactor(producer): stop coupling the hdr disk-headroom gate's control flow to error-string matching
Addresses non-blocking review feedback on #2256: the previous shape used
err.message.includes(...) inside a catch to distinguish a real headroom
violation from a statfsSync failure — a future message tweak would silently
fail open (statfs-unsupported and headroom-violation would take the same
code path). Now statfsSync failure returns early (skip the gate) and the
headroom check/throw happens in plain control flow outside any try/catch.
2026-07-11 14:14:09 -07:00
Vance Ingalls 9a1afce43c fix(producer): disk-headroom gate before hdr raw frame pre-extraction 2026-07-11 14:14:09 -07:00
Vance Ingalls 3dc08c0868 feat(cli): preview --browser-no-gpu for gpu-unstable hosts 2026-07-11 14:14:08 -07:00
Vance Ingalls 8bbd5a6496 fix(cli): settle post-seek font subset loads before audit screenshots 2026-07-11 14:14:08 -07:00
Vance Ingalls 1b95af8bbb fix(engine): densify drawelement self-verify with parallel worker count 2026-07-11 14:14:08 -07:00
Vance Ingalls ddcd380a1a fix(producer): withhold de parallel router below a machine ram floor 2026-07-11 14:14:08 -07:00
Vance Ingalls e37ebe7999 fix(producer,engine): gate drawelement on ancestor background-image + tail verify sample (#2247)
## What

Two fixes from adversarial testing of the DE parallel router (10 hostile comps, routed vs screenshot-baseline PSNR). The router itself held — both bugs are in general drawElement fast capture, and one slipped past self-verify.

### 1. Compile gate: ancestor background-image (`producer`)

`drawElementService`'s per-frame ancestor fill replicates what lies behind the captured subtree by walking up the DOM for the nearest non-transparent **`backgroundColor`**. A background-**image** (`linear-gradient`, `url()`) on `body`/`html`/a wrapper reads as transparent in that scan, so a deeper ancestor's solid color paints instead wherever the subtree leaves pixels uncovered.

Measured repro: body `linear-gradient` + html solid color + an element shrinking late in the comp → DE paints the html purple instead of the body gradient. 30.9 dB min frame vs baseline, visually unmistakable. Identical damage single-worker and parallel — general DE bug, in every wild DE render matching this (very common) authoring pattern.

Fix: `detectAncestorBackgroundImage()` in the compiler (DOM-aware — inline styles on the root's ancestor chain + `<style>` rules resolved via `querySelectorAll`, so class-selected wrappers are covered; backgrounds *inside* the root are deliberately not matched). New compile gate `ancestor_background_image`, same shape as the 3D/mix-blend gates, bypass `HF_FAST_CAPTURE_ANCESTOR_BG=true`.

### 2. Self-verify tail sample (`engine`)

The verify grid sampled at `(i+1)/(k+1)` → [20/40/60/80]% of the timeline. The damage above starts at ~79% and peaks after the last sample — verification **passed** on output that bottomed at 30.9 dB (threshold 32 dB would have caught it, it just never looked there).

Fix: `computeDeVerifySampleFractions()` — first k−1 samples evenly spaced, last pinned at 95%. Default k=4 grid becomes [25/50/75/95]%. Kills the whole late-onset damage class, not just this repro.

## Validation

- Repro comp (body gradient + shrink reveal): now gates → baseline route, 54.7 dB avg vs ground truth (was 41.6 avg / 30.9 min with the purple surround)
- Control comp (nested stacked fades, routed): still routes, verify grid `[90, 180, 270, 342] of 360`, passes, 60.6 dB avg — unchanged
- Full adversarial matrix context: 6/10 comps routed clean (49–68 dB min), blend/3D gated correctly, animated-canvas damage caught by verify at 16.5 dB with clean revert, video comps route legitimately (frames pre-extracted)
- Tests: 7 new detection cases (`htmlCompiler.test.ts`), 5 new grid cases (`frameCapture-verifySampleFractions.test.ts`); `compileStage.test.ts` + `frameCapture.test.ts` suites green

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-11 14:13:09 -07:00
vanceingalls 2cd8a750e3 docs(producer): note attempt-scoped beginFrameReuse semantics after partial retries 2026-07-11 20:32:20 +00:00
Miguel Angel Simon Sierra 3b081a44ae chore: release v0.7.53 2026-07-11 15:59:07 -04:00
Miguel Ángel 03c47dbb37 Merge pull request #2230 from heygen-com/fix/studio-graded-element-editing
fix(studio): graded elements survive manual editing (disappear/resize/rotate/crop/panel)
2026-07-11 15:57:42 -04:00
vanceingalls dde3afb72e feat(producer): surface beginframe no-damage reuse counters in perf summary and telemetry 2026-07-11 19:44:24 +00:00
Miguel Angel Simon Sierra 39f33bd3a3 fix(studio): draw the crop UI in the element's rotated frame
Selecting a rotated (cropped) element appeared to straighten it: the crop
dim and dashed window were drawn on the axis-aligned bounding box, so the
bright window was a straight rectangle and the element's rotated corners
were masked to near-black — while the DOM transform was untouched.

clip-path applies in the element's LOCAL frame, before its transform, so
the crop visualization now renders inside a container rotated with the
element: readElementCropFrame decomposes the computed 2D matrix into
angle + per-axis scale (element scale finally factored into the px
mapping too) and 3D/unparseable transforms keep the axis-aligned
presentation. Pointer deltas rotate into the element frame before the
inset resolvers, so edge/pan drags track the rotated handles correctly.
2026-07-11 15:34:41 -04:00
Miguel Angel Simon Sierra 5d1cafff82 fix(studio): address review findings on graded-element editing
Review follow-ups (both reviewers, all findings):

- resize captures scope to the resize group: convert-to-keyframes
  resolvedFromValues and the whole-offset backfill pass the group filter,
  so an opacity-touching intro tween can't ride into a converted scale
  tween (the rotation fix's contract, now uniform across intercepts)
- commitStaticSet resolves every group's target set BEFORE committing and
  coalesces groups landing on the same legacy mixed set into one commit —
  the second commit can no longer chase a stale group-derived id
- installAuthoredOpacityCapture also stamps an element the moment it GAINS
  data-color-grading at runtime (attributeFilter), not just at insertion
- both writer twins now share the same emitted-set dedupe shape
- applySoftReload's positional tail becomes a SoftReloadOptions object
- readAllAnimatedProperties builds the group-filtered key set immutably
  instead of deleting from the set mid-iteration
- applyAuthoredInlineOpacity documents the priority-lossy round-trip
- the marquee hit-test reads activeCompositionPathRef like its neighbors

New tests: resize intercept (scale route + group filter + non-uniform
longhands), after-write-HTML / stamp / empty-stamp opacity restore, the
no-op-commit-with-missed-instant-patch soft-reload contract, and the
runtime-gained-grading stamp.
2026-07-11 15:18:49 -04:00
Vance Ingalls 0809c98518 fix(producer,engine): gate drawelement on ancestor background-image + tail verify sample 2026-07-11 12:00:25 -07:00
Miguel Angel Simon Sierra 67cfae2587 refactor: address review nits
- merge gsapResizeIntercept's duplicate module imports
- move the core-constant imports to the file headers (picker, domEditingDom)
- justify the cross-realm HTMLElement casts (iframe-realm nodes fail
  instanceof; access is duck-typed)
2026-07-11 14:58:21 -04:00
Miguel Angel Simon Sierra b7fddd548a fix(studio): crop restore value is owned by the lifted element's gesture
The deselect restore read a ref recomputed from RENDER state — on a direct
A→B selection switch, state re-syncs to B before A's effect cleanup runs,
so after a committed crop gesture A was restored with B's crop string (or
lost its crop when B had none). The committed value is now written at
gesture-commit time (tri-state: none committed / crop removal / the exact
committed string), so cleanup never touches render state. Adds component
tests for lift/restore ordering, including the direct A→B switch and the
uneditable-clip stand-down.
2026-07-11 14:58:17 -04:00
James 9e7b11998c test(producer): gate source tests by execution lane 2026-07-11 10:38:02 -07:00
James 179c942cd2 fix(core): make timing masks safe for source loaders 2026-07-11 10:04:27 -07:00
James 585aa9f6b2 chore(repo): forbid tracked generated artifacts 2026-07-11 01:20:22 -07:00
Miguel Angel Simon Sierra fe52966728 fix(lint): anchor the opacity-zero probe so 0.98 stops matching
gsap_from_opacity_noop matched 'opacity: 0' as a prefix — an authored
'opacity: 0.98' triggered the rule. One boundary-anchored predicate now
owns the exactly-zero test for both block and inline declarations,
including a block's last declaration without a trailing semicolon.

domEditingDom now imports the grading contract attribute from core
instead of re-declaring the literal.
2026-07-11 04:08:07 -04:00
Miguel Angel Simon Sierra 90d77d016f fix(parsers): never emit duplicate vars keys in either writer
A parsed timeline set carries immediateRender in extras; the recast
statement builder ALSO pushed the flag unconditionally, so every
split/re-add of a set wrote 'immediateRender: true, immediateRender:
true' into the file, doubling on each pass. Both writer twins (recast +
acorn) now emit each vars key exactly once, properties winning over
extras, and reconcileEditableProps skips newProps keys it already
preserved.
2026-07-11 04:08:04 -04:00
Miguel Angel Simon Sierra 066c3dae37 fix(studio): route panel property commits to a group-owning set
commitStaticSet merged every property into the FIRST set found for the
selector: a panel W edit on an element whose only set was positional
produced tl.set("#el",{x,y,width}) — a mixed-group set the split
machinery exists to prevent — labeled "Set 3D transform" in undo.

Commits now batch per property group into a set that owns that group
(exact group match, then a mixed set already carrying the group, then a
fresh off-timeline gsap.set), with undo labels derived from the group
(Move layer / Resize layer / Rotate layer / Set 3D transform).
2026-07-11 04:08:00 -04:00
Miguel Angel Simon Sierra cbfb6ba943 fix(studio): crop tool stands down for clips it cannot edit
The always-on crop lifted EVERY selected element's clip-path and restored
only what it could parse as a px inset — selecting an element with a
circle/polygon/percentage clip visually un-clipped it, and deselecting
deleted the authored clip outright.

readElementCropInsets is now tri-state (zeros = no clip, null = a clip
the tool can't represent): uneditable clips get no lift and no handles,
and the lift restores the pre-lift inline value verbatim unless a crop
gesture actually committed.
2026-07-11 04:07:57 -04:00
Miguel Angel Simon Sierra 3525c7ff52 fix(studio): correct gesture commits for scaled and graded elements
- resize on a scale-driven element commits per-axis scale (scaleX/scaleY
  for non-uniform drags) with keyframe normalization to the longhands, and
  clears the width/height draft so size can't double-apply; the intercept
  moves to gsapResizeIntercept.ts
- the drop frame applies the corrected position synchronously in the same
  microtask chain as the soft reload (no network-window jump), and the
  draft pins the anchor through accumulated moves on scaled elements
- gesture size/position math divides by the element's own content scale
- convert-to-keyframes resolves current values through the property-group
  filter for ALL capture passes (opacity/rotationX from unrelated tweens
  no longer leak into a rotation commit), and a grading-hidden source's
  opacity is read from its canvas, not the inline hide
- canvas pointer-down confirms the hover target with a synchronous
  hit-test before starting a marquee (stale-hover race lost selections)
2026-07-11 04:07:06 -04:00
Miguel Angel Simon Sierra 5cc14c2221 fix(studio): stop tween re-inits from baking runtime opacity transients
Editing commits made elements vanish or dim permanently: invalidating the
whole timeline (or re-running the composition script on soft reload) made
GSAP re-capture tween bounds while runtime transients were live — the
grading hide's opacity 0, or a mid-flight tween value — so from()/to()
bounds got poisoned and the element rendered invisible from then on.

- patch only the edited tween in place, never timeline.invalidate()
- soft reload restores every animated element's authored inline opacity
  (after-write HTML first, parse-time stamp as fallback) before the script
  re-runs and re-captures
- a paired x/y commit whose second half is a no-op (changed=false) still
  applies its instant patch, so panel edits reflect without deselecting
2026-07-11 03:34:21 -04:00
Miguel Angel Simon Sierra 3147c8e063 fix(core): capture authored inline opacity at parse time and follow source geometry
The color-grading engine hides its source element with inline
'opacity: 0 !important', so any code that later reads or re-captures the
element's opacity sees the hide instead of the authored value. Stamp the
authored inline opacity on every [data-color-grading] element at document
parse time (MutationObserver installed at runtime-bundle eval, before any
composition script runs) and prefer the stamp when hiding/restoring.

Also re-sync the grading canvas when the source's inline geometry mutates
(rAF-throttled style observer): a studio drag moves the source via its
transform, which fires no media event, so the visible canvas froze in
place until the next seek.
2026-07-11 03:33:32 -04:00
Miguel Ángel 522d7c93b7 fix(hooks): scope pre-commit build check to target repo, fix stale symlinks (#2175)
* fix(hooks): scope pre-commit build check to the actual target repo

The PreToolUse hook matched any Bash command containing "git commit" and
always ran this repo's bun build/lint/typecheck from its own process cwd,
even when the command targeted a different repo (e.g. a sibling worktree
reached via a leading `cd`). Resolve the real target directory from the
command text first, and skip silently for any repo that isn't this one.

* fix(lefthook): force-add already-tracked files under gitignored paths

The format hook's auto-restage (`git add {staged_files}`) exits non-zero
for any staged file that lives under a gitignored directory (e.g.
.claude/settings.json, tracked despite .claude/ being ignored for worktree
noise), silently aborting the whole commit even though the file was
already correctly staged.

* fix(producer): repoint stale puppeteer symlinks to the pinned 25.x install

packages/producer's tracked node_modules symlinks still pointed at
puppeteer@24.43.1, which no longer exists after a fresh install resolves
package.json's ^25.2.1 range to 25.3.0 — breaking the producer TypeScript
build with a missing puppeteer-core module error.

* fix(producer): stop tracking node_modules symlinks

packages/producer/node_modules was accidentally swept into a prior commit
despite the repo-wide node_modules/ gitignore rule, and its ~30 tracked
symlinks silently drift from whatever bun install actually resolves —
the exact cause of the stale puppeteer symlinks fixed earlier in this
branch. CI always runs bun install --frozen-lockfile before building, so
nothing depends on these being pre-committed.
2026-07-11 00:58:12 -04:00
Miguel Ángel 05c3b5503f fix(lint): parse HTML structure without regex (#2223)
* fix(lint): ignore scripts inside quoted attributes

* Address PR review feedback (#2223)

- replace repeated attribute scanner with quoted tag ranges
- remove the Fallow complexity finding

* refactor(lint): parse HTML structure with htmlparser2

* fix(lint): traverse template style sources

* test(lint): cover nested template style sources
2026-07-11 00:03:07 -04:00
Miguel Ángel 87618eef4c fix(telemetry): expose stalled render stages (#2220)
* fix(telemetry): expose stalled render stages

* fix(telemetry): preserve capture data on terminal stage events

* fix(telemetry): fix calibration TDZ crash, tag encode/assemble, extend heartbeat cadence

capture_calibration referenced captureStageObservationData before its
declaration (later in the same scope), which would throw a ReferenceError
for any render hitting the calibration path. Hoist the closure and split
workerCount's declaration from its resolution so calibration can safely
read it as undefined before capture strategy resolves worker count.

Also address the two non-blocking review items: wire encode/assemble
stages through captureStageObservationData for consistent tagging, and
extend the heartbeat schedule to repeat every 120s after the initial
30/60/120s ramp instead of going dark on stalls beyond two minutes.
2026-07-10 23:59:08 -04:00
Miguel Ángel d51fa7eba2 fix(lint): flag digit-leading element ids (#2222) 2026-07-10 23:03:17 -04:00
Miguel Ángel 5f22209a82 fix(cli): correct strict warning hint (#2221) 2026-07-10 22:57:29 -04:00
Vance Ingalls 7498eb4a3a chore: release v0.7.52 2026-07-10 19:47:59 -07:00
Miguel Ángel b95ddd74d5 fix(cli): honor direct entries in keyframe shots (#2217)
* fix(cli): honor direct entries in keyframe shots

* fix(core): rebase entry-authored asset paths for direct-entry bundling
2026-07-10 22:32:26 -04:00
Miguel Ángel de4e85add6 fix(skills): align core contract with check (#2218) 2026-07-10 21:37:35 -04:00
Vance Ingalls 469cfb3a32 Merge pull request #2115 from heygen-com/de-parallel-router-failure-telemetry
feat(producer,cli): full telemetry visibility for DE parallel-router/inversion failures
2026-07-10 18:24:11 -07:00
Vance Ingalls 2d17e34b90 Merge pull request #2189 from heygen-com/fix/sdk-template-gsap-resolver-parity
fix(sdk): include template GSAP scripts in resolver parity
2026-07-10 18:22:34 -07:00
Vance Ingalls 392dd410a5 Merge remote-tracking branch 'origin/main' into de-parallel-router-failure-telemetry
# Conflicts:
#	packages/cli/src/telemetry/config.ts
2026-07-10 18:15:12 -07:00
Vance Ingalls a61f7de8d7 fix(sdk): align template script traversal 2026-07-10 17:36:09 -07:00