- Portrait compositions render the full frame when the project was
scaffolded at a different size: html/body are sized to the composition
root at runtime; new lint warning root_dimensions_mismatch (#4005,
fixes#4001)
- render_complete telemetry adds composition_element_tags,
aroll_video_count, heygen_video_count; render_error adds error_name and
failed_stage_code (#4004)
- Catalog docs "Source" accordion titles are plain strings, so docs
search no longer shows [object Object] (#4006)
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
A composition whose root declares portrait data-width/data-height renders
at the correct output size, but a project scaffolded landscape (init
without --resolution) still ships html/body CSS and a viewport meta at the
old landscape size. The stale body's overflow:hidden then clips the
correctly-sized root at the old height, so everything past it renders as
page background instead of real content.
applyCompositionSizing now mirrors the SAME forced width/height it already
computes for the root onto documentElement/body, reusing rather than
recomputing them. overflow:hidden is left untouched (a deliberate
anti-white-bar guard set earlier in the same init pass): once body's own
size agrees with the root it contains, it clips nothing that matters.
Runs in both preview and render, since both load the one bundled runtime
artifact this file builds into.
Adds a warning-level root_dimensions_mismatch lint rule alongside
root_missing_dimensions, comparing the root's dims against the html/body
CSS block and the viewport meta - the two other places a scaffold's
resolution lives besides the root itself. The read-only regexes live in
@hyperframes/parsers (packages/lint cannot depend on packages/cli, which
owns the closest existing pattern in applyResolutionPreset) so a future
caller has one place to update if the scaffold's shape changes, rather
than a rule silently comparing against a guess.
Closes#4001.
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
* chore: release v0.8.42
- Studio preview works for projects whose path has non-Latin-1 characters (#3982)
- Studio render downloads use RFC 6266 headers for non-Latin-1 filenames (#3984)
- check --json reports HDR auto-promotion; render prints one warning (#3985)
- Forced --sdr renders tone-map HDR sources before extraction (#3981)
- Render/preview workers no longer survive as orphans after Ctrl-C or parent exit;
cancellation reaches final artifact promotion; HYPERFRAMES_RENDER_DETACHED=1 for
intentionally detached renders (#3974)
- HDR preflight test coverage for held tails (#3963)
Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
* chore: release v0.8.42 notes — name the #3982 symptom and scope
- #3982 entry: a Studio preview server crash for compositions whose file path
has non-Latin-1 characters (the project directory is not involved)
Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
* chore: release v0.8.42 notes — name the #3982 symptom
- #3982 entry: the uncaught ETag TypeError took the preview server down, so
call it a Studio preview server crash rather than a preview failure
Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
---------
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
`registry_item_added` fires when a catalog block is installed and
`render_complete` fires when a video is produced, but nothing joined them, so
"did this video use the catalog?" had no answer.
`hyperframes add` now records each installed item in `hyperframes.json`
(installed files are plain composition HTML with no provenance marker, so this
manifest is the only record that a file came from the registry), and
`render_complete` reports both the items the project installed and the blocks
the rendered composition actually reaches. An item installed and then never
mounted was tried and dropped, which no add-time event can express.
The scan answering "which sub-compositions does this file mount" now has one
owner, `collectSubCompositionSrcs` in `@hyperframes/parsers`, shared with
lint's `lintMissingOrEmptySubComposition`. It holds two invariants that were
previously restated per call site and got re-derived wrongly: it is a text scan
rather than a DOM query, because `<template>` content is inert and every
sub-composition except the render entry is wrapped in one; and references
resolve root-relative at every nesting level, matching `parseSubCompositions`.
It walks tag by tag rather than running open-ended spans across the whole file,
so a malformed composition cannot stall the render plan.
Also: `registryItems` is declared in the config schema, which closes with
`additionalProperties: false`, with an ajv-backed test pinning every key the CLI
writes; counts are never truncated by the name cap, and the reported used blocks
stay a subset of the reported installed ones, with `registry_items_truncated`
marking a windowed list; and an unreadable manifest reports itself rather than
posing as a project that never used the catalog.
A data-var-src value the runtime refuses to load is dropped at bind time and
the element's authored fallback src renders instead, so the video ships the
wrong media and the render still exits 0. lint said nothing, because the
scheme allowlist only existed inside the runtime.
Move that predicate into @hyperframes/parsers, where both the runtime and the
linter can reach it, and error at lint time on any declared default it
rejects. The value provably cannot load, so there is no false positive.
While reproducing that, lint turned out to report an unrelated missing file:
`\bsrc\s*=` also matches the tail of `data-var-src="bg"` (hyphen to `s` is a
word boundary), and `[^>]*` is greedy, so the variable id beat the real src
earlier in the same tag. Every binding was reported as a missing asset named
after the variable, and `<audio data-var-src>` was told the render would be
silent. All three copies of that regex now share one helper that requires
whitespace before the attribute.
* fix(audio): raise the authoring gain ceiling and carry it through the probes
Builds on #3328, which made the preview graph apply author gain and user volume
exactly once each. That ownership is now correct but everything is still clamped
to 1.0, so a clip authored above unity cannot be heard or rendered.
`HTMLMediaElement.volume` is spec-clamped to [0,1], so both timeline probes lost
a clip's authored gain the moment it also carried a fade: the probe seeded the
element at the clamped value and every sample read back at or below 0 dB, and
the mixer prefers probed keyframes over the static volume. Both probes now
shadow the accessor for their own duration and forward the clamped value to the
native setter, so the authored gain survives while nothing outside the probe
ever sees an illegal volume.
Measured on one 6 s composition, first 4 s: unity -32.8 LUFS, boosted-with-fade
-32.8 before and -27.0 after — +5.8 dB, exactly the gain the clip was authored
at.
One ceiling, defined once in `audioGain.ts` and reachable from both sides: the
render mixer imports it, and the page-serialized probe takes it as a parameter
rather than re-literalling it. User volume stays spec-clamped — it is a fader,
not a gain.
Also holds the percent volume slider above unity in both property panels. That
control tops out at 100%, so one touch would cap a boosted clip and drop up to
12 dB that now genuinely renders; the dB fader that can represent these levels
replaces it in the next PR.
* fix(audio): carry a static above-unity gain onto the preview gain node
Review follow-up.
`setElementVolume` receives the clip's author gain and clamped it to [0,1],
so a static `data-volume` above unity was capped on the WebAudio preview path
while the render honoured it — the exact preview/render divergence this
ceiling exists to close. Automation lanes hid it: they schedule ramps onto the
param directly and never pass through here. The master volume beside it stays
spec-clamped, because a user fader is not a gain.
Verified by mutation: restoring the [0,1] clamp reds the new case.
Also scope the leveller's rationale to this rung — `VOLUME_RANGE` still stops
at unity until the dB fader lands, so "both now span the same range" was
premature — and say why the GSAP-tracking fallback is unity-capped: it reads
back through `el.volume`, which the spec pins to [0,1], so it cannot observe an
above-unity value however wide the clamp gets.
* fix(audio): restore the live test files this branch overwrote, and uncap preview
Review blocker: three files were wholesale copies from the abandoned #3304
branch laid over a two-day-newer base, so they silently reverted work that had
landed in between. CI could not see it — deleted tests do not fail.
- `audioMixer.test.ts` was byte-identical to #3304's head: 1186 lines against a
base of 1353. Gone with it were the `data-playback-start` fallthrough cases
from #3322 — merged 54 minutes before this branch's own merge base — and all
retiming coverage (`playbackRate` 7 to 0, `atempo` 5 to 0), the strict
literal-timing table, and the zero-window cases.
- `mediaVolumeEnvelope.test.ts` dropped the trailing-garbage duration case and
the plateau-retention case.
- `packages/core/package.json` rolled the package version back 0.8.3 to 0.7.109.
All three are restored from `main` with only this PR's additions re-applied on
top, and the subpath export is regenerated by the repo's own script rather than
hand-edited.
Also closes the preview/render split the same review raised. Two clamps had to
go, not one: `setElementVolume` capped the author gain at the transport, and
the first-tick branch in `syncRuntimeMedia` trusted `el.volume` — which is
spec-bound to [0,1] and so cannot represent a boost, opening a boosted clip at
0 dB for one tick before the steady-state branch took over. Both pinned by
tests, both verified by mutation.