fix(engine): descend into repeating nested timelines for call() detection

Address PR #2093 review feedback (Miga, Rames D Jusso):

- The walker treated a repeating nested timeline (total > single) as an
  opaque interval and never descended into it, so a tl.call() living
  inside one would slip past hasTimelineCall detection entirely — the
  "any tl.call() disqualifies" claim wasn't quite literal. Now recurses
  for detection purposes even when the span is already opaque; the
  parent-level interval still dominates for frame-animated-marking, so
  this only widens what counts as "has a call()," never narrows the
  existing interval coverage.
- Restored the totalDuration() vs duration() rationale comment that got
  dropped when the tl.call() detection comment was added above it.
This commit is contained in:
Vance Ingalls
2026-07-08 21:19:22 -07:00
parent 50c4a10234
commit b9321b7489
+14 -2
View File
@@ -1983,6 +1983,8 @@ export async function computeStaticFrameSet(
};
const intervals: Array<{ start: number; end: number }> = [];
let tweenCount = 0;
// totalDuration() (NOT duration()): a repeat/yoyo tween animates past one iteration;
// a repeating timeline is marked opaque over its whole span (conservative).
// A GSAP tl.call() is a zero-duration tween whose vars wire the callback as
// onComplete (and onReverseComplete, fired on backward crossing — GSAP has
// no separate "undo" callback, so both directions invoke the SAME forward
@@ -2003,8 +2005,18 @@ export async function computeStaticFrameSet(
const single = typeof child.duration === "function" ? child.duration() : 0;
const total = typeof child.totalDuration === "function" ? child.totalDuration() : single;
if (typeof child.getChildren === "function") {
if (total > single + 1e-6) intervals.push({ start, end: start + total });
else walk(child, start);
if (total > single + 1e-6) {
intervals.push({ start, end: start + total });
// Still descend for hasTimelineCall even though the repeating
// span is already opaque (its frames are excluded from dedup
// regardless): a call() inside it is a review-flagged detection
// gap otherwise — the arm-time verifier can still forward-seek
// through this span while checking a LATER static run, firing
// the call() and corrupting the page (review).
walk(child, start);
} else {
walk(child, start);
}
} else {
tweenCount++;
intervals.push({ start, end: start + total });