feat(core): warn when a registered timeline has children but no duration

Backstop for the same defect the unpause fixes. A timeline that owns children
yet reports a duration of 0 cannot be correct, and the symptom is an entirely
black render that lint, check and validate all pass, because none of them looks
at pixels.

There is no legitimate composition where children span time and the total is
zero, so the check has no false-positive case. Anything that reaches it has a
cause the unpause did not cover, which is exactly the case worth hearing about
rather than capturing frozen frames in silence.

Emitted to the console because the capture session forwards browser console
output into the producer's diagnostics, which is the channel a render actually
surfaces.

Verified by disabling the unpause and rendering the reported composition: the
warning fires through the real pipeline, naming the composition id and the
child count.
This commit is contained in:
Miguel Angel Simon Sierra
2026-08-22 11:55:26 -04:00
parent be6e922b6b
commit fac9bb8f44
+37
View File
@@ -137,6 +137,34 @@ function unpauseNestedTimelines(timeline: RuntimeTimelineLike | null): void {
}
}
/**
* Warn when a registered timeline has children but no duration.
*
* There is no legitimate composition where children span time and the total is
* zero, so this has no false-positive case. Emitted to the console because the
* capture session forwards browser console output into the producer's
* diagnostics, which is the channel a render actually surfaces.
*/
function reportZeroDurationWithChildren(
compositionId: string,
timeline: RuntimeTimelineLike | null,
): void {
if (!timeline || typeof timeline.getChildren !== "function") return;
try {
const childCount = timeline.getChildren(false).length;
if (childCount === 0) return;
console.warn(
`[hyperframes] Composition "${compositionId}" registered a timeline with ` +
`${childCount} child timeline(s) but a duration of 0. Every frame will render ` +
`the t=0 state, producing a blank video that lint/check/validate cannot see. ` +
`A child left paused is the usual cause: only the timeline registered on ` +
`window.__timelines should be paused.`,
);
} catch (err) {
swallow("runtime.init.zeroDurationReport", err);
}
}
export function initSandboxRuntimeModular(): void {
const state = createRuntimeState();
// Own the analytics bridge before any best-effort runtime installation so
@@ -1152,6 +1180,15 @@ export function initSandboxRuntimeModular(): void {
}
}
const rootDurationSeconds = getTimelineDurationSeconds(rootTimeline);
// Backstop for the class of bug the unpause above fixes: a registered
// timeline that owns children yet reports no duration cannot be right,
// and the symptom is an entirely black render that lint, check and
// validate all pass because none of them looks at pixels. Anything
// reaching here has a cause the unpause did not cover, so say so rather
// than capturing frozen frames in silence.
if (!isUsableTimelineDuration(rootDurationSeconds)) {
reportZeroDurationWithChildren(rootCompositionId, rootTimeline);
}
if (!isUsableTimelineDuration(rootDurationSeconds) && rootChildCandidates.length > 0) {
const selectedTimelineIds = rootChildCandidates.map((candidate) => candidate.compositionId);
const compositeTimeline = createCompositeTimelineFromCandidates(rootChildCandidates);