Commit Graph

4242 Commits

Author SHA1 Message Date
heygen-rui-bot 319eae3d83 codegen: resync cloud client from EF 3f3ca3ff 2026-09-09 02:55:49 +03:00
miga-heygen 662f96b3f4 chore: release v0.8.32 (#3788)
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
v0.8.32
2026-09-08 19:40:29 -04:00
James Russo efe2404cbd fix(skills): stage media downloads in private directories (#3787) 2026-09-08 19:29:26 -04:00
James Russo 91a34ffc8a fix(studio): claim upload filenames exclusively (#3786)
* fix(studio): claim upload filenames exclusively

* fix(studio): write uploads through exclusive descriptors
2026-09-08 18:51:58 -04:00
James Russo a7bf0d23d9 fix(skills): retain audio metadata file identity during generation (#3778)
* fix(skills): retain audio metadata file identity during generation

* test(skills): isolate audio metadata CLI credentials
2026-09-08 17:35:12 -04:00
James Russo a92dc6865e fix(skills): read BGM logs without pathname prechecks (#3769) 2026-09-08 16:40:20 -04:00
James Russo d66cd6dcc8 fix(catalog): publish cache assets without overwriting entries (#3739)
* fix(catalog): publish cache assets without overwriting entries

* fix(catalog): publish cache assets without overwriting entries
2026-09-08 14:54:27 -04:00
Miguel Ángel 65cc1d6023 fix(shader): keep vertex shaders within their WebGL context (#3672) 2026-09-08 18:12:28 +00:00
Miguel Ángel e1fda7866e fix(engine): use supported VideoToolbox quality options (#3687) 2026-09-08 18:12:07 +00:00
Miguel Ángel 0ce0bb13eb fix(runtime): preserve fractional final-frame visibility (#3696)
* fix(runtime): preserve fractional final-frame visibility

* test(runtime): cover repeated nested fractional tails
2026-09-08 18:11:40 +00:00
Miguel Ángel 6c0ee41ac4 fix(lint): flag autoAlpha on clip elements (#3692) 2026-09-08 18:11:24 +00:00
Miguel Ángel c5b7f6aeab fix(check): retry system Chrome after managed launch crash (#3701) 2026-09-08 18:11:13 +00:00
Miguel Ángel 4a72a98a49 fix(player): pause the hidden loader sheen (#3673) 2026-09-08 18:11:02 +00:00
Miguel Ángel 01744f2b0c fix(media): unify nested start coordinate mapping (#3732) 2026-09-08 18:10:51 +00:00
Miguel Ángel e5a6023228 fix(skills): guard canonical stores from mirror aliases (#3714) 2026-09-08 18:10:44 +00:00
Miguel Ángel 400925f432 fix(keyframes): resolve helper-returned DOM targets (#3688) 2026-09-08 18:10:38 +00:00
Miguel Ángel 5c44cd212c fix(render): probe runtime media source mutations (#3729) 2026-09-08 18:10:34 +00:00
Miguel Ángel 9308eadcfc fix(render): bound and recover capture stalls (#3700)
* test(producer): pin unpinned drawElement stall retry

* fix(engine): bound drawElement frames so a wedged renderer falls back instead of failing (#3172)

* fix(engine): bound drawElement frames so a wedged renderer falls back instead of failing

A single drawElement frame could kill an entire render. On one comp
(caption-editorial-emphasis) drawElementImage returns normally and the renderer
then stops draining its task queue: the setTimeout(…, 0) that schedules
toDataURL never fires, the capture page.evaluate never settles, and 60s later
the stage watchdog fails the whole render. Deterministic on 152.0.7977.30,
always the same frame. Root-cause detail in PRINFRA-488.

Two gaps, both closed here.

1. Nothing below the 60s stage watchdog bounded a frame. Adds a per-frame
   deadline (HF_DE_FRAME_TIMEOUT_MS, default 15000, 0 disables) around the WHOLE
   frame operation, not just the drawElementImage call — the stall surfaces at
   whichever page round-trip comes next, and was observed at both the capture
   and the seek's background-image decode. Bounding one call missed it.

   Deliberately NO per-frame screenshot fallback for this error: once the
   renderer stops scheduling it is wedged for every subsequent round-trip on
   that page. Measured — the screenshot fallback blew the same deadline. The
   frame fails fast instead, so the recovery that actually works can run.

2. That recovery was ineligible. shouldRetryViaPinnedFallback only retried a
   generic capture failure when the worker count was PINNED by the inversion or
   the router; a comp that engaged drawElement on the ordinary single-worker
   path had no whole-render fallback at all. A renderer stall is now retryable
   on any routing — the failure is a property of drawElement itself, and the
   retry re-renders on a fresh page via screenshot. Reported as
   de_fallback_reason "de_renderer_stall" so it is distinguishable from
   capture_error in telemetry, and counted per-session as deFrameTimeouts.

Verified end to end on the repro: previously exit 1 with "stalled: no frame
progress for 60000ms"; now the deadline fires, the render retries via
screenshot, and RENDER_OK with a valid 1920x1080 / 240-frame / 8.0s MP4.

Tests cover the predicate both ways plus the cross-package error match. Engine
1,481 and producer 587 green (audioPadTrim.integration flakes only under
full-suite parallel load — passes in isolation, and on clean HEAD it is green
too, so it is not from this change).

Refs PRINFRA-488

* fix(engine,producer,cli): count drawElement frame timeouts where they happen

`session.deFrameTimeouts` was incremented inside `captureFrameCore`'s catch,
but the deadline races that function from OUTSIDE it — `withFrameDeadline` is
applied by `captureFrameToBuffer`, and `DeFrameTimeoutError` is constructed
nowhere else. So the branch was unreachable: on a wedged renderer the counter
stayed 0, the engine-side stall log never printed, and
`CapturePerfSummary.deFrameTimeouts` reported 0 on every affected render. The
end-to-end recovery worked throughout — `isDeRendererStallError` matches by
name at the producer — only the observability was dead.

Moved the increment and the log into an `onTimeout` hook that fires from the
deadline itself, deleted the unreachable branch and the now-unused
`isDeFrameTimeoutError`, and threaded the count out to
`render_completed.de_frame_timeouts` so the rate is graphable apart from the
other `capture_error` fallbacks. A stall always costs a whole-render re-run,
which is why it deserves its own series.

Also corrected the `fallbackReason` docstring, which still listed only
`psnr | blank | oom | capture_error` after this branch added
`de_renderer_stall`.

`withFrameDeadline` is exported for a two-case fake-timer test: the deadline
fires and calls `onTimeout` once, and a resolving inner promise passes its
value through with no late timer. The first case fails if the counter moves
back inside the work promise.

(cherry picked from commit ec2b2b5b2a)

* fix(render): retry sequential screenshot stalls

* fix(render): bound parallel worker capture phases

---------

Co-authored-by: Vance Ingalls <vance@heygen.com>
2026-09-08 18:10:28 +00:00
Miguel Ángel 78dacc828c fix(skills): accept durationless transition roots (#3683) 2026-09-08 18:10:24 +00:00
Miguel Ángel 776c291f42 fix(check): clip overlap geometry to visible fragments (#3698) 2026-09-08 18:10:02 +00:00
Miguel Ángel 2c14438bb0 fix(lint): flag hidden-style opacity guards (#3694) 2026-09-08 18:09:54 +00:00
Miguel Ángel 4958a9fba5 fix(lint): warn on undefined GSAP color variables (#3723) 2026-09-08 18:09:41 +00:00
Miguel Ángel 00c91d6807 fix(check): allow missing caption overrides (#3716) 2026-09-08 18:09:35 +00:00
Miguel Ángel 8fc8294dbd docs: clarify paused composition roots in project instructions (#3670) 2026-09-08 18:09:29 +00:00
Miguel Ángel 2f7b7a9529 fix(render): reject unsupported ProRes rate controls (#3702) 2026-09-08 18:09:19 +00:00
Miguel Ángel e1ff7e6f53 fix(lint): flag repeated fromTo state leaks (#3699)
* fix(lint): flag repeated fromTo state leaks

* fix(lint): require a timeline baseline for repeated fromTo
2026-09-08 18:09:15 +00:00
Miguel Ángel 131336f5dc fix(skills): bootstrap npm safely on Windows (#3705)
* fix(skills): bootstrap npm safely on Windows

* test(skills): verify npm bootstrap on Windows Node 24
2026-09-08 18:09:10 +00:00
Miguel Ángel 63182f6a93 fix(cli): reject confirmed unsupported SDR encoders (#3679) 2026-09-08 18:08:59 +00:00
Miguel Ángel ab07d67380 fix(studio): preserve timeline DOM identity during hydration (#3681) 2026-09-08 18:08:55 +00:00
Kakumanu Ashok Reddy 250afc313b docs: fix broken Claude Design guide link (#3584)
Co-authored-by: ashokDevs <bhargavcodes3@gmail.com>
2026-09-08 17:56:48 +00:00
heygengenesis[bot] e02722098e fix(engine): isolate static dedup verification seeks (#3768)
Co-authored-by: heygengenesis[bot] <262951085+heygengenesis[bot]@users.noreply.github.com>
Co-authored-by: miguel.sierra <229591595+miguel-heygen@users.noreply.github.com>
2026-09-08 17:56:25 +00:00
Xuanru Li e5d89f770f feat(check): flag connectors that point at nothing and stylesheets that leak into the frame (#3736)
* feat(lint): catch leftover marker heads and guessed marked shafts

Dash-draw on a marked path shows the arrowhead before the shaft exists.
A long marked path that misses every node in both user and screen space
is the same detach as the CTM-paste bug, without the counterfactual.

* feat(check): flag orphan connectors and unbalanced style tags

Catch a visible shaft while fewer than two nodes are on stage (enter-early /
exit-late), and extra </style> that dumps CSS onto the frame.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(check): judge connector_orphan by the shaft's own endpoints

The rule counted anchors stage-wide and skipped whenever two were on. That
tier is every visible text-bearing or opaque element under 15% of the
stage, so a title and a footer alone satisfied it — the check could not
fire on a composition that had any chrome, which is all of them.

Endpoints are now resolved per shaft, against candidates gathered by
layout instead of by visibility, so a node hidden with opacity:0 still
owns the endpoint that meets it and its visibility is what the rule tests.
An endpoint that meets no node is left to connector_detached.

* revert(lint): drop the marker rules this repo would only duplicate

marker_orient_typo and marker_dash_draw_on already run in Zephyr's Python
connector lint, down to the same regexes and the same message and fixHint
strings. Zephyr concatenates its local findings onto this bridge's result
with no dedupe, so shipping them here reports each one twice and counts it
twice, and every later edit has to land in two places.

The browser-check work stays: a marked shaft that meets no node, and an
orphan shaft, are both runtime facts this repo can see and a source-reading
rule cannot.

* chore(check): drop the comments added with the orphan rewrite

* fix(check): score orphan and detach on overflow-opted connector layers

Allow-overflow is a clip opt-out, not a lifecycle skip. Orphan now uses
the same connector names as detach so flow and arrow shafts are judged.

* fix(check): see axis-aligned shafts, and script tags closed with a space

connector_orphan gated on isVisibleElement, which rejects anything whose
bounding box has no height or width. A straight horizontal or vertical
connector is exactly that, and it is the common shape, so the rule was
blind to most of what it exists to catch: across 47 production
compositions it produced nothing. It now tests paint -- display,
visibility and the opacity chain -- and leaves size to the 80px chord
floor it already had. The same 47 compositions now yield three findings,
each naming the endpoint that is still dark.

unbalanced_style_tags stripped scripts with a regex that only matched
`</script>`. `</script >` is valid, so the script survived the strip and
a `"</style>"` string literal inside it counted toward the tag balance,
reporting an error on a composition whose tags are paired. Both the strip
and the closer count now tolerate whitespace before the `>`, which is also
what CodeQL flagged on this branch.

* fix(lint): count style tags in one pass instead of stripping scripts

CodeQL reads the `source.replace(/<script.../)` as an incomplete HTML
sanitizer. It was never one — the rule only needs to not count `<style`
tokens that live inside a script string. A single alternation scan, where
the script branch consumes the whole block, gets the same counts without
a replace.

* test(check): cover the two orphan guards that no fixture was crossing

Neutering `shaftIsPainted`'s opacity check, or the endpoint proximity
threshold, left the suite green. Both are false-positive guards: a
connector staged hidden before its reveal, and a hidden element that
happens to be the nearest thing to an endpoint without belonging to it.

The hidden-shaft case now runs over all four ways a shaft can be
invisible, so the display and visibility clauses are crossed too.

* test: pin the two boundaries raised in review

A closer inside an html comment is already dropped upstream by
stripHtmlComments, so the rule never sees it. The test fails if that
stripping regresses; adding a comment branch to the rule's own scan
would have reintroduced the polynomial-redos pattern that stripper
exists to avoid.

A node hidden with display:none has no geometry, so nothing can place it
near an endpoint and connector_orphan stays silent. connector_detached
does not pick it up either, since it allows a half-attached shaft by
design. That case is uncovered, and the fixture says so.

* fix(check): type the hidden-shaft fixture table as a style record

* fix(check): key connector_orphan by geometry and let a live node win the endpoint

Two defects found in review.

connector_orphan was persistence-tiered but absent from the geometry key,
so several id-less shafts orphaning at one sample each collapsed into a
single finding that then read as held rather than transient. On the 47
corpus compositions this recovers 9 findings across 5 compositions where
3 across 2 were reported; one composition fades its nodes and its
connectors on a shared stagger, so four shafts each outlive a different
node and only one of the four survived the collapse.

The endpoint scan took the nearest candidate and asked whether that one
was hidden, without asking whether a visible box was also in range. A
staged halo sitting on the node it belongs to produced a finding naming
the halo. A visible candidate within threshold now settles the endpoint.

Fixtures added for both, and for the two guards a mutation sweep found
unpinned: the connector-shape test and the dash-offset skip.

* test(check): kill every surviving mutant in the connector_orphan path

Seven guards still passed the suite when deleted: the four candidate
filters, the connector layer's own visibility gate, the defs/marker skip
and the chord floor. Each now has a fixture where that guard alone
decides the outcome.

The earlier sweep that missed them was mutating the wrong copy of a
shared line — connectorAnchorRects and connectorEndpointCandidates carry
the same filter text, and a first-match replace edited the detached one.
Mutations are scoped by enclosing function now.

* test(check): order the halo before the node it hides under

The halo fixture proved nothing. Both boxes contain the endpoint, so both
score gap 0, and the strict tie-break keeps whichever comes first in
document order — which was the visible node. The old code picked the same
winner and stayed silent too.

Putting the halo first makes the tie-break hand it the slot, so only the
visible-candidate check keeps the endpoint attached.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-08 01:22:48 +00:00
blw dc9fb67320 fix(slideshow): retry init at DOMContentLoaded when the children were not parsed yet (#3750)
connectedCallback defers init to a setTimeout(0) so the parser can append
the element's children first. With the bundle loaded from <head>, as
`hyperframes present` emits it, headless Chromium fires that timer while
the parser is still inside <hyperframes-slideshow>: init finds no player
or a half-streamed island, returns, and the deck never binds.

While document.readyState is "loading", init now retries once at
DOMContentLoaded. After parsing there is nothing to wait for, so a missing
or malformed island still fails quietly as before. The subtree check is
split out as locateSlideshowParts so the classification is testable
without the element lifecycle.
2026-09-07 16:03:16 -04:00
James Russo bb89f7507d docs(changelog): weekly digest 2026-08-31–2026-09-07 (#3749)
* docs(changelog): weekly digest 2026-08-31–2026-09-07

Rewritten from the 111 non-merge commits in the window, grouped by theme.
The week was mostly hardening: file reads pinned to checked descriptors,
bounded parser scans, and isolated concurrent writes. Studio agent control
and site capture continued.

Every commit sha and PR number was resolved and verified against the repo,
not hand-typed.

— Rames

* docs(changelog): add weekly changelog video for 2026-08-31–2026-09-07

Embeds the rendered 49.5s digest video. Uploaded to
static.heygen.ai/hyperframes/changelog-videos/weekly-changelog-aug31-sep07.mp4
and verified live before committing the src (200, content-length matches the
object, cache miss).

— Rames Jusso
2026-09-07 15:56:02 -04:00
James Russo b9aae16d6d fix(producer): try Windows junctions before copying cached frames (#3740)
Follow up Anton Sidorov’s junction staging contribution in #2314 on current main.

Co-authored-by: Anton Sidorov aka anticodeguy <a@anticodeguy.com>
2026-09-07 15:52:24 -04:00
Miguel Ángel 30d6f43bdb chore: release v0.8.31 (#3747)
* chore: release v0.8.31

* docs(release): describe the range fix on its own terms
v0.8.31
2026-09-07 12:12:35 -04:00
Miguel Ángel 8958342dd1 fix(studio-server): bridge the node web stream type for the Windows build (#3746)
tsc on the Windows jobs rejects a direct cast from node:stream/web's
ReadableStream to the global one (TS2352), which broke the build after
#3745. Cast through unknown, the bridge the error itself recommends.
2026-09-07 10:57:58 -04:00
Miguel Ángel 8825def610 fix(studio-server): stream preview media byte ranges instead of reading the whole file (#3745)
The Studio preview asset route answered every Range request by reading the
entire file into memory with readFileSync and slicing the window out of the
buffer. A browser refills a playing <video> or <audio> with a fresh Range
request every few hundred milliseconds and issues one per seek, so a source
of a few hundred MB cost a full synchronous read per refill and per scrub
step. The read also blocked the event loop, so the voice track, saves and
the file-change stream all waited behind it. Sources over 2 GiB could not
be served at all, because readFileSync refuses them.

Stream only the requested window with createReadStream, take the size from
stat instead of the buffer, and answer 416 for a range that starts past the
end. Text assets keep the in-memory utf-8 round trip. The sibling static
project server already did this.
2026-09-07 10:55:54 -04:00
Xuanru Li 0d5d3f3eb3 feat(lint): flag video/img src pointing at an audio file (#3741)
* feat(lint): flag video/img src pointing at an audio file

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(lint): drop m4a from the audio src kinds

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 19:08:51 -07:00
Xuanru Li b0ac581d8e fix(lint): see the grouped gsap.set that stages a whole scene at once (#3737)
Hiding several elements in one call is the shortest way to stage a scene, and it was
the one form the hidden-selector extractor could not read: a multi-element array
failed the target regex, which forbade commas, and a single-element array then failed
the selector parse, which accepted only a quoted string or a known alias. So the two
error rules that ask whether a hidden element is ever properly revealed had an empty
hidden set for every grouped hide.

Each part of a group now resolves on its own, and a comma-separated selector string
resolves the same way. The target pattern stays paren-free so a set whose vars are a
variable cannot run past its own closing paren and swallow the next call.

Two false positives the wider hidden set exposed in the fullscreen-overlay rule: a
fromTo at 0 seats its from-vars immediately, so hidden from-vars there mean the
overlay does start hidden; and an overlay hidden by a standalone gsap.set is what
that rule's own fixHint prescribes.
2026-09-06 19:00:33 -07:00
Xuanru Li c59fd162aa fix(check): key content_overlap and text_occluded collapse by pair, and bill a clipped text box once (#2801)
Combines two fixes to the layout audit:

- `staticIssueKey` no longer includes `issue.text` for `content_overlap`
  and `text_occluded`; both builders now emit a unique selector for both
  ends, so the selector pair is the identity. An animating subject
  (count-up, typewriter, rotating word) no longer splits one held
  collision into transient groups.
- A clipping box that is its own nearest constraint reports
  `clipped_text` only, instead of also reporting `text_box_overflow`.
  A non-clipping self-constraint still reports.
2026-09-06 18:54:18 -07:00
Xuanru Li 051336c07b fix(lint): stop three rules matching code a composition only displays (#2811)
A code-explainer composition renders selectors and API calls as text.
`stripJsStringLiterals` blanks string and template contents (offsets
preserved) so `composition_id_selector_in_comment`,
`template_literal_selector` and `raf_animation_loop` scan real code only.

The scanner tracks regex-vs-division context so a regex literal's own
quotes cannot open a phantom string and blank the rest of the script; on
any unbalanced scan it returns the source unchanged, so an unmodelled
parse degrades to the pre-existing behaviour rather than blanking code on
an error-severity gate.
2026-09-06 18:34:02 -07:00
Xuanru Li 39ad2721b3 fix(check): keep auditing when a motion spec is partly unusable (#2805)
Combines two fixes to the motion sidecar path:

- An ambiguous selector no longer aborts the whole spec. It is reported
  as a finding, the assertions that depend on it are skipped and named,
  and every other assertion still evaluates. `assertionTargets` is now
  exhaustive over the assertion kinds so a new kind cannot silently fall
  back to an arbitrary first match.
- A sidecar that will not parse is reported as a spec finding instead of
  ending the run, so the composition is still audited.
2026-09-06 18:02:21 -07:00
James Russo 7a2a691736 fix(catalog): bind asset reads to checked project files (#3735)
* fix(catalog): bind asset reads to checked project files

* fix(catalog): budget the exact bytes published

* fix(catalog): bound directory asset reads
2026-09-06 01:18:16 -04:00
James Russo f1d0c2e553 fix(producer): pin lint entry reads to checked descriptors (#3734)
* fix(producer): pin lint entry reads to checked descriptors

* test(producer): replace lint entry paths portably
2026-09-06 00:12:41 -04:00
James Russo e5b3514118 fix(studio-server): publish waveform caches atomically (#3731)
* fix(studio-server): publish waveform caches atomically

* fix(studio-server): reject linked waveform cache directories
2026-09-05 23:21:36 -04:00
Miguel Ángel 3874990449 chore: release v0.8.30 (#3733) v0.8.30 2026-09-05 23:14:12 -04:00
Miguel Ángel 672ea844a5 fix(cli): clarify publish visibility and claim links (#3730)
* fix(cli): clarify publish visibility and claim links

* style(cli): apply oxfmt to publish visibility test

* chore(skills): regenerate skills manifest for updated references

* fix(cli): stop an in-place re-publish claiming it made the project private

* test(cli): pin in-place visibility copy to the plain re-publish route
2026-09-05 23:10:42 -04:00
Miguel Ángel 1fa31d5dfb feat(cli): keep every icon a site declares and headline the bare mark (#3727)
A brand band shows logo artwork on a transparency checker, where a badge (a
mark knocked out of a full-bleed disc or square) reads as a solid blob. The
capture kept only the first icon that downloaded, so which of a site's icons
survived was decided by ranking alone, and nothing recorded what the survivor
actually looked like.

Keep them all. Each declared icon is written as assets/icon-<rel>-<sizes>.<ext>
and classified as bare-mark, badge, or unknown, then the best one is copied onto
the historical assets/favicon.<ext> stem so a stem match still finds it.

Classification is deterministic and cheap. SVG is read from markup, not
rendered: these files routinely fill via CSS custom properties behind a
prefers-color-scheme query, and librsvg resolves neither, rasterising one real
favicon to a fully transparent image while Chrome draws it correctly. Raster
icons are sampled at the midpoint of each edge rather than the corners, because
a full-bleed disc has four transparent corners and would otherwise read as a
bare mark.

The headline prefers a bare mark, then falls back to the existing ranking.
Preference is binary on purpose: an "unknown" is not promoted above a badge,
or an undecodable .ico would outrank a good SVG purely for being unexaminable.

extracted/icons-manifest.json records every icon, its shape and the evidence
for that shape, plus which file became the headline, its rank, and why. A
choice whose losers are invisible is indistinguishable from having had no
choice, which is how a substituted icon went unnoticed in the first place.

Worth noting for consumers: sites often declare no bare mark at all. An
apple-touch-icon is composited onto an opaque tile per Apple's spec, so it is
a badge, not a mark. The manifest says so rather than implying the preference
was satisfied.
2026-09-05 22:35:45 -04:00
James Russo 97fde27df7 fix(cli): pin Studio bundle, signature and runtime file reads (#3728)
* fix(cli): read Studio bundle files through checked descriptors

* fix(cli): pin Studio signature and runtime artifact reads
2026-09-05 22:23:15 -04:00