From f9e85c666f687faca623b7df29db1e155af28c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Thu, 25 Jun 2026 15:01:41 +0000 Subject: [PATCH 1/3] perf(engine): reduce init overhead in headless capture sessions Two changes to reduce the ~2.5x captureAvgMs regression between v0.6.42 and v0.7.5 (issue #1715 / #1653): 1. Synchronous GSAP proxy flush: expose flushPendingOperations() as window.__hfFlushSync in the early stub, then call it from initializeSession before pollHfReady. In headless mode there's no UI responsiveness concern, so draining the proxy queue instantly eliminates the dominant init cost for tween-heavy compositions (previously 100 ops/rAF-tick at 33ms intervals). 2. Parallel init polls: run pollVideosReady, pollImagesReady + decodeAllImages, document.fonts.ready, and waitForOptionalTailwindReady concurrently via Promise.all instead of sequentially. These are independent DOM queries that don't depend on each other's completion. Both optimizations apply to screenshot and BeginFrame capture modes. Closes #1715 Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/engine/src/services/frameCapture.ts | 157 +++++++++--------- .../src/generated/hf-early-stub-inline.ts | 2 +- packages/producer/stubs/hf-early-stub.ts | 3 + 3 files changed, 82 insertions(+), 80 deletions(-) diff --git a/packages/engine/src/services/frameCapture.ts b/packages/engine/src/services/frameCapture.ts index 011660056f..9e464dc80b 100644 --- a/packages/engine/src/services/frameCapture.ts +++ b/packages/engine/src/services/frameCapture.ts @@ -963,6 +963,13 @@ export async function initializeSession(session: CaptureSession): Promise await gotoEntryPage(); logInitPhase("page.goto complete"); + // Flush the GSAP proxy queue synchronously instead of waiting for + // rAF-based batch ticks (100 ops/tick at ~16ms). In headless mode there's + // no UI responsiveness concern, so draining instantly eliminates the + // largest init-time cost for tween-heavy compositions. + await page.evaluate(`window.__hfFlushSync?.()`); + logInitPhase("GSAP proxy flush complete"); + const pageReadyTimeout = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG.playerReadyTimeout; await pollHfReady(page, pageReadyTimeout); @@ -974,24 +981,37 @@ export async function initializeSession(session: CaptureSession): Promise await applyVideoMetadataHints(page, session.options.videoMetadataHints); logInitPhase("applyVideoMetadataHints complete"); - // Wait for all video elements to have decoded their CURRENT frame, not - // just metadata. readyState >= 2 (HAVE_CURRENT_DATA) means a frame is - // actually rasterized and ready to paint — at >= 1 (HAVE_METADATA) we - // only know the dimensions, and the first