mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
When a composition script throws during execution, the GSAP timeline registration never arrives and pollSubCompositionTimelines times out. Previously the render continued with a degenerate 2-frame output and reported success — now it fails loudly. Two changes: 1. Detect composition script runtime errors in the browser console handler and feed them into scriptLoadFailures, triggering the existing fail-fast path (same as script load 404s). 2. Make sub_timeline_script_failure a fatal warning in applyRenderWarningPolicy, alongside audio_processing_failed. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
05275c1e8c
commit
e69be30e98
@@ -1889,13 +1889,16 @@ function recordCaptureWarnings(session: CaptureSession, warnings: readonly Captu
|
||||
function recordSubTimelineWarning(session: CaptureSession, timeoutMs: number): void {
|
||||
if (session.subTimelineWaitOutcome === "ready" || !session.subTimelineWaitOutcome) return;
|
||||
const scriptFailure = session.subTimelineWaitOutcome === "script_failure";
|
||||
const hasRuntimeErrors = session.scriptLoadFailures.some((f) => f.startsWith("runtime-error:"));
|
||||
recordCaptureWarnings(session, [
|
||||
{
|
||||
code: scriptFailure ? "sub_timeline_script_failure" : "sub_timeline_readiness_timeout",
|
||||
message: scriptFailure
|
||||
? "A sub-composition timeline script failed to load"
|
||||
? hasRuntimeErrors
|
||||
? `A sub-composition script threw during execution — timeline registration never arrived (${session.scriptLoadFailures.join(", ")})`
|
||||
: `A sub-composition timeline script failed to load (${session.scriptLoadFailures.join(", ")})`
|
||||
: `Sub-composition timelines did not become ready within ${timeoutMs}ms`,
|
||||
details: { timeoutMs },
|
||||
details: { timeoutMs, sources: [...session.scriptLoadFailures] },
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2066,6 +2069,16 @@ export async function initializeSession(session: CaptureSession): Promise<void>
|
||||
const diagnostic = formatConsoleDiagnostic(type, text, locationUrl);
|
||||
if (!diagnostic.suppressHostLog) console.log(diagnostic.text);
|
||||
appendBrowserDiagnostic(session, diagnostic.text);
|
||||
|
||||
// Composition script runtime errors mean the GSAP timeline registration
|
||||
// can never arrive — same fail-fast treatment as script load failures.
|
||||
// Without this, pollSubCompositionTimelines burns the full timeout and
|
||||
// the render silently succeeds with a degenerate 2-frame output (#3352).
|
||||
if (type === "error" && text.startsWith("[HyperFrames] composition script error:")) {
|
||||
const detail = text.slice("[HyperFrames] composition script error:".length).trim();
|
||||
const compId = detail.split(" ")[0] || "unknown";
|
||||
recordScriptLoadFailure(session, `runtime-error:${compId}`);
|
||||
}
|
||||
});
|
||||
|
||||
page.on("pageerror", (err) => {
|
||||
|
||||
@@ -246,6 +246,45 @@ describe("updateJobStatus", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("blocks sub-timeline script failures in best-effort mode (#3352)", () => {
|
||||
const job = createRenderJob({ fps: 30, quality: "high" });
|
||||
const log = { error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn() };
|
||||
expect(() =>
|
||||
applyRenderWarningPolicy(
|
||||
job,
|
||||
[
|
||||
{
|
||||
code: "sub_timeline_script_failure",
|
||||
message: "A sub-composition script threw during execution",
|
||||
details: {
|
||||
timeoutMs: 45_000,
|
||||
sources: ["runtime-error:decision-tree-123"],
|
||||
},
|
||||
},
|
||||
],
|
||||
log,
|
||||
),
|
||||
).toThrow(RenderQualityError);
|
||||
expect(job.warnings).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("allows sub-timeline readiness timeout in best-effort mode", () => {
|
||||
const job = createRenderJob({ fps: 30, quality: "high" });
|
||||
const log = { error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn() };
|
||||
applyRenderWarningPolicy(
|
||||
job,
|
||||
[
|
||||
{
|
||||
code: "sub_timeline_readiness_timeout",
|
||||
message: "Sub-composition timelines did not become ready within 45000ms",
|
||||
details: { timeoutMs: 45_000 },
|
||||
},
|
||||
],
|
||||
log,
|
||||
);
|
||||
expect(job.warnings).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("fails explicitly strict renders on correctness warnings", () => {
|
||||
const job = createRenderJob({ fps: 30, quality: "high", strictness: "strict" });
|
||||
const log = { error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn() };
|
||||
|
||||
@@ -685,7 +685,13 @@ export function applyRenderWarningPolicy(
|
||||
const hasAudioProcessingFailure = job.warnings.some(
|
||||
(warning) => warning.code === "audio_processing_failed",
|
||||
);
|
||||
if (strictness === "strict" || hasAudioProcessingFailure) {
|
||||
// A script failure means the composition's GSAP timelines can never
|
||||
// register — the render produces a degenerate 2-frame output that looks
|
||||
// like a still image. Fail loudly rather than shipping garbage (#3352).
|
||||
const hasSubTimelineScriptFailure = job.warnings.some(
|
||||
(warning) => warning.code === "sub_timeline_script_failure",
|
||||
);
|
||||
if (strictness === "strict" || hasAudioProcessingFailure || hasSubTimelineScriptFailure) {
|
||||
throw new RenderQualityError(job.warnings);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user