Repository navigation
fix(core): a set written before its tween no longer erases the tween's end - #7397
Merged
Merged
Conversation
Contributor
Contributor
There was a problem hiding this comment.
Approving at 00d4d081: I found no blocker.
What I verified
- The tests fail without the fix. At this head,
gsap.order.test.tsandinit.authoredOrder.test.tspass, 16/16.- With
main'sgsap.ts, exactly the two new cases fail: "shows the tween's end after the fade-in through the render seek…" and "…through the runtime seek…". The other 13 pass, including #7318's panel cases. - With
main'sinit.ts, the init test fails. That matches thefails without the fix:line.
- With
- Nothing else in the runtime breaks. All of
packages/core/src/runtimepasses: 128 files, 2,975 tests.tsc --noEmitfor core and oxlint on the five files are clean. - Plain GSAP agrees with the new behaviour. I wrote a standalone probe that steps the reduced cards film frame by frame. The card is at opacity 1 at 7.5 s whether the set is written before or after the
fromTo. So for the set-first case, this change brings the runtime back to what GSAP shows. - Stagger is handled. A staggered tween builds its own inner timeline, and the inner
tl.tocalls go through the patched prototype too.authoredOrderOfkeeps the outermost stamp belowowner, so a staggered child takes its parent tween's write order, not the inner one. Inner stamps are taken after any earlier set, so the comparison holds either way. - The guards are sound.
_hfAuthored === undefinedstops a call that added no child from re-stamping the previous_recent. When either side has no stamp, the code falls back to the old behaviour. This covers a film built before the runtime loaded,tl.add(gsap.to(…)), and a bundled GSAP.
Non-blocking notes
- My probe shows GSAP's own frame-by-frame stepping also lets the tween's end win in #7318's panel case, where the set is written after the fade. That gives 0 after 1 s, while the runtime asserts 1. So the set-after override is still a deliberate difference from plain GSAP. This PR doesn't widen it, and it reads as intended per #7283, but it's worth knowing write order is not GSAP's own discriminator.
runtime/inspection.tsalso wrapsTimeline.prototype.{to,from,fromTo,set}. Its restorer only unwraps when it is still on top. If inspection hooks before GSAP loads, the stamp wrapper lands above it, so inspection's wrapper stays underneath after dispose. That is harmless, but worth knowing if either patch changes.
miguel-heygen
enabled auto-merge
November 8, 2026 05:35
Collaborator
Author
|
Thanks for the review. Both notes are known and I'm leaving the code as is:
|
… typecheck passes
Contributor
There was a problem hiding this comment.
Re-approving at 5e1d7e1f.
What changed since my approval at 00d4d081: only gsapAuthoredOrder.ts. stampAuthoredOrder now takes object | undefined and narrows internally to core.Timeline.prototype. The runtime logic and the tests are unchanged.
What I verified
- The error I missed is fixed. My earlier pass only ran
tsc --noEmit. Core'stypecheckscript also runstypecheck:runtime(tsconfig.runtime.json).- At
00d4d081, that config reports TS2345 atinit.ts:468:Window["gsap"]has no properties in common with the old parameter type. - At
5e1d7e1f, it is clean.
- At
- Tests still pass.
gsap.order.test.tsandinit.authoredOrder.test.tspass, 16/16. Thefails without the fix:line is unchanged and still accurate. - CI is green on this head. CI, regression, Windows render verification, CodeQL, preview-regression and the Player checks all succeeded.
The non-blocking notes from my earlier review still apply.
Merged
SanHsien
added a commit
to SanHsien/hyperframes
that referenced
this pull request
Nov 9, 2026
Port the adopted upstream fixes from f5bde95 to the current upstream tip: heygen-com#7375 multipart cloud upload, heygen-com#7382 minParallelChunks, heygen-com#7349 HLG header SEI, heygen-com#7369 skip unmounted compositions in lint/check, heygen-com#7381 filesystem-path media in the cloud archive, heygen-com#7383 Safari cut-point pre-roll revert, heygen-com#7373 clock steering between audio time updates, heygen-com#7384 injector test isolation, heygen-com#7378 preview seek retention, heygen-com#7390 virtual timer schedule per worker, heygen-com#7394 CDN script failure naming, heygen-com#7395 menu row radius, heygen-com#7119 decode timeout caller test, heygen-com#7397 authored tween order, heygen-com#7402 capture page clip loading, heygen-com#7412 muted video audio track at cuts, heygen-com#6150 template root variable test, heygen-com#7442 held tail seek once. Deliberate local differences: heygen-com#7067's audio-start hold is not carried into clock.ts, mountedSources comes from heygen-com#6827 as a helper only, cloud/_gen/types.ts omits the image-request types the fork does not generate, and the lint symlink test skips through symlinkOrSkip where the account cannot create links. Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A set written before the tween it sits inside no longer erases that tween's end value in a render or in preview. A film whose cards fade in with a stagger after a positionless
set(cards, {opacity: 0})keeps them visible until its own fade-out, as it does on 0.9.18 and earlier.Why
Since 0.9.19 (#7283, extended by #7318), a set whose time falls inside an earlier-starting tween on the same property is replayed after that tween ends, so the set's value wins. That is right when the film wrote the set after the tween: an override. It is wrong when the film wrote the set first and the tween after: the set is the tween's initial state, and the tween's end is what the author sees.
Both films end up with the same timeline positions. GSAP sorts children by start time and keeps no record of the order they were written in, so the two can't be told apart from the timeline alone. Reduced to four cards:
Rendered at 30 fps with one worker, card pixel at 7.3, 7.6 and 8.2 s: 0.9.14, 0.9.17 and 0.9.18 show the cards (255). 0.9.19 to 0.9.22 show none (background). The cards vanish at frame 218, the moment the stagger ends.
Related work
Refs #7283 and #7318, whose panel case stays as it was: there the set is written after the tween.
How
When GSAP loads, the runtime wraps
Timeline.to,from,fromToandsetso each child they add gets a write-order number (gsapAuthoredOrder.ts, installed beside the force3D setup ininstallFlatGsapTransforms).overlapsOnOneTargetmarks the later write as a set that interrupts only when it was not written before the tween it overlaps. A film built before the runtime loaded has no numbers and behaves as before.Test plan
gsap.order.test.ts, "a set written before the tween it sits inside", through the render seek (every frame equals GSAP stepping in order) and through the runtime's seek and re-render (every frame from the end of the fade-in), with 1, 2, 3, 4 and 6 workers;init.authoredOrder.test.ts, the load hook numbers a timeline's children in write order.gsap.tsboth newgsap.ordercases fail (frame 218, opacity 0 where GSAP shows 1); withstampAuthoredOrderremoved from the load hook the new init test fails; the other 13gsap.ordercases, including fix(core): a set that interrupts a tween holds in a real render at any worker count #7318's panel cases, pass on both sides.packages/core/src/runtime: 128 files, 2,975 tests pass.Not covered: inside the overlap (one frame, 6.33 s in the reduced film) GSAP's own in-order stepping shows the set's value where the runtime's re-render shows the tween's; that differs before and after this change and the runtime's value is the tween's, so the runtime-seek test compares from the end of the fade-in.