From 8fee20a52543dc961983d5e31d3b44125c944416 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Wed, 8 Jul 2026 15:42:35 -0700 Subject: [PATCH] fix(engine): log composition-id attribution on script-failure bail too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address max-effort code-review finding on PR #2045 (confirmed, not addressed by the earlier review-gap commit): the script_failure bail path skipped the composition-id enumeration entirely, so a render with multiple sub-compositions sharing a failed script only logged the raw failed URL(s), never which composition(s) were still waiting on it — a real observability regression versus the pre-#2045 behavior, which always logged the missing-id list on any non-ready outcome. Now enumerate unregistered composition ids unconditionally and log them alongside whichever reason (script_failure or natural timeout) fired. --- packages/engine/src/services/frameCapture.ts | 30 ++++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/engine/src/services/frameCapture.ts b/packages/engine/src/services/frameCapture.ts index 72d1ec030..6f2cba869 100644 --- a/packages/engine/src/services/frameCapture.ts +++ b/packages/engine/src/services/frameCapture.ts @@ -1214,18 +1214,24 @@ export async function pollSubCompositionTimelines( })()`); return "ready"; } - if (!scriptFailureBail) { - const missing = await page.evaluate(`(function() { - var hosts = document.querySelectorAll("[data-composition-id]"); - var timelines = window.__timelines || {}; - var m = []; - for (var i = 0; i < hosts.length; i++) { - if (hosts[i].hasAttribute("data-no-timeline")) continue; - var id = hosts[i].getAttribute("data-composition-id"); - if (id && !timelines[id]) m.push(id); - } - return m.join(", "); - })()`); + // Enumerate the still-unregistered composition ids regardless of bail + // reason — a script-failure bail used to skip this entirely, so a render + // with multiple sub-compositions only named the failed script URL(s), not + // which composition(s) it was still waiting on (review). + const missing = await page.evaluate(`(function() { + var hosts = document.querySelectorAll("[data-composition-id]"); + var timelines = window.__timelines || {}; + var m = []; + for (var i = 0; i < hosts.length; i++) { + if (hosts[i].hasAttribute("data-no-timeline")) continue; + var id = hosts[i].getAttribute("data-composition-id"); + if (id && !timelines[id]) m.push(id); + } + return m.join(", "); + })()`); + if (scriptFailureBail) { + console.warn(`[FrameCapture] Composition(s) still waiting on the failed script: ${missing}.`); + } else { console.warn( `[FrameCapture] Sub-composition timelines not registered after ${timeoutMs}ms: ${missing}. ` + `Compositions that load data asynchronously (e.g. fetch) must register window.__timelines[id] after setup completes. ` +