Commit Graph
1549 Commits
Author SHA1 Message Date
Miguel Ángel bff39004b0 fix(preview): the proxy copy for the video at the playhead is made ahead of the rest (#6586)
* fix(preview): the proxy copy for the video at the playhead is made ahead of the rest

* test(preview): the resolveProxy assertions carry the priority option

* fix(preview): the proxy copy asked for last goes first, and a repeat ask moves it to the front

* refactor(preview): the proxy wait loop moves its header and progress handling into helpers
2026-10-27 20:08:57 +00:00
Miguel Ángel 53c9759666 chore: release v0.8.209 (#6562) 2026-10-27 14:34:53 +00:00
Miguel Ángel d6e16aa187 fix(studio-server): undo and redo on Windows wait out a briefly held file (#6559)
* fix(studio-server): undo and redo on Windows wait out a briefly held file

* style(core): format the atomic write test
2026-10-27 14:28:43 +00:00
Miguel Ángel 28f7ee456b fix(core): a page's variable keeps its own default when a scene declares the same id (#6558) 2026-10-27 13:48:51 +00:00
Miguel Ángel 6092785348 fix(studio-server): preview loads root-path media, images and CSS from the project, as render does (#6552)
* fix(studio-server): preview loads root-path media, images and CSS from the project, as render does

* fix(studio-server): preview writes root paths project-relative, the form Studio reads back
2026-10-27 12:29:17 +00:00
Miguel Ángel 58552ede4b test(studio): record each edit-accuracy shard's file writes and reloads when HF_WRITE_TRACE is set (#6532)
* test(studio): record each edit-accuracy shard's file writes and reloads when HF_WRITE_TRACE is set

* style(studio): format the write trace

* fix(core): hash the write trace with sha256

* style(studio): wrap the trace hash helper

* style(studio): format the trace hash helper

* fix(studio): turn the reload log on in the Studio page the trace watches
2026-10-27 11:50:20 +00:00
Miguel Ángel 7053aa90dd fix(core): text a script splits into letters shows once and carries the variable's value (#6543)
* fix(core): text a script splits into letters shows once and carries the variable's value

* fix(core): Studio's mounted and swapped scenes read bound text, and instance values load first

* test(core): the scoped variables table is emitted compact in head
2026-10-27 11:29:02 +00:00
Miguel Ángel f007882ca7 chore: release v0.8.208 (#6529) 2026-10-27 11:12:48 +00:00
Miguel Ángel 859cd2fb9c fix(core): retiming a parked scene in the preview takes effect without a reload (#6522)
* fix(core): retiming a parked scene in the preview takes effect without a reload

* fix(core): an edit that leaves a parked scene without a start or length in seconds reloads instead
2026-10-27 06:15:38 +00:00
Miguel Ángel 33e0ba550b fix(core): an audio clip that never gets data no longer freezes the playhead (#6523) 2026-10-27 06:10:26 +00:00
Miguel Ángel 594df21537 fix(core): a video after a cut plays in sync instead of about 70 ms behind (#6494)
* fix(core): a video after a cut starts early by its measured start delay, so it plays in sync

* test(player): playbench reports how far the picture after a cut runs behind the playhead

* fix(core): a looping video no longer skews the learned video start delay

* fix(core): the video start delay is learned in less code and capped at 100 ms under test
2026-10-27 06:09:05 +00:00
Miguel Ángel bd9b134cbb fix(core): the npm package no longer ships the unminified media sync runtime (#6515)
* fix(core): the npm package no longer ships the unminified media sync runtime

* style(core): a two-line comment for the media clip's volume keyframes

* style(core): the moved media clip type keeps only the comments its names do not say
2026-10-27 05:03:45 +00:00
Miguel Ángel 619b8bc4d7 chore: release v0.8.207 (#6510) 2026-10-27 04:33:06 +00:00
Miguel Ángel 142f246d7c fix(cli): a finished capture no longer fails when Windows refuses to delete its Chrome profile (#6505)
Chrome's profile lockfile is held for a moment after the browser exits, so removing the
capture profile threw EBUSY and a finished capture exited 1. The profile and staging
removals now go through a core helper that retries transient locks and, for cleanup of
finished work, warns instead of failing.
2026-10-27 03:10:11 +00:00
Miguel Ángel 736f38ff4a fix(player): each take plays its own audio when the browser blocks autoplay (#6417)
* fix(player): each take plays its own audio when the browser blocks autoplay

* fix(player): a parent audio proxy of a video clip is seeked when its lag holds, not rate-chased

* style(core): keep the steering policy's comment share where it was
2026-10-27 01:49:09 +00:00
Miguel Ángel 6b5d068e4a fix(core): a video no longer runs ahead of the playhead or past its out-point (#6438)
* fix(core): a video no longer runs ahead of the playhead or past its out-point

* fix(core): keep a looping video playing when the playhead wraps before it
2026-10-27 01:45:05 +00:00
Miguel Ángel 95e3930faf fix(core): audio clips join on time and their volume lane follows the audio (#6419)
* fix(core): audio clips join on time and their volume lane follows the audio

An incoming <audio> clip is now pre-seeked to its in-point like a video, and
a playing audio element that drifts is steered by rate instead of seeked
while it sounds. A routed clip's volume lane and fades are re-aimed at the
element's own position whenever it starts, is seeked or changes rate.

* fix(core): sounding audio is nudged at most 10%, and a larger lag seeks under a short dip

Speech audibly changes tempo past a 10% rate nudge, and a bare seek clicks.
Audio that holds a lag rate cannot close in about a second is seeked under a
15 ms dip on its routed graph; an unrouted element is seeked plainly. Video
keeps its steps.

* fix(core): a routed clip's automation has one owner, its element, looping included

A rate change re-aimed a routed clip's lane on the context clock and again
when the element took the new rate. Only the element re-aims it now. A
looping element's wrapped position is placed on the pass the transport is in,
so a looping bed's lane follows it too.

* style(core): wrap a steering comment to the line limit

* refactor(core): one drift policy for playing media, exported for the parent proxy

The steering steps, the audio cap and limit, and the steering state move to
runtime/mediaSteer.ts as steerMedia(el, offset, baseRate, settled) and
forgetSteering(el), exported as @hyperframes/core/runtime/media-steer so the
player's parent-frame proxy uses the same policy instead of a second one.

* style(core): trim the drift policy's comments to the package's share

* fix(core): the dipped seek lands on the frame-start media time

Routes the one currentTime write this branch adds through mediaElementTime,
so Chrome's truncated currentTime cannot land it a frame early.

* test(core): expect the plain audio seek at the frame-start media time
2026-10-27 00:09:27 +00:00
Miguel Ángel 097cc094b7 fix(studio): a reload refused because the page is missing mid-edit warns instead of erroring (#6491) 2026-10-26 23:53:48 +00:00
Miguel Ángel 75bee42919 fix(core): paused preview seeks land on the composition's own frame rate (#6460)
* fix(core): preview seeks land on the composition's own frame rate

* fix(studio): frame step, frame readout and ruler use the composition's frame rate

* fix(studio): an end trim previews the clip's last frame at the composition's frame rate

* refactor(studio): ruler labels follow the ticks' frame rate and fps parsing splits by form

* style(studio): format the ruler label helper and its test

* chore(inspector): raise the package size budget to the measured size plus headroom
2026-10-26 22:54:21 +00:00
Miguel Ángel 020e6c99f3 fix(check): a mask that cuts descenders and a shared scene's own selector are handled (#6454)
* fix(check): a mask that cuts descenders and a shared scene's own selector are handled

* test(core): update the page-build goldens for the scoped selector rewrite

* fix(core): a shared scene's authored-id selector rewrites to the mount without a second pattern
2026-10-26 18:58:49 +00:00
Miguel Ángel 85fdc9aa88 chore: release v0.8.206 (#6469) 2026-10-26 15:00:57 -04:00
Miguel Ángel 3e16603d37 fix(render): a PNG's colour tags no longer change its pixels in preview, check, snapshot or render (#6453)
* fix(render): a PNG's colour tags no longer change its pixels in preview, check, snapshot or render

* fix(render): the play and present server also serves a PNG without its colour tags

* fix(render): bundled pages inline a PNG without its colour tags too
2026-10-26 17:48:57 +00:00
Miguel Ángel e3e93aa7fe fix(core): paused preview and render show the same, correct video frame (#6432)
* feat(core): one function gives the media time that shows the frame starting at an instant

* fix(core): paused preview and render show the same, correct video frame

* fix(core): register the media-clock subpath in its descriptor and keep the reason beside the test

* fix(core): media seeks lead by 10 us so Chrome's timestamp rounding never shows the prior frame

* fix(core): generated pages lead media seeks by a literal, on every generator branch
2026-10-26 17:48:51 +00:00
Miguel Ángel 4a5ee73614 fix(producer): a composition with no timeline-registering script no longer waits 45 s at setup (#6436)
* fix(producer): a composition with no timeline-registering script no longer waits 45 s at setup

The render waited the full timeout for every composition that never registers a timeline, such as one driven by canvas or requestAnimationFrame.

* fix(producer): module and importing scripts still count as able to register a timeline

* fix(producer): a failed script keeps the readiness wait, and the page scan runs once
2026-10-26 17:28:56 +00:00
Miguel Ángel 0979e32a8d fix(core): a composition behind a wrapper takes its host's start (#6435)
* fix(core): a composition behind a wrapper takes its host's start

Fixes #4334.

* fix(core): a composition inside a timed wrapper starts with the wrapper

The nearest host or timed wrapper above a mounted root sets its start, so its clock starts when the wrapper shows it, matching how CSS animations already pick their clip.

* test(producer): re-pin the test classification digest
2026-10-26 16:48:57 +00:00
Miguel Angel Simon Sierra b4eab1a8c1 chore: release v0.8.205 2026-10-26 12:41:40 -04:00
9ecb13ed69 fix(producer): deduplicate scene font faces before embedding (#6395)
* fix(producer): deduplicate scene font faces before embedding

* fix(producer): address font deduplication review

* docs(help): document render font-face deduplication

The troubleshooting guide this section originally targeted was folded
into docs/help.mdx on main, so the section now lives under the render
troubleshooting entries there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: James <james.russo@heygen.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 16:06:29 +00:00
jbernard077andClaude Fable 5.1 36a24aeb85 fix(core): the fonts gate starts loading declared faces no laid-out text uses yet (#6415)
* fix(core): the fonts gate starts loading declared faces no laid-out text uses yet

Before composition scripts run, waitForFonts forces one layout and awaits
document.fonts.ready. A face that only script-built text uses has not started
loading at that point, so ready does not wait for it and the script measures
the fallback face. Start every face still unloaded after the forced layout,
under the existing timeout; a load that rejects falls through to the same
timeout warning instead of stopping the scripts.

Refs #5112

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(core): keep the fonts gate's comment share level; cover a face that never settles

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-26 14:56:59 +00:00
Miguel Ángel 2d1b5cfbd2 chore: release v0.8.204 (#6423) 2026-10-26 10:16:23 -04:00
Miguel Ángel dc56b3d20a perf(studio): the layer walk skips clips the runtime hid, so scrubbing a long film stays smooth (#6416)
The off-canvas indicators (always mounted) and the Layers panel rebuild their layer list every
100 ms while a scrub mutates the preview, and each rebuild read the computed style of every element
of every scene, on screen or not. The runtime already hides everything inside a clip its visibility
pass hid, so those subtrees hold no layer: the walk now skips a clip marked data-hf-clip-hidden
with an inline visibility of hidden. The marker constant moves to studio-preview-mark so the runtime
and Studio share one owner.
2026-10-26 13:22:09 +00:00
Miguel Ángel ff0a56340b chore: release v0.8.203 (#6412) 2026-10-26 07:20:07 -04:00
Miguel Ángel 84fd701daa feat(studio): preview copy progress, retry and a remembered quality (#6366)
* feat(studio-server): a copy being made says how far it has got, and can be retried

* feat(core): the preview reports its lighter copies' progress and retries failed ones

* feat(studio): the preview quality and its lighter copies' progress live in the player store

* feat(studio): every host forwards the preview quality, and the choice is kept per computer

* feat(studio): the preview quality knows if a person or the app chose it

* fix(core): the first report of the lighter copies is not held back by the throttle

* test(studio): the preview quality preference test writes under the real key

* fix(core): a quality change reports its copies at once, not after the throttle

* style: format the preview quality files

* feat(core): report how playback is going, and what the preview holds, to the host

* fix(studio): the preview quality levels come from a browser-safe core entry

* style: format the new entry's files

* style: drop two comments the file's ratchet and the citation check refuse

* feat(core): tell which lower levels would give a video a lighter copy

* fix(studio): drop the old level's playback health when the level changes

* feat(studio-server): preview facts carry the probed codec of every video

* refactor(studio): route the preview's reports through a table for the complexity gate

* fix(core): a copy's retry flag is sent on the first ask only, not on every poll

* fix(core): type the health report and restore the player hook's complexity directive

* style: cut four comments the comment ratchet refuses

* fix(studio): keep playerStore.ts within its 600-line cap

* fix(core): a scene mounting does not restart the playback health window

* fix(core): the playback health window restarts only when the display scale changes

* fix(core): a repeated display scale no longer restarts the playback health window

* fix(studio): route the preview's reports by an explicit list of message types
2026-10-26 09:20:07 +00:00
Vance Ingalls f8d51f3146 chore: release v0.8.202 (#6404) 2026-10-26 07:55:04 +00:00
Vance IngallsandClaude Opus 5.5 07a126d80f fix(core): preview audio fades play as ramps on the audio clock, so cuts no longer click (#6397)
* fix(core): preview audio fades play as ramps on the audio clock, so cuts no longer click

data-fade-in / data-fade-out were folded into one volume per 50 ms sync tick and applied as
a step (el.volume, or gain.value in the Web Audio transport), so a 20 ms fade never played
and each clip started and stopped at near full level: a click at every cut. The export
applies the fades (ffmpeg afade) and does not click.

The transport now books each clip's fades on a gain of its own when it schedules the
clip, for both decoded buffers and captured media elements: a linear ramp from where the
clip starts sounding up to its fade-in, and down to silence at its end. The sync tick hands
the transport the level without the fade (onElementVolume's new fifth argument, sent only
where a fade applies), so a booked fade is not applied twice. The plain el.volume fallback
keeps its stepped fade.

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

* fix(core): one doc block on setElementVolume

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

* fix(core): keep the comment share where it was

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

* fix(core): booked fades hold through pre-roll, follow rate changes, skip unbounded ends

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

* chore: raise the producer unpacked budget to the measured size plus 1%

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 06:25:24 +00:00
Miguel Ángel 8d773fc68a feat(cli): lambda render --width/--height at a whole-number multiple renders at that scale (#6390) 2026-10-26 04:56:01 +00:00
Miguel Ángel 3ff8cd8683 chore: release v0.8.201 (#6377) 2026-10-26 04:50:26 +00:00
Miguel Ángel 38d9aeb5ea feat(core): lighter preview copies of heavy videos below full quality (#6365)
* feat(core): tell which video files deserve a lighter preview copy, and at which size

* feat(studio-server): serve lighter copies of heavy playable files, a keyframe every second

* feat(core): the preview plays lighter copies of heavy videos below full quality

* style: shorten two comments

* style: shorten a comment

* style: shorten a comment

* fix(core): format the preview quality files and split the copy sizing for the complexity gate

* style: format the studio-server preview files

* fix(core): type the copy job's level as a preview quality

* chore(producer): raise the package size budget for the preview runtime growth
2026-10-26 03:53:21 +00:00
Miguel Ángel f32e2a6fbe fix(core): pressing play puts a sped-up or looping video at the right moment in one seek (#5800)
* fix(core): pressing play puts a sped-up or looping video at the right moment in one seek

Pressing play, or seeking while playing, placed every video as if it played at 1x from its start and never looped. The media sync that runs right after knew better, saw a gap of seconds on a 2x or looping clip, and seeked again: two decoder resets per press. Both now read one function for where a media element belongs in its source at a given time, so the first placement is the only seek.

* fix(core): play keeps a video's own speed and skips seeking a video already in place

Setting the transport rate wrote that rate onto every media element, so a 1.15x clip played at 1x until the next media sync put it back; Studio sends a rate on every Play. The media sync already sets each element to its own rate times the transport rate, so the rate change now leaves elements alone and lets that sync apply it. Pressing Play also no longer seeks a video that is already at its source time, since every seek flushes the decoder.

* fix(core): pause and play leave media alone that another feature or the slideshow is driving

Play and pause placed every in-window video at the playhead, including a video the grading preview or a Studio scrub holds while paused, and slide media while the slideshow drives media itself. They now ask the media sync which clips are the runtime's to move.

* refactor(core): trim comments the comment-share check rejects
2026-10-26 02:15:41 +00:00
Miguel Ángel 84b6319346 perf(core): keep long-media analysis responsive and sample the whole clip (#5793)
* perf(studio): music beat analysis no longer freezes the page or runs during playback

* fix(core): media-treatment --analyze finishes on long HDR clips

Grade analysis decoded the clip from the start and kept every frame until the
fps filter had emitted five samples. Past 50 s the filter rate floors at 0.1
fps, so it always decoded the first 40 s at full rate and resolution: a
3-minute 4K60 10-bit HLG clip timed out at 15 s, and every long clip was
graded from its first 40 s only.

Each sample now seeks to the middle of one fifth of the clip and decodes one
frame. The timeout applies per sample and its error names the sample time and
HYPERFRAMES_ANALYZE_TIMEOUT_MS.

* refactor(core): name the keyframe seek instead of commenting it

* fix(core): count one frame per grade sample on ffmpeg 8
2026-10-26 00:15:51 +00:00
Miguel Ángel 3ad80aa82e fix(core): a script's hidden clip stays hidden after a jump or a re-entry (#6355)
* fix(core): a script's hidden clip stays hidden after a jump or a re-entry

* fix(core): re-hide an ended clip unless the tween hid it
2026-10-26 00:13:12 +00:00
Miguel Ángel b054bb98fe perf(core): look up the root only for composition elements when sizing a clip window (#6361) 2026-10-25 23:42:45 +00:00
Miguel Ángel 6bc0b68be0 perf(core): a film opens and seeks without pre-styling every hidden scene, cuts stay smooth (#6356)
* perf(core): a film opens and seeks without pre-styling every hidden scene, cuts stay smooth

* style(core): format the warm-on-play test
2026-10-25 23:32:57 +00:00
Abdullah M. Nazif 3c016f6280 fix(core): preserve composition startup error stacks (#6353) 2026-10-25 23:26:10 +00:00
Akshar Patel 04df17cccb fix(sdk): keep font and image declarations after object edits (#6281)
* fix(sdk): preserve font and image declarations after object edits

* fix(studio): preserve variable object defaults when editing declarations

* fix(studio): show font and image values in bind warnings
2026-10-25 23:01:32 +00:00
Abdullah M. Nazif c194969242 fix(skills): load dotLottie through its browser ESM entry (#5562)
* fix(skills): load dotLottie through its browser ESM entry

* chore: prepare conflict resolution against current main

* fix(skills): retain local Lottie setup after syncing main

* chore: prepare conflict resolution against current main

* fix(skills): retain browser dotlottie setup in current runtime guidance

* chore: prepare conflict resolution against current main

* fix(skills): refresh dotLottie fingerprints against current main

* chore: prepare conflict resolution against current main

* chore: merge main and refresh dotLottie skill manifest

* chore: prepare conflict resolution against current main

* fix(skills): keep Lottie setup guidance in its reference
2026-10-25 23:01:29 +00:00
Abdullah M. Nazif 707c150cbd fix(cli): include external srcset images in project archives (#6335) 2026-10-25 22:55:35 +00:00
Miguel Ángel 553f2c5880 fix(core): a timeline set that hides a timed clip hides it in preview and render (#6351)
* fix(core): a timeline set that hides a timed clip hides it in preview and render

* fix(core): tell a script's hidden clip from the runtime's by the declaration's priority

* fix(core): a scene a script hides takes its timed children with it
2026-10-25 22:53:10 +00:00
Miguel Ángel 0ae48c9f33 fix(core): a root without data-duration stays on screen after its GSAP timeline ends (#6354)
A root composition with no data-duration took its own GSAP timeline's length as its visibility
window, so when media made the composition longer, the whole frame went black from the end of
that timeline in preview and render. The root's window is now open-ended, bounded by the
composition's length. In a framed preview, the duration the runtime stamps on GSAP targets for
Studio's timeline no longer hides the clips inside them either: the ancestor chain check now
skips discovery stamps, as the clip's own visibility pass already does.
2026-10-25 22:12:42 +00:00
Miguel Ángel 33685ef2be fix(core): a self-closed audio group no longer hides the clips after it (#6344)
* fix(core): a self-closed audio group no longer hides the clips after it

HTML does not close a custom element on `/>`, so `<hf-audio-group id="sfx" />` takes in every
later sibling, and the runtime's inert style (`display:none`) hid them in preview, snapshot and
render. The style is now `display:contents`: an empty group still makes no box, and anything
inside one renders as written. A lint warning names the elements a group holds, and the docs and
mixing reference show the group closed.

* style(core): cut the audio group comments to the comment length limits

* fix(core): a muted audio group still hides what sits inside it

A muted bus (`data-hidden`) is hidden by an inline display:none that the bus rule outranked, so
clips inside a muted self-closed bus kept painting while their audio and video frames dropped.
2026-10-25 21:44:32 +00:00
Miguel Ángel 2c10eb0a33 chore: release v0.8.200 (#6337) 2026-10-25 20:50:21 +00:00