heygen-com/hyperframes · error · Error
motionToGsap: empty track "${track.property}"
Error message
motionToGsap: empty track "${track.property}" What it means
Thrown by buildTween as a defensive guard after the length check passes: track.values[0] is undefined despite values.length >= 2. With a dense array and values.length >= 2 this is unreachable, so in practice it fires only for a sparse/holey array (e.g. [undefined, 1]) — a malformed MotionTrack where the first keyframe slot exists but holds no value. It prevents GSAP from receiving an undefined initial value and emitting a broken tween deep in the pipeline.
Source
Thrown at packages/core/src/figma/motionToGsap.ts:78
const rawEase = track.ease[i - 1] ?? "linear";
const ease = resolveStepEase(rawEase, customEases, counter);
steps.push({ value, duration: (tCur - tPrev) * track.duration, ease });
}
return steps;
}
function buildTween(
track: MotionTrack,
selector: string,
customEases: CustomEaseRef[],
counter: CustomEaseCounter,
): GsapTween {
if (track.values.length < 2 || track.times.length !== track.values.length) {
throw new Error(`motionToGsap: invalid track "${track.property}" (values/times mismatch)`);
}
const initial = track.values[0];
if (initial === undefined) throw new Error(`motionToGsap: empty track "${track.property}"`);
return {
selector,
property: track.property,
initial,
steps: buildSteps(track, customEases, counter),
repeat: clampRepeat(track.repeat),
};
}
export function motionToGsap(doc: MotionDoc): TimelineSpec {
const customEases: CustomEaseRef[] = [];
const counter: CustomEaseCounter = { value: 0 };
const tweens = doc.tracks.map((track) => buildTween(track, doc.selector, customEases, counter));
return { timelineId: deriveId(doc.selector), tweens, customEases };
}
View on GitHub (pinned to c2996c8626)
Solutions
- Build the values array with push()/literal form so no holes appear.
- Validate the track before motionToGsap: values.every(v => v !== undefined).
- Inspect the source of the track (figma motion snippet parser) for an off-by-one in array assignment.
Example fix
// before — off-by-one leaves index 0 undefined const values = new Array(2); for (let i = 1; i <= 2; i++) values[i] = i * 0.5; // values[0] is a hole // after — assign from 0, or build with a literal const values = [0, 0.5, 1];
Defensive patterns
Strategy: validation
Validate before calling
import type { MotionTrack } from '.../figma/types';
export function isDenseTrack(t: MotionTrack): boolean {
return t.values.length >= 2 && t.values.every((v) => v !== undefined);
}
if (!isDenseTrack(track)) throw new Error(`track ${track.property} has holes`); Type guard
import type { MotionTrack } from '.../figma/types';
export function isDenseAnimatableTrack(t: MotionTrack): t is MotionTrack {
return t.values.length >= 2
&& t.times.length === t.values.length
&& t.values.every((v) => v !== undefined);
} Prevention
- Build value arrays with literals or push(), never new Array(n) + index assignment that can leave holes.
- Validate array density at the boundary where MotionTracks enter your code.
- Watch for off-by-one loops that assign starting at index 1.
When it happens
Trigger: A MotionTrack whose values array is sparse ([undefined, 0.5] or values[0] deleted); a builder that allocated array length then failed to populate index 0; JSON deserialisation producing holes after filtering.
Common situations: A motion-snippet parser that does `values[i] = ...` starting at i=1; an array built with `new Array(n)` whose slots were never all assigned; an object spread that dropped the first value.
Related errors
- motionToGsap: invalid track "${track.property}" (values/time
- ref "${refInput}" has no node id — share a link with ?node-i
- figma asset import produced no result for "${refInput}"
- all refs in one import must share a fileKey (batch is per-fi
- figma asset import produced no result for "${refInputs[i]}"
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/6310aeececbaf4e3.
Report an issue: GitHub.